summaryrefslogtreecommitdiff
path: root/includes/specials
diff options
context:
space:
mode:
authorPierre Schmitz <pierre@archlinux.de>2010-07-28 11:52:48 +0200
committerPierre Schmitz <pierre@archlinux.de>2010-07-28 11:52:48 +0200
commit222b01f5169f1c7e69762e0e8904c24f78f71882 (patch)
tree8e932e12546bb991357ec48eb1638d1770be7a35 /includes/specials
parent00ab76a6b686e98a914afc1975812d2b1aaa7016 (diff)
update to MediaWiki 1.16.0
Diffstat (limited to 'includes/specials')
-rw-r--r--includes/specials/SpecialActiveusers.php195
-rw-r--r--includes/specials/SpecialAllmessages.php581
-rw-r--r--includes/specials/SpecialAllpages.php180
-rw-r--r--includes/specials/SpecialAncientpages.php28
-rw-r--r--includes/specials/SpecialBlankpage.php19
-rw-r--r--includes/specials/SpecialBlockip.php433
-rw-r--r--includes/specials/SpecialBooksources.php4
-rw-r--r--includes/specials/SpecialBrokenRedirects.php42
-rw-r--r--includes/specials/SpecialCategories.php13
-rw-r--r--includes/specials/SpecialConfirmemail.php14
-rw-r--r--includes/specials/SpecialContributions.php318
-rw-r--r--includes/specials/SpecialDeletedContributions.php243
-rw-r--r--includes/specials/SpecialDisambiguations.php2
-rw-r--r--includes/specials/SpecialDoubleRedirects.php28
-rw-r--r--includes/specials/SpecialEmailuser.php23
-rw-r--r--includes/specials/SpecialExport.php69
-rw-r--r--includes/specials/SpecialFewestrevisions.php21
-rw-r--r--includes/specials/SpecialFileDuplicateSearch.php23
-rw-r--r--includes/specials/SpecialFilepath.php4
-rw-r--r--includes/specials/SpecialImport.php15
-rw-r--r--includes/specials/SpecialIpblocklist.php64
-rw-r--r--includes/specials/SpecialLinkSearch.php18
-rw-r--r--includes/specials/SpecialListUserRestrictions.php162
-rw-r--r--includes/specials/SpecialListfiles.php22
-rw-r--r--includes/specials/SpecialListgrouprights.php74
-rw-r--r--includes/specials/SpecialListredirects.php9
-rw-r--r--includes/specials/SpecialListusers.php34
-rw-r--r--includes/specials/SpecialLockdb.php6
-rw-r--r--includes/specials/SpecialLog.php12
-rw-r--r--includes/specials/SpecialMIMEsearch.php20
-rw-r--r--includes/specials/SpecialMergeHistory.php38
-rw-r--r--includes/specials/SpecialMostlinked.php37
-rw-r--r--includes/specials/SpecialMostlinkedcategories.php2
-rw-r--r--includes/specials/SpecialMostlinkedtemplates.php33
-rw-r--r--includes/specials/SpecialMostrevisions.php9
-rw-r--r--includes/specials/SpecialMovepage.php138
-rw-r--r--includes/specials/SpecialNewimages.php81
-rw-r--r--includes/specials/SpecialNewpages.php38
-rw-r--r--includes/specials/SpecialPopularpages.php12
-rw-r--r--includes/specials/SpecialPreferences.php1315
-rw-r--r--includes/specials/SpecialPrefixindex.php28
-rw-r--r--includes/specials/SpecialProtectedpages.php47
-rw-r--r--includes/specials/SpecialProtectedtitles.php6
-rw-r--r--includes/specials/SpecialRandompage.php61
-rw-r--r--includes/specials/SpecialRandomredirect.php5
-rw-r--r--includes/specials/SpecialRecentchanges.php112
-rw-r--r--includes/specials/SpecialRecentchangeslinked.php72
-rw-r--r--includes/specials/SpecialRemoveRestrictions.php10
-rw-r--r--includes/specials/SpecialResetpass.php76
-rw-r--r--includes/specials/SpecialRestrictUser.php190
-rw-r--r--includes/specials/SpecialRevisiondelete.php2801
-rw-r--r--includes/specials/SpecialSearch.php1417
-rw-r--r--includes/specials/SpecialShortpages.php11
-rw-r--r--includes/specials/SpecialSpecialpages.php6
-rw-r--r--includes/specials/SpecialStatistics.php70
-rw-r--r--includes/specials/SpecialTags.php12
-rw-r--r--includes/specials/SpecialUncategorizedtemplates.php2
-rw-r--r--includes/specials/SpecialUndelete.php366
-rw-r--r--includes/specials/SpecialUnlockdb.php6
-rw-r--r--includes/specials/SpecialUnusedcategories.php2
-rw-r--r--includes/specials/SpecialUnusedimages.php16
-rw-r--r--includes/specials/SpecialUnusedtemplates.php13
-rw-r--r--includes/specials/SpecialUnwatchedpages.php12
-rw-r--r--includes/specials/SpecialUpload.php2374
-rw-r--r--includes/specials/SpecialUploadMogile.php135
-rw-r--r--includes/specials/SpecialUserlogin.php215
-rw-r--r--includes/specials/SpecialUserlogout.php10
-rw-r--r--includes/specials/SpecialUserrights.php390
-rw-r--r--includes/specials/SpecialVersion.php312
-rw-r--r--includes/specials/SpecialWantedcategories.php39
-rw-r--r--includes/specials/SpecialWantedfiles.php59
-rw-r--r--includes/specials/SpecialWantedpages.php93
-rw-r--r--includes/specials/SpecialWantedtemplates.php59
-rw-r--r--includes/specials/SpecialWatchlist.php120
-rw-r--r--includes/specials/SpecialWhatlinkshere.php84
-rw-r--r--includes/specials/SpecialWithoutinterwiki.php11
76 files changed, 6182 insertions, 7439 deletions
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php
new file mode 100644
index 00000000..7d907fb5
--- /dev/null
+++ b/includes/specials/SpecialActiveusers.php
@@ -0,0 +1,195 @@
+<?php
+# Copyright (C) 2008 Aaron Schulz
+#
+# http://www.mediawiki.org/
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# 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
+
+/**
+ * This class is used to get a list of active users. The ones with specials
+ * rights (sysop, bureaucrat, developer) will have them displayed
+ * next to their names.
+ *
+ * @file
+ * @ingroup SpecialPage
+ */
+class ActiveUsersPager extends UsersPager {
+
+ function __construct( $group = null ) {
+ global $wgRequest, $wgRCMaxAge;
+ $this->RCMaxAge = ceil( $wgRCMaxAge / ( 3600 * 24 ) ); // Constant
+
+ $un = $wgRequest->getText( 'username' );
+ $this->requestedUser = '';
+ if ( $un != '' ) {
+ $username = Title::makeTitleSafe( NS_USER, $un );
+ if( !is_null( $username ) ) {
+ $this->requestedUser = $username->getText();
+ }
+ }
+
+ $this->setupOptions();
+
+ parent::__construct();
+ }
+
+ public function setupOptions() {
+ global $wgRequest;
+
+ $this->opts = new FormOptions();
+
+ $this->opts->add( 'hidebots', false, FormOptions::BOOL );
+ $this->opts->add( 'hidesysops', false, FormOptions::BOOL );
+
+ $this->opts->fetchValuesFromRequest( $wgRequest );
+
+ $this->groups = array();
+ if ($this->opts->getValue('hidebots') == 1)
+ $this->groups['bot'] = true;
+ if ($this->opts->getValue('hidesysops') == 1)
+ $this->groups['sysop'] = true;
+ }
+
+ function getIndexField() {
+ return 'rc_user_text';
+ }
+
+ function getQueryInfo() {
+ $dbr = wfGetDB( DB_SLAVE );
+ $conds = array( 'rc_user > 0' ); // Users - no anons
+ $conds[] = 'ipb_deleted IS NULL'; // don't show hidden names
+ $conds[] = "rc_log_type IS NULL OR rc_log_type != 'newusers'";
+
+ if( $this->requestedUser != '' ) {
+ $conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser );
+ }
+
+ $query = array(
+ 'tables' => array( 'recentchanges', 'user', 'ipblocks' ),
+ 'fields' => array( 'rc_user_text AS user_name', // inheritance
+ 'rc_user_text', // for Pager
+ 'user_id',
+ 'COUNT(*) AS recentedits',
+ 'MAX(ipb_user) AS blocked'
+ ),
+ 'options' => array(
+ 'GROUP BY' => 'rc_user_text, user_id',
+ 'USE INDEX' => array( 'recentchanges' => 'rc_user_text' )
+ ),
+ 'join_conds' => array(
+ 'user' => array( 'INNER JOIN', 'rc_user_text=user_name' ),
+ 'ipblocks' => array( 'LEFT JOIN', 'user_id=ipb_user AND ipb_auto=0 AND ipb_deleted=1' ),
+ ),
+ 'conds' => $conds
+ );
+ return $query;
+ }
+
+ function formatRow( $row ) {
+ global $wgLang;
+ $userName = $row->user_name;
+
+ $ulinks = $this->getSkin()->userLink( $row->user_id, $userName );
+ $ulinks .= $this->getSkin()->userToolLinks( $row->user_id, $userName );
+
+ $list = array();
+ foreach( self::getGroups( $row->user_id ) as $group ) {
+ if (isset($this->groups[$group]))
+ return;
+ $list[] = self::buildGroupLink( $group );
+ }
+ $groups = $wgLang->commaList( $list );
+
+ $item = wfSpecialList( $ulinks, $groups );
+ $count = wfMsgExt( 'activeusers-count',
+ array( 'parsemag' ),
+ $wgLang->formatNum( $row->recentedits ),
+ $userName,
+ $wgLang->formatNum ( $this->RCMaxAge )
+ );
+ $blocked = $row->blocked ? ' ' . wfMsgExt( 'listusers-blocked', array( 'parsemag' ), $userName ) : '';
+
+ return Html::rawElement( 'li', array(), "{$item} [{$count}]{$blocked}" );
+ }
+
+ function getPageHeader() {
+ global $wgScript, $wgRequest;
+
+ $self = $this->getTitle();
+ $limit = $this->mLimit ? Xml::hidden( 'limit', $this->mLimit ) : '';
+
+ $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag
+ $out .= Xml::fieldset( wfMsg( 'activeusers' ) ) . "\n";
+ $out .= Xml::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n";
+
+ $out .= Xml::inputLabel( wfMsg( 'activeusers-from' ), 'username', 'offset', 20, $this->requestedUser ) . '<br />';# Username field
+
+ $out .= Xml::checkLabel( wfMsg('activeusers-hidebots'), 'hidebots', 'hidebots', $this->opts->getValue( 'hidebots' ) );
+
+ $out .= Xml::checkLabel( wfMsg('activeusers-hidesysops'), 'hidesysops', 'hidesysops', $this->opts->getValue( 'hidesysops' ) ) . '<br />';
+
+ $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n";# Submit button and form bottom
+ $out .= Xml::closeElement( 'fieldset' );
+ $out .= Xml::closeElement( 'form' );
+
+ return $out;
+ }
+}
+
+/**
+ * @ingroup SpecialPage
+ */
+class SpecialActiveUsers extends SpecialPage {
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct( 'Activeusers' );
+ }
+
+ /**
+ * Show the special page
+ *
+ * @param $par Mixed: parameter passed to the page or null
+ */
+ public function execute( $par ) {
+ global $wgOut, $wgLang, $wgRCMaxAge;
+
+ $this->setHeaders();
+
+ $up = new ActiveUsersPager();
+
+ # getBody() first to check, if empty
+ $usersbody = $up->getBody();
+
+ $s = Html::rawElement( 'div', array( 'class' => 'mw-activeusers-intro' ),
+ wfMsgExt( 'activeusers-intro', array( 'parsemag', 'escape' ), $wgLang->formatNum( ceil( $wgRCMaxAge / 86400 ) ) )
+ );
+
+ $s .= $up->getPageHeader();
+ if( $usersbody ) {
+ $s .= $up->getNavigationBar();
+ $s .= Html::rawElement( 'ul', array(), $usersbody );
+ $s .= $up->getNavigationBar();
+ } else {
+ $s .= Html::element( 'p', array(), wfMsg( 'activeusers-noresult' ) );
+ }
+
+ $wgOut->addHTML( $s );
+ }
+
+}
diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php
index 38181c08..1745bf6c 100644
--- a/includes/specials/SpecialAllmessages.php
+++ b/includes/specials/SpecialAllmessages.php
@@ -4,233 +4,414 @@
* @file
* @ingroup SpecialPage
*/
+class SpecialAllmessages extends SpecialPage {
-/**
- * Constructor.
- */
-function wfSpecialAllmessages() {
- global $wgOut, $wgRequest, $wgMessageCache, $wgTitle;
- global $wgUseDatabaseMessages, $wgLang;
-
- # The page isn't much use if the MediaWiki namespace is not being used
- if( !$wgUseDatabaseMessages ) {
- $wgOut->addWikiMsg( 'allmessagesnotsupportedDB' );
- return;
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct( 'Allmessages' );
}
- wfProfileIn( __METHOD__ );
+ /**
+ * Show the special page
+ *
+ * @param $par Mixed: parameter passed to the page or null
+ */
+ public function execute( $par ) {
+ global $wgOut, $wgRequest;
- wfProfileIn( __METHOD__ . '-setup' );
- $ot = $wgRequest->getText( 'ot' );
+ $this->setHeaders();
- $navText = wfMsg( 'allmessagestext' );
+ global $wgUseDatabaseMessages;
+ if( !$wgUseDatabaseMessages ) {
+ $wgOut->addWikiMsg( 'allmessagesnotsupportedDB' );
+ return;
+ } else {
+ $this->outputHeader( 'allmessagestext' );
+ }
- # Make sure all extension messages are available
+ $this->filter = $wgRequest->getVal( 'filter', 'all' );
+ $this->prefix = $wgRequest->getVal( 'prefix', '' );
- $wgMessageCache->loadAllMessages();
+ $this->table = new AllmessagesTablePager(
+ $this,
+ $conds = array(),
+ wfGetLangObj( $wgRequest->getVal( 'lang', $par ) )
+ );
- $sortedArray = array_merge( Language::getMessagesFor( 'en' ),
- $wgMessageCache->getExtensionMessagesFor( 'en' ) );
- ksort( $sortedArray );
+ $this->langCode = $this->table->lang->getCode();
+
+ $wgOut->addHTML( $this->buildForm() .
+ $this->table->getNavigationBar() .
+ $this->table->getLimitForm() .
+ $this->table->getBody() .
+ $this->table->getNavigationBar() );
- $messages = array();
- foreach( $sortedArray as $key => $value ) {
- $messages[$key]['enmsg'] = $value;
- $messages[$key]['statmsg'] = wfMsgReal( $key, array(), false, false, false );
- $messages[$key]['msg'] = wfMsgNoTrans( $key );
- $sortedArray[$key] = NULL; // trade bytes from $sortedArray to this
-
}
- unset($sortedArray); // trade bytes from $sortedArray to this
-
- wfProfileOut( __METHOD__ . '-setup' );
-
- wfProfileIn( __METHOD__ . '-output' );
- $wgOut->addScriptFile( 'allmessages.js' );
- if ( $ot == 'php' ) {
- $navText .= wfAllMessagesMakePhp( $messages );
- $wgOut->addHTML( $wgLang->pipeList( array(
- 'PHP',
- '<a href="' . $wgTitle->escapeLocalUrl( 'ot=html' ) . '">HTML</a>',
- '<a href="' . $wgTitle->escapeLocalUrl( 'ot=xml' ) . '">XML</a>' .
- '<pre>' . htmlspecialchars( $navText ) . '</pre>'
- ) ) );
- } else if ( $ot == 'xml' ) {
- $wgOut->disable();
- header( 'Content-type: text/xml' );
- echo wfAllMessagesMakeXml( $messages );
- } else {
- $wgOut->addHTML( $wgLang->pipeList( array(
- '<a href="' . $wgTitle->escapeLocalUrl( 'ot=php' ) . '">PHP</a>',
- 'HTML',
- '<a href="' . $wgTitle->escapeLocalUrl( 'ot=xml' ) . '">XML</a>'
- ) ) );
- $wgOut->addWikiText( $navText );
- $wgOut->addHTML( wfAllMessagesMakeHTMLText( $messages ) );
- }
- wfProfileOut( __METHOD__ . '-output' );
-
- wfProfileOut( __METHOD__ );
-}
-function wfAllMessagesMakeXml( &$messages ) {
- global $wgLang;
- $lang = $wgLang->getCode();
- $txt = "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n";
- $txt .= "<messages lang=\"$lang\">\n";
- foreach( $messages as $key => $m ) {
- $txt .= "\t" . Xml::element( 'message', array( 'name' => $key ), $m['msg'] ) . "\n";
- $messages[$key] = NULL; // trade bytes
- }
- $txt .= "</messages>";
- return $txt;
-}
+ function buildForm() {
+ global $wgScript;
-/**
- * Create the messages array, formatted in PHP to copy to language files.
- * @param $messages Messages array.
- * @return The PHP messages array.
- * @todo Make suitable for language files.
- */
-function wfAllMessagesMakePhp( &$messages ) {
- global $wgLang;
- $txt = "\n\n\$messages = array(\n";
- foreach( $messages as $key => $m ) {
- if( $wgLang->getCode() != 'en' && $m['msg'] == $m['enmsg'] ) {
- continue;
- } else if ( wfEmptyMsg( $key, $m['msg'] ) ) {
- $m['msg'] = '';
- $comment = ' #empty';
- } else {
- $comment = '';
+ $languages = Language::getLanguageNames( false );
+ ksort( $languages );
+
+ $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) .
+ Xml::fieldset( wfMsg( 'allmessages-filter-legend' ) ) .
+ Xml::hidden( 'title', $this->getTitle() ) .
+ Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" .
+ '<tr>
+ <td class="mw-label">' .
+ 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' ) ) .
+ "</td>\n
+ </tr>
+ <tr>\n
+ <td class='mw-label'>" .
+ wfMsg( 'allmessages-filter' ) .
+ "</td>\n
+ <td class='mw-input'>" .
+ Xml::radioLabel( wfMsg( 'allmessages-filter-unmodified' ),
+ 'filter',
+ 'unmodified',
+ 'mw-allmessages-form-filter-unmodified',
+ ( $this->filter == 'unmodified' ? true : false )
+ ) .
+ Xml::radioLabel( wfMsg( 'allmessages-filter-all' ),
+ 'filter',
+ 'all',
+ 'mw-allmessages-form-filter-all',
+ ( $this->filter == 'all' ? true : false )
+ ) .
+ Xml::radioLabel( wfMsg( 'allmessages-filter-modified' ),
+ 'filter',
+ 'modified',
+ 'mw-allmessages-form-filter-modified',
+ ( $this->filter == 'modified' ? true : false )
+ ) .
+ "</td>\n
+ </tr>
+ <tr>\n
+ <td class=\"mw-label\">" .
+ Xml::label( wfMsg( 'allmessages-language' ), 'mw-allmessages-form-lang' ) .
+ "</td>\n
+ <td class=\"mw-input\">" .
+ Xml::openElement( 'select', array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' ) );
+
+ foreach( $languages as $lang => $name ) {
+ $selected = $lang == $this->langCode ? true : false;
+ $out .= Xml::option( $lang . ' - ' . $name, $lang, $selected ) . "\n";
}
- $txt .= "'$key' => '" . preg_replace( '/(?<!\\\\)\'/', "\'", $m['msg']) . "',$comment\n";
- $messages[$key] = NULL; // trade bytes
+ $out .= Xml::closeElement( 'select' ) .
+ "</td>\n
+ </tr>
+ <tr>\n
+ <td></td>
+ <td>" .
+ Xml::submitButton( wfMsg( 'allmessages-filter-submit' ) ) .
+ "</td>\n
+ </tr>" .
+ Xml::closeElement( 'table' ) .
+ $this->table->getHiddenFields( array( 'title', 'prefix', 'filter', 'lang' ) ) .
+ Xml::closeElement( 'fieldset' ) .
+ Xml::closeElement( 'form' );
+ return $out;
}
- $txt .= ');';
- return $txt;
}
-/**
- * Create a list of messages, formatted in HTML as a list of messages and values and showing differences between the default language file message and the message in MediaWiki: namespace.
- * @param $messages Messages array.
- * @return The HTML list of messages.
+/* 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.
*/
-function wfAllMessagesMakeHTMLText( &$messages ) {
- global $wgLang, $wgContLang, $wgUser;
- wfProfileIn( __METHOD__ );
-
- $sk = $wgUser->getSkin();
- $talk = wfMsg( 'talkpagelinktext' );
-
- $input = Xml::element( 'input', array(
- 'type' => 'text',
- 'id' => 'allmessagesinput',
- 'onkeyup' => 'allmessagesfilter()'
- ), '' );
- $checkbox = Xml::element( 'input', array(
- 'type' => 'button',
- 'value' => wfMsgHtml( 'allmessagesmodified' ),
- 'id' => 'allmessagescheckbox',
- 'onclick' => 'allmessagesmodified()'
- ), '' );
-
- $txt = '<span id="allmessagesfilter" style="display: none;">' . wfMsgHtml( 'allmessagesfilter' ) .
- " {$input}{$checkbox} " . '</span>';
-
- $txt .= '
-<table border="1" cellspacing="0" width="100%" id="allmessagestable">
- <tr>
- <th rowspan="2">' . wfMsgHtml( 'allmessagesname' ) . '</th>
- <th>' . wfMsgHtml( 'allmessagesdefault' ) . '</th>
- </tr>
- <tr>
- <th>' . wfMsgHtml( 'allmessagescurrent' ) . '</th>
- </tr>';
-
- wfProfileIn( __METHOD__ . "-check" );
-
- # This is a nasty hack to avoid doing independent existence checks
- # without sending the links and table through the slow wiki parser.
- $pageExists = array(
- NS_MEDIAWIKI => array(),
- NS_MEDIAWIKI_TALK => array()
- );
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( 'page',
- array( 'page_namespace', 'page_title' ),
- array( 'page_namespace' => array(NS_MEDIAWIKI,NS_MEDIAWIKI_TALK) ),
- __METHOD__,
- array( 'USE INDEX' => 'name_title' )
- );
- while( $s = $dbr->fetchObject( $res ) ) {
- $pageExists[$s->page_namespace][$s->page_title] = 1;
- }
- $dbr->freeResult( $res );
- wfProfileOut( __METHOD__ . "-check" );
-
- wfProfileIn( __METHOD__ . "-output" );
-
- $i = 0;
-
- foreach( $messages as $key => $m ) {
- $title = $wgLang->ucfirst( $key );
- if( $wgLang->getCode() != $wgContLang->getCode() ) {
- $title .= '/' . $wgLang->getCode();
- }
+class AllmessagesTablePager extends TablePager {
- $titleObj = Title::makeTitle( NS_MEDIAWIKI, $title );
- $talkPage = Title::makeTitle( NS_MEDIAWIKI_TALK, $title );
+ public $mLimitsShown;
- $changed = ( $m['statmsg'] != $m['msg'] );
- $message = htmlspecialchars( $m['statmsg'] );
- $mw = htmlspecialchars( $m['msg'] );
+ function __construct( $page, $conds, $langObj = null ) {
+ parent::__construct();
+ $this->mIndexField = 'am_title';
+ $this->mPage = $page;
+ $this->mConds = $conds;
+ $this->mDefaultDirection = true; // always sort ascending
+ // We want to have an option for people to view *all* the messages,
+ // so they can use Ctrl+F to search them. 5000 is the maximum that
+ // will get through WebRequest::getLimitOffset().
+ $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 => wfMsg('limitall') );
- if( array_key_exists( $title, $pageExists[NS_MEDIAWIKI] ) ) {
- $pageLink = $sk->makeKnownLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" .
- htmlspecialchars( $key ) . '</span>' );
+ 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 {
- $pageLink = $sk->makeBrokenLinkObj( $titleObj, "<span id=\"sp-allmessages-i-$i\">" .
- htmlspecialchars( $key ) . '</span>' );
+ $this->custom = ($wgRequest->getVal( 'filter' ) == 'unmodified');
}
- if( array_key_exists( $title, $pageExists[NS_MEDIAWIKI_TALK] ) ) {
- $talkLink = $sk->makeKnownLinkObj( $talkPage, htmlspecialchars( $talk ) );
+
+ $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 {
- $talkLink = $sk->makeBrokenLinkObj( $talkPage, htmlspecialchars( $talk ) );
+ $this->prefix = false;
}
+ $this->getSkin();
- $anchor = 'msg_' . htmlspecialchars( strtolower( $title ) );
- $anchor = "<a id=\"$anchor\" name=\"$anchor\"></a>";
-
- if( $changed ) {
- $txt .= "
- <tr class=\"orig\" id=\"sp-allmessages-r1-$i\">
- <td rowspan=\"2\">
- $anchor$pageLink<br />$talkLink
- </td><td>
- $message
- </td>
- </tr><tr class=\"new\" id=\"sp-allmessages-r2-$i\">
- <td>
- $mw
- </td>
- </tr>";
+ // 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 {
- $txt .= "
- <tr class=\"def\" id=\"sp-allmessages-r1-$i\">
- <td>
- $anchor$pageLink<br />$talkLink
- </td><td>
- $mw
- </td>
- </tr>";
+ $this->suffix = '';
}
- $messages[$key] = NULL; // trade bytes
- $i++;
}
- $txt .= '</table>';
- wfProfileOut( __METHOD__ . '-output' );
- wfProfileOut( __METHOD__ );
- return $txt;
+ function getAllMessages( $descending ) {
+ wfProfileIn( __METHOD__ );
+ $messageNames = Language::getLocalisationCache()->getSubitemList( 'en', 'messages' );
+ if( $descending ){
+ rsort( $messageNames );
+ } else {
+ asort( $messageNames );
+ }
+
+ // Normalise message names so they look like page titles
+ $messageNames = array_map( array( $this->lang, 'ucfirst' ), $messageNames );
+ wfProfileIn( __METHOD__ );
+
+ return $messageNames;
+ }
+
+ /**
+ * 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.
+ */
+ function getCustomisedStatuses( $messageNames ) {
+ wfProfileIn( __METHOD__ . '-db' );
+
+ $dbr = wfGetDB( DB_SLAVE );
+ $res = $dbr->select( 'page',
+ array( 'page_namespace', 'page_title' ),
+ array( 'page_namespace' => array( NS_MEDIAWIKI, NS_MEDIAWIKI_TALK ) ),
+ __METHOD__,
+ array( 'USE INDEX' => 'name_title' )
+ );
+ $xNames = array_flip( $messageNames );
+
+ $pageFlags = $talkFlags = array();
+
+ while( $s = $dbr->fetchObject( $res ) ) {
+ if( $s->page_namespace == NS_MEDIAWIKI ) {
+ if( $this->foreign ) {
+ $title = explode( '/', $s->page_title );
+ if( count( $title ) === 2 && $this->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 ){
+ $talkFlags[$s->page_title] = true;
+ }
+ }
+ $dbr->freeResult( $res );
+
+ wfProfileOut( __METHOD__ . '-db' );
+
+ return array( 'pages' => $pageFlags, 'talks' => $talkFlags );
+ }
+
+ /* 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 );
+
+ $count = 0;
+ foreach( $messageNames as $key ) {
+ $customised = isset( $statuses['pages'][$key] );
+ if( $customised !== $this->custom &&
+ ( $descending && ( $key < $offset || !$offset ) || !$descending && $key > $offset ) &&
+ ( ( $this->prefix && preg_match( $this->prefix, $key ) ) || $this->prefix === false )
+ ){
+ $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_customised' => $customised,
+ 'am_talk_exists' => isset( $statuses['talks'][$key] )
+ );
+ $count++;
+ }
+ if( $count == $limit ) break;
+ }
+ return $result;
+ }
+
+ function getStartBody() {
+ return Xml::openElement( 'table', array( 'class' => 'TablePager', 'id' => 'mw-allmessagestable' ) ) . "\n" .
+ "<thead><tr>
+ <th rowspan=\"2\">" .
+ wfMsg( 'allmessagesname' ) . "
+ </th>
+ <th>" .
+ wfMsg( 'allmessagesdefault' ) .
+ "</th>
+ </tr>\n
+ <tr>
+ <th>" .
+ wfMsg( 'allmessagescurrent' ) .
+ "</th>
+ </tr></thead><tbody>\n";
+ }
+
+ function formatValue( $field, $value ){
+ global $wgLang;
+ switch( $field ){
+
+ case 'am_title' :
+
+ $title = Title::makeTitle( NS_MEDIAWIKI, $value . $this->suffix );
+ $talk = Title::makeTitle( NS_MEDIAWIKI_TALK, $value . $this->suffix );
+
+ if( $this->mCurrentRow->am_customised ){
+ $title = $this->mSkin->linkKnown( $title, $wgLang->lcfirst( $value ) );
+ } else {
+ $title = $this->mSkin->link(
+ $title,
+ $wgLang->lcfirst( $value ),
+ array(),
+ array(),
+ array( 'broken' )
+ );
+ }
+ if ( $this->mCurrentRow->am_talk_exists ) {
+ $talk = $this->mSkin->linkKnown( $talk , $this->talk );
+ } else {
+ $talk = $this->mSkin->link(
+ $talk,
+ $this->talk,
+ array(),
+ array(),
+ array( 'broken' )
+ );
+ }
+ return $title . ' (' . $talk . ')';
+
+ case 'am_default' :
+ return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES );
+ case 'am_actual' :
+ return Sanitizer::escapeHtmlAllowEntities( $value, ENT_QUOTES );
+ }
+ return '';
+ }
+
+ function formatRow( $row ){
+ // Do all the normal stuff
+ $s = parent::formatRow( $row );
+
+ // But if there's a customised message, add that too.
+ if( $row->am_customised ){
+ $s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) );
+ $formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) );
+ if ( $formatted == '' ) {
+ $formatted = '&nbsp;';
+ }
+ $s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted )
+ . "</tr>\n";
+ }
+ return $s;
+ }
+
+ function getRowAttrs( $row, $isSecond = false ){
+ $arr = array();
+ global $wgLang;
+ if( $row->am_customised ){
+ $arr['class'] = 'allmessages-customised';
+ }
+ if( !$isSecond ){
+ $arr['id'] = Sanitizer::escapeId( 'msg_' . $wgLang->lcfirst( $row->am_title ) );
+ }
+ return $arr;
+ }
+
+ function getCellAttrs( $field, $value ){
+ if( $this->mCurrentRow->am_customised && $field == 'am_title' ){
+ return array( 'rowspan' => '2', 'class' => $field );
+ } else {
+ return array( 'class' => $field );
+ }
+ }
+
+ // This is not actually used, as getStartBody is overridden above
+ function getFieldNames() {
+ return array(
+ 'am_title' => wfMsg( 'allmessagesname' ),
+ 'am_default' => wfMsg( 'allmessagesdefault' )
+ );
+ }
+ function getTitle() {
+ return SpecialPage::getTitleFor( 'Allmessages', false );
+ }
+ function isFieldSortable( $x ){
+ return false;
+ }
+ function getDefaultSort(){
+ return '';
+ }
+ function getQueryInfo(){
+ return '';
+ }
+}
+/* Overloads the relevant methods of the real ResultsWrapper so it
+ * doesn't go anywhere near an actual database.
+ */
+class FakeResultWrapper extends ResultWrapper {
+
+ var $result = array();
+ var $db = null; // And it's going to stay that way :D
+ var $pos = 0;
+ var $currentRow = null;
+
+ function __construct( $array ){
+ $this->result = $array;
+ }
+
+ function numRows() {
+ return count( $this->result );
+ }
+
+ function fetchRow() {
+ $this->currentRow = $this->result[$this->pos++];
+ return $this->currentRow;
+ }
+
+ function seek( $row ) {
+ $this->pos = $row;
+ }
+
+ function free() {}
+
+ // Callers want to be able to access fields with $this->fieldName
+ function fetchObject(){
+ $this->currentRow = $this->result[$this->pos++];
+ return (object)$this->currentRow;
+ }
+
+ function rewind() {
+ $this->pos = 0;
+ $this->currentRow = null;
+ }
}
diff --git a/includes/specials/SpecialAllpages.php b/includes/specials/SpecialAllpages.php
index bded8835..a36cdca7 100644
--- a/includes/specials/SpecialAllpages.php
+++ b/includes/specials/SpecialAllpages.php
@@ -14,7 +14,7 @@ class SpecialAllpages extends IncludableSpecialPage {
/**
* Maximum number of pages to show on single index subpage.
*/
- protected $maxLineCount = 200;
+ protected $maxLineCount = 100;
/**
* Maximum number of chars to show for an entry.
@@ -48,7 +48,8 @@ class SpecialAllpages extends IncludableSpecialPage {
$namespaces = $wgContLang->getNamespaces();
- $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ?
+ $wgOut->setPagetitle(
+ ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ?
wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
wfMsg( 'allarticles' )
);
@@ -69,53 +70,52 @@ class SpecialAllpages extends IncludableSpecialPage {
* @param string $to dbKey we are ending listing at.
*/
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 .= Xml::hidden( 'title', $t->getPrefixedText() );
- $out .= Xml::openElement( 'fieldset' );
- $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
- $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
- $out .= "<tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'allpagesto' ), 'nsto' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) .
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>" .
- Xml::label( wfMsg( 'namespace' ), 'namespace' ) .
- "</td>
- <td class='mw-input'>" .
- Xml::namespaceSelector( $namespace, null ) . ' ' .
- Xml::submitButton( wfMsg( 'allpagessubmit' ) ) .
- "</td>
- </tr>";
- $out .= Xml::closeElement( 'table' );
- $out .= Xml::closeElement( 'fieldset' );
- $out .= Xml::closeElement( 'form' );
- $out .= Xml::closeElement( 'div' );
- return $out;
+ global $wgScript;
+ $t = $this->getTitle();
+
+ $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
+ $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
+ $out .= Xml::hidden( 'title', $t->getPrefixedText() );
+ $out .= Xml::openElement( 'fieldset' );
+ $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
+ $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
+ $out .= "<tr>
+ <td class='mw-label'>" .
+ Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
+ " </td>
+</tr>
+<tr>
+ <td class='mw-label'>" .
+ Xml::label( wfMsg( 'allpagesto' ), 'nsto' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) .
+ " </td>
+</tr>
+<tr>
+ <td class='mw-label'>" .
+ Xml::label( wfMsg( 'namespace' ), 'namespace' ) .
+ " </td>
+ <td class='mw-input'>" .
+ Xml::namespaceSelector( $namespace, null ) . ' ' .
+ Xml::submitButton( wfMsg( 'allpagessubmit' ) ) .
+ " </td>
+</tr>";
+ $out .= Xml::closeElement( 'table' );
+ $out .= Xml::closeElement( 'fieldset' );
+ $out .= Xml::closeElement( 'form' );
+ $out .= Xml::closeElement( 'div' );
+ return $out;
}
/**
* @param integer $namespace (default NS_MAIN)
*/
function showToplevel( $namespace = NS_MAIN, $from = '', $to = '' ) {
- global $wgOut, $wgContLang;
- $align = $wgContLang->isRtl() ? 'left' : 'right';
+ global $wgOut;
# TODO: Either make this *much* faster or cache the title index points
# in the querycache table.
@@ -126,8 +126,8 @@ class SpecialAllpages extends IncludableSpecialPage {
$from = Title::makeTitleSafe( $namespace, $from );
$to = Title::makeTitleSafe( $namespace, $to );
- $from = ( $from && $from->isLocal() ) ? $from->getDBKey() : null;
- $to = ( $to && $to->isLocal() ) ? $to->getDBKey() : null;
+ $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
+ $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
if( isset($from) )
$where[] = 'page_title >= '.$dbr->addQuotes( $from );
@@ -190,7 +190,7 @@ class SpecialAllpages extends IncludableSpecialPage {
// Instead, display the first section directly.
if( count( $lines ) <= 2 ) {
if( !empty($lines) ) {
- $this->showChunk( $namespace, $lines[0], $lines[count($lines)-1] );
+ $this->showChunk( $namespace, $from, $to );
} else {
$wgOut->addHTML( $this->namespaceForm( $namespace, $from, $to ) );
}
@@ -198,13 +198,13 @@ class SpecialAllpages extends IncludableSpecialPage {
}
# At this point, $lines should contain an even number of elements.
- $out .= "<table class='allpageslist' style='background: inherit;'>";
+ $out .= Xml::openElement( 'table', array( 'class' => 'allpageslist' ) );
while( count ( $lines ) > 0 ) {
$inpoint = array_shift( $lines );
$outpoint = array_shift( $lines );
$out .= $this->showline( $inpoint, $outpoint, $namespace );
}
- $out .= '</table>';
+ $out .= Xml::closeElement( 'table' );
$nsForm = $this->namespaceForm( $namespace, $from, $to );
# Is there more?
@@ -213,11 +213,17 @@ class SpecialAllpages extends IncludableSpecialPage {
} else {
if( isset($from) || isset($to) ) {
global $wgUser;
- $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">';
- $out2 .= '<tr valign="top"><td>' . $nsForm;
- $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' .
- $wgUser->getSkin()->makeKnownLinkObj( $this->getTitle(), wfMsgHtml ( 'allpages' ) );
- $out2 .= "</td></tr></table>";
+ $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' ),
+ array(), array(), 'known' ) .
+ "</td>
+ </tr>" .
+ Xml::closeElement( 'table' );
} else {
$out2 = $nsForm;
}
@@ -233,7 +239,6 @@ class SpecialAllpages extends IncludableSpecialPage {
*/
function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) {
global $wgContLang;
- $align = $wgContLang->isRtl() ? 'left' : 'right';
$inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) );
$outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) );
// Don't let the length runaway
@@ -248,7 +253,7 @@ class SpecialAllpages extends IncludableSpecialPage {
"<a href=\"$link\">$inpointf</a></td><td>",
"</td><td><a href=\"$link\">$outpointf</a>"
);
- return '<tr><td align="' . $align . '">'.$out.'</td></tr>';
+ return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>';
}
/**
@@ -264,8 +269,6 @@ class SpecialAllpages extends IncludableSpecialPage {
$fromList = $this->getNamespaceKeyAndText($namespace, $from);
$toList = $this->getNamespaceKeyAndText( $namespace, $to );
$namespaces = $wgContLang->getNamespaces();
- $align = $wgContLang->isRtl() ? 'left' : 'right';
-
$n = 0;
if ( !$fromList || !$toList ) {
@@ -299,13 +302,12 @@ class SpecialAllpages extends IncludableSpecialPage {
);
if( $res->numRows() > 0 ) {
- $out = '<table style="background: inherit;" border="0" width="100%">';
-
+ $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 );
if( $t ) {
$link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
- $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) .
+ $sk->linkKnown( $t, htmlspecialchars( $t->getText() ) ) .
($s->page_is_redirect ? '</div>' : '' );
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
@@ -316,13 +318,13 @@ class SpecialAllpages extends IncludableSpecialPage {
$out .= "<td width=\"33%\">$link</td>";
$n++;
if( $n % 3 == 0 ) {
- $out .= '</tr>';
+ $out .= "</tr>\n";
}
}
if( ($n % 3) != 0 ) {
- $out .= '</tr>';
+ $out .= "</tr>\n";
}
- $out .= '</table>';
+ $out .= Xml::closeElement( 'table' );
} else {
$out = '';
}
@@ -342,7 +344,9 @@ class SpecialAllpages extends IncludableSpecialPage {
'page_title',
array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ),
__METHOD__,
- array( 'ORDER BY' => 'page_title DESC', 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 ) )
+ array( 'ORDER BY' => 'page_title DESC',
+ 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 )
+ )
);
# Get first title of previous complete chunk
@@ -370,28 +374,44 @@ class SpecialAllpages extends IncludableSpecialPage {
$self = $this->getTitle();
$nsForm = $this->namespaceForm( $namespace, $from, $to );
- $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">';
- $out2 .= '<tr valign="top"><td>' . $nsForm;
- $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' .
- $sk->makeKnownLinkObj( $self,
- wfMsgHtml ( 'allpages' ) );
+ $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ).
+ '<tr>
+ <td>' .
+ $nsForm .
+ '</td>
+ <td class="mw-allpages-nav">' .
+ $sk->link( $self, wfMsgHtml ( 'allpages' ), array(), array(), 'known' );
# Do we put a previous link ?
if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
- $q = 'from=' . $prevTitle->getPartialUrl()
- . ( $namespace ? '&namespace=' . $namespace : '' );
- $prevLink = $sk->makeKnownLinkObj( $self,
- wfMsgHTML( 'prevpage', htmlspecialchars( $pt ) ), $q );
+ $query = array( 'from' => $prevTitle->getText() );
+
+ if( $namespace )
+ $query['namespace'] = $namespace;
+
+ $prevLink = $sk->linkKnown(
+ $self,
+ htmlspecialchars( wfMsg( 'prevpage', $pt ) ),
+ array(),
+ $query
+ );
$out2 = $wgLang->pipeList( array( $out2, $prevLink ) );
}
if( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
# $s is the first link of the next chunk
$t = Title::MakeTitle($namespace, $s->page_title);
- $q = 'from=' . $t->getPartialUrl()
- . ( $namespace ? '&namespace=' . $namespace : '' );
- $nextLink = $sk->makeKnownLinkObj( $self,
- wfMsgHtml( 'nextpage', htmlspecialchars( $t->getText() ) ), $q );
+ $query = array( 'from' => $t->getText() );
+
+ if( $namespace )
+ $query['namespace'] = $namespace;
+
+ $nextLink = $sk->linkKnown(
+ $self,
+ htmlspecialchars( wfMsg( 'nextpage', $t->getText() ) ),
+ array(),
+ $query
+ );
$out2 = $wgLang->pipeList( array( $out2, $nextLink ) );
}
$out2 .= "</td></tr></table>";
@@ -399,7 +419,7 @@ class SpecialAllpages extends IncludableSpecialPage {
$wgOut->addHTML( $out2 . $out );
if( isset($prevLink) or isset($nextLink) ) {
- $wgOut->addHTML( '<hr /><p style="font-size: smaller; float: ' . $align . '">' );
+ $wgOut->addHTML( '<hr /><p class="mw-allpages-nav">' );
if( isset( $prevLink ) ) {
$wgOut->addHTML( $prevLink );
}
@@ -430,7 +450,7 @@ class SpecialAllpages extends IncludableSpecialPage {
if ( $t && $t->isLocal() ) {
return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
} else if ( $t ) {
- return NULL;
+ return null;
}
# try again, in case the problem was an empty pagename
@@ -439,7 +459,7 @@ class SpecialAllpages extends IncludableSpecialPage {
if ( $t && $t->isLocal() ) {
return array( $t->getNamespace(), '', '' );
} else {
- return NULL;
+ return null;
}
}
}
diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php
index 188ad914..92192435 100644
--- a/includes/specials/SpecialAncientpages.php
+++ b/includes/specials/SpecialAncientpages.php
@@ -25,8 +25,25 @@ class AncientPagesPage extends QueryPage {
$db = wfGetDB( DB_SLAVE );
$page = $db->tableName( 'page' );
$revision = $db->tableName( 'revision' );
- $epoch = $wgDBtype == 'mysql' ? 'UNIX_TIMESTAMP(rev_timestamp)' :
- 'EXTRACT(epoch FROM rev_timestamp)';
+
+ switch ($wgDBtype) {
+ case 'mysql':
+ $epoch = 'UNIX_TIMESTAMP(rev_timestamp)';
+ break;
+ case 'ibm_db2':
+ // TODO implement proper conversion to a Unix epoch
+ $epoch = 'rev_timestamp';
+ break;
+ case 'oracle':
+ $epoch = '((trunc(rev_timestamp) - to_date(\'19700101\',\'YYYYMMDD\')) * 86400)';
+ break;
+ case 'sqlite':
+ $epoch = 'rev_timestamp';
+ break;
+ default:
+ $epoch = 'EXTRACT(epoch FROM rev_timestamp)';
+ }
+
return
"SELECT 'Ancientpages' as type,
page_namespace as namespace,
@@ -46,8 +63,11 @@ class AncientPagesPage extends QueryPage {
$d = $wgLang->timeanddate( wfTimestamp( TS_MW, $result->value ), true );
$title = Title::makeTitle( $result->namespace, $result->title );
- $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) );
- return wfSpecialList($link, $d);
+ $link = $skin->linkKnown(
+ $title,
+ htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
+ );
+ return wfSpecialList($link, htmlspecialchars($d) );
}
}
diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php
index 29d6b96c..e1fadd02 100644
--- a/includes/specials/SpecialBlankpage.php
+++ b/includes/specials/SpecialBlankpage.php
@@ -1,6 +1,17 @@
<?php
-
-function wfSpecialBlankpage() {
- global $wgOut;
- $wgOut->addWikiMsg('intentionallyblankpage');
+/**
+ * Special page designed for basic benchmarking of
+ * MediaWiki since it doesn't really do much.
+ *
+ * @ingroup SpecialPage
+ */
+class SpecialBlankpage extends UnlistedSpecialPage {
+ public function __construct() {
+ parent::__construct( 'Blankpage' );
+ }
+ public function execute( $par ) {
+ global $wgOut;
+ $this->setHeaders();
+ $wgOut->addWikiMsg('intentionallyblankpage');
+ }
}
diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php
index f002e570..16720dd1 100644
--- a/includes/specials/SpecialBlockip.php
+++ b/includes/specials/SpecialBlockip.php
@@ -17,7 +17,6 @@ function wfSpecialBlockip( $par ) {
$wgOut->readOnlyPage();
return;
}
-
# Permission check
if( !$wgUser->isAllowed( 'block' ) ) {
$wgOut->permissionRequired( 'block' );
@@ -27,9 +26,9 @@ function wfSpecialBlockip( $par ) {
$ipb = new IPBlockForm( $par );
$action = $wgRequest->getVal( 'action' );
- if ( 'success' == $action ) {
+ if( 'success' == $action ) {
$ipb->showSuccess();
- } else if ( $wgRequest->wasPosted() && 'submit' == $action &&
+ } elseif( $wgRequest->wasPosted() && 'submit' == $action &&
$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
$ipb->doSubmit();
} else {
@@ -44,18 +43,17 @@ function wfSpecialBlockip( $par ) {
*/
class IPBlockForm {
var $BlockAddress, $BlockExpiry, $BlockReason;
-# var $BlockEmail;
// The maximum number of edits a user can have and still be hidden
const HIDEUSER_CONTRIBLIMIT = 1000;
- function IPBlockForm( $par ) {
+ public function __construct( $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->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
@@ -64,21 +62,30 @@ class IPBlockForm {
$this->BlockAnonOnly = $wgRequest->getBool( 'wpAnonOnly', $byDefault );
$this->BlockCreateAccount = $wgRequest->getBool( 'wpCreateAccount', $byDefault );
$this->BlockEnableAutoblock = $wgRequest->getBool( 'wpEnableAutoblock', $byDefault );
- $this->BlockEmail = $wgRequest->getBool( 'wpEmailBan', false );
- $this->BlockWatchUser = $wgRequest->getBool( 'wpWatchUser', false );
- # Re-check user's rights to hide names, very serious, defaults to 0
- $this->BlockHideName = ( $wgRequest->getBool( 'wpHideName', 0 ) && $wgUser->isAllowed( 'hideuser' ) ) ? 1 : 0;
+ $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();
}
- function showForm( $err ) {
+ public function showForm( $err ) {
global $wgOut, $wgUser, $wgSysopUserBans;
- $wgOut->setPagetitle( wfMsg( 'blockip' ) );
+ $wgOut->setPageTitle( wfMsg( 'blockip-title' ) );
$wgOut->addWikiMsg( 'blockiptext' );
- if($wgSysopUserBans) {
+ if( $wgSysopUserBans ) {
$mIpaddress = Xml::label( wfMsg( 'ipadressorusername' ), 'mw-bi-target' );
} else {
$mIpaddress = Xml::label( wfMsg( 'ipaddress' ), 'mw-bi-target' );
@@ -90,25 +97,28 @@ class IPBlockForm {
$titleObj = SpecialPage::getTitleFor( 'Blockip' );
$user = User::newFromName( $this->BlockAddress );
-
+
$alreadyBlocked = false;
- if ( $err && $err[0] != 'ipb_already_blocked' ) {
- $key = array_shift($err);
- $msg = wfMsgReal($key, $err);
+ $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 ) {
- $userId = 0;
- if ( is_object( $user ) )
- $userId = $user->getId();
+ } elseif( $this->BlockAddress ) {
+ # 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
+ 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 ) ) ) {
- $wgOut->addWikiMsg( 'ipb-needreblock', $this->BlockAddress );
- $alreadyBlocked = true;
- # Set the block form settings to the existing 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;
@@ -121,21 +131,38 @@ class IPBlockForm {
$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->addWikiMsg( 'ipb-needreblock', $this->BlockAddress );
}
$scBlockExpiryOptions = wfMsgForContent( 'ipboptions' );
$showblockoptions = $scBlockExpiryOptions != '-';
- if (!$showblockoptions)
- $mIpbother = $mIpbexpiry;
+ 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);
+ 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 ? true : false ) . "\n";
}
@@ -146,25 +173,27 @@ class IPBlockForm {
global $wgStylePath, $wgStyleVersion;
$wgOut->addHTML(
Xml::tags( 'script', array( 'type' => 'text/javascript', 'src' => "$wgStylePath/common/block.js?$wgStyleVersion" ), '' ) .
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( "action=submit" ), 'id' => 'blockip' ) ) .
+ 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' ) ) .
+ Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-blockip-table' ) ) .
"<tr>
<td class='mw-label'>
{$mIpaddress}
</td>
<td class='mw-input'>" .
- Xml::input( 'wpBlockAddress', 45, $this->BlockAddress,
- array(
- 'tabindex' => '1',
- 'id' => 'mw-bi-target',
- 'onchange' => 'updateBlockOptions()' ) ). "
+ 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 ) {
+ if( $showblockoptions ) {
$wgOut->addHTML("
<td class='mw-label'>
{$mIpbexpiry}
@@ -204,8 +233,12 @@ class IPBlockForm {
{$mIpbreason}
</td>
<td class='mw-input'>" .
- Xml::input( 'wpBlockReason', 45, $this->BlockReason,
- array( 'tabindex' => '5', 'id' => 'mw-bi-reason', 'maxlength'=> '200' ) ) . "
+ 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'>
@@ -234,36 +267,37 @@ class IPBlockForm {
</tr>"
);
- global $wgSysopEmailBans, $wgBlockAllowsUTEdit;
- if ( $wgSysopEmailBans && $wgUser->isAllowed( 'blockemail' ) ) {
+ if( self::canBlockEmail( $wgUser ) ) {
$wgOut->addHTML("
<tr id='wpEnableEmailBan'>
<td>&nbsp;</td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'ipbemailban' ),
'wpEmailBan', 'wpEmailBan', $this->BlockEmail,
- array( 'tabindex' => '9' )) . "
+ array( 'tabindex' => '9' ) ) . "
</td>
</tr>"
);
}
// Allow some users to hide name from block log, blocklist and listusers
- if ( $wgUser->isAllowed( 'hideuser' ) ) {
+ if( $wgUser->isAllowed( 'hideuser' ) ) {
$wgOut->addHTML("
<tr id='wpEnableHideUser'>
<td>&nbsp;</td>
<td class='mw-input'><strong>" .
Xml::checkLabel( wfMsg( 'ipbhidename' ),
'wpHideName', 'wpHideName', $this->BlockHideName,
- array( 'tabindex' => '10' ) ) . "
+ array( 'tabindex' => '10' )
+ ) . "
</strong></td>
</tr>"
);
}
-
- # Watchlist their user page?
- $wgOut->addHTML("
+
+ # Watchlist their user page? (Only if user is logged in)
+ if( $wgUser->isLoggedIn() ) {
+ $wgOut->addHTML("
<tr id='wpEnableWatchUser'>
<td>&nbsp;</td>
<td class='mw-input'>" .
@@ -272,7 +306,11 @@ class IPBlockForm {
array( 'tabindex' => '11' ) ) . "
</td>
</tr>"
- );
+ );
+ }
+
+ # Can we explicitly disallow the use of user_talk?
+ global $wgBlockAllowsUTEdit;
if( $wgBlockAllowsUTEdit ){
$wgOut->addHTML("
<tr id='wpAllowUsertalkRow'>
@@ -314,12 +352,22 @@ class IPBlockForm {
}
/**
+ * 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' ) );
+ }
+
+ /**
* 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;
+ global $wgUser, $wgSysopUserBans, $wgSysopRangeBans, $wgBlockAllowsUTEdit, $wgBlockCIDRLimit;
$userId = 0;
# Expand valid IPv6 addresses, usernames are left as is
@@ -330,24 +378,28 @@ class IPBlockForm {
$rxIP = "($rxIP4|$rxIP6)";
# Check for invalid specifications
- if ( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) {
+ if( !preg_match( "/^$rxIP$/", $this->BlockAddress ) ) {
$matches = array();
- if ( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) {
+ if( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) {
# IPv4
- if ( $wgSysopRangeBans ) {
- if ( !IP::isIPv4( $this->BlockAddress ) || $matches[2] < 16 || $matches[2] > 32 ) {
- return array('ip_range_invalid');
+ if( $wgSysopRangeBans ) {
+ 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');
+ return array( 'range_block_disabled' );
}
- } else if ( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) {
+ } elseif( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) {
# IPv6
- if ( $wgSysopRangeBans ) {
- if ( !IP::isIPv6( $this->BlockAddress ) || $matches[2] < 64 || $matches[2] > 128 ) {
- return array('ip_range_invalid');
+ if( $wgSysopRangeBans ) {
+ 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 {
@@ -356,30 +408,30 @@ class IPBlockForm {
}
} else {
# Username block
- if ( $wgSysopUserBans ) {
+ if( $wgSysopUserBans ) {
$user = User::newFromName( $this->BlockAddress );
if( !is_null( $user ) && $user->getId() ) {
# Use canonical name
$userId = $user->getId();
$this->BlockAddress = $user->getName();
} else {
- return array('nosuchusershort', htmlspecialchars( $user ? $user->getName() : $this->BlockAddress ) );
+ return array( 'nosuchusershort', htmlspecialchars( $user ? $user->getName() : $this->BlockAddress ) );
}
} else {
- return array('badipaddress');
+ return array( 'badipaddress' );
}
}
}
- if ( $wgUser->isBlocked() && ( $wgUser->getId() !== $userId ) ) {
+ if( $wgUser->isBlocked() && ( $wgUser->getId() !== $userId ) ) {
return array( 'cant-block-while-blocked' );
}
$reasonstr = $this->BlockReasonList;
- if ( $reasonstr != 'other' && $this->BlockReason != '' ) {
+ if( $reasonstr != 'other' && $this->BlockReason != '' ) {
// Entry from drop down menu + additional comment
$reasonstr .= wfMsgForContent( 'colon-separator' ) . $this->BlockReason;
- } elseif ( $reasonstr == 'other' ) {
+ } elseif( $reasonstr == 'other' ) {
$reasonstr = $this->BlockReason;
}
@@ -387,44 +439,45 @@ class IPBlockForm {
if( $expirestr == 'other' )
$expirestr = $this->BlockOther;
- if ( ( strlen( $expirestr ) == 0) || ( strlen( $expirestr ) > 50) ) {
- return array('ipb_expiry_invalid');
+ if( ( strlen( $expirestr ) == 0) || ( strlen( $expirestr ) > 50 ) ) {
+ return array( 'ipb_expiry_invalid' );
}
- if ( false === ($expiry = Block::parseExpiryInput( $expirestr )) ) {
+ if( false === ( $expiry = Block::parseExpiryInput( $expirestr ) ) ) {
// Bad expiry.
- return array('ipb_expiry_invalid');
+ return array( 'ipb_expiry_invalid' );
}
-
+
if( $this->BlockHideName ) {
- if( !$userId ) {
- // IP users should not be hidden
- $this->BlockHideName = false;
- } else if( $expiry !== 'infinity' ) {
+ // 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');
- } else if( User::edits($userId) > self::HIDEUSER_CONTRIBLIMIT ) {
+ 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');
+ return array( 'ipb_hide_invalid' );
}
}
- # Create block
+ # 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
+ $this->BlockEmail,
+ isset( $this->BlockAllowUsertalk ) ? $this->BlockAllowUsertalk : $wgBlockAllowsUTEdit
);
# Should this be privately logged?
$suppressLog = (bool)$this->BlockHideName;
- if ( wfRunHooks('BlockIp', array(&$block, &$wgUser)) ) {
+ if( wfRunHooks( 'BlockIp', array( &$block, &$wgUser ) ) ) {
# Try to insert block. Is there a conflicting block?
- if ( !$block->insert() ) {
+ if( !$block->insert() ) {
# Show form unless the user is already aware of this...
- if ( !$this->BlockReblock ) {
+ if( !$this->BlockReblock ) {
return array( 'ipb_already_blocked' );
# Otherwise, try to update the block...
} else {
@@ -436,8 +489,8 @@ class IPBlockForm {
}
# 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( 'hookaborted' );
+ if( $currentBlock->mHideName && !$wgUser->isAllowed( 'hideuser' ) ) {
+ return array( 'cant-see-hidden-user' );
}
$currentBlock->delete();
$block->insert();
@@ -452,19 +505,18 @@ class IPBlockForm {
} else {
$log_action = 'block';
}
- wfRunHooks('BlockIpComplete', array($block, $wgUser));
+ wfRunHooks( 'BlockIpComplete', array( $block, $wgUser ) );
# Set *_deleted fields if requested
if( $this->BlockHideName ) {
self::suppressUserName( $this->BlockAddress, $userId );
}
- if ( $this->BlockWatchUser &&
- # Only show watch link when this is no range block
- $block->mRangeStart == $block->mRangeEnd) {
- $wgUser->addWatch ( Title::makeTitle( NS_USER, $this->BlockAddress ) );
+ # 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;
@@ -478,34 +530,34 @@ class IPBlockForm {
$log_type = $suppressLog ? 'suppress' : 'block';
$log = new LogPage( $log_type );
$log->addEntry( $log_action, Title::makeTitle( NS_USER, $this->BlockAddress ),
- $reasonstr, $logParams );
+ $reasonstr, $logParams );
# Report to the user
return array();
} else {
- return array('hookaborted');
+ return array( 'hookaborted' );
}
}
-
- public static function suppressUserName( $name, $userId ) {
+
+ public static function suppressUserName( $name, $userId, $dbw = null ) {
$op = '|'; // bitwise OR
- return self::setUsernameBitfields( $name, $userId, $op );
+ return self::setUsernameBitfields( $name, $userId, $op, $dbw );
}
-
- public static function unsuppressUserName( $name, $userId ) {
+
+ public static function unsuppressUserName( $name, $userId, $dbw = null ) {
$op = '&'; // bitwise AND
- return self::setUsernameBitfields( $name, $userId, $op );
+ return self::setUsernameBitfields( $name, $userId, $op, $dbw );
}
-
- private static function setUsernameBitfields( $name, $userId, $op ) {
- if( $op !== '|' && $op !== '&' )
- return false; // sanity check
- $dbw = wfGetDB( DB_MASTER );
+
+ 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();
+ $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
@@ -516,27 +568,29 @@ class IPBlockForm {
$delAction = "~{$delAction}";
}
# Hide name from live edits
- $dbw->update( 'revision', array("rev_deleted = rev_deleted $op $delUser"),
- array('rev_user' => $userId), __METHOD__ );
+ $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__ );
+ $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__ );
+ $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 $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__ );
+ $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__ );
+ # $dbw->update( 'filearchive', array( "fa_deleted = fa_deleted $op $delUser" ),
+ # array( 'fa_user_text' => $name ), __METHOD__ );
# Done!
return true;
}
@@ -545,11 +599,10 @@ class IPBlockForm {
* UI entry point for blocking
* Wraps around doBlock()
*/
- function doSubmit()
- {
+ public function doSubmit() {
global $wgOut;
$retval = $this->doBlock();
- if(empty($retval)) {
+ if( empty( $retval ) ) {
$titleObj = SpecialPage::getTitleFor( 'Blockip' );
$wgOut->redirect( $titleObj->getFullURL( 'action=success&ip=' .
urlencode( $this->BlockAddress ) ) );
@@ -558,27 +611,55 @@ class IPBlockForm {
$this->showForm( $retval );
}
- function showSuccess() {
+ public function showSuccess() {
global $wgOut;
- $wgOut->setPagetitle( wfMsg( 'blockip' ) );
+ $wgOut->setPageTitle( wfMsg( 'blockip-title' ) );
$wgOut->setSubtitle( wfMsg( 'blockipsuccesssub' ) );
$text = wfMsgExt( 'blockipsuccesstext', array( 'parse' ), $this->BlockAddress );
$wgOut->addHTML( $text );
}
- function showLogFragment( $out, $title ) {
+ private function showLogFragment( $out, $title ) {
global $wgUser;
- $out->addHTML( Xml::element( 'h2', NULL, LogPage::logName( 'block' ) ) );
- $count = LogEventsList::showLogExtract( $out, 'block', $title->getPrefixedText(), '', 10 );
- if($count > 10){
- $out->addHTML( $wgUser->getSkin()->link(
- SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'blocklog-fulllog' ),
- array(),
+
+ // 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( 'hideuser' ) ) {
+ LogEventsList::showLogExtract( $out, 'suppress', $title->getPrefixedText(), '',
array(
- 'type' => 'block',
- 'page' => $title->getPrefixedText() ) ) );
+ 'lim' => 10,
+ 'conds' => array(
+ 'log_action' => array(
+ 'block',
+ 'reblock',
+ 'unblock'
+ )
+ ),
+ 'msgKey' => array(
+ 'blocklog-showsuppresslog',
+ $userBlocked
+ ),
+ 'showIfEmpty' => false
+ )
+ );
}
}
@@ -596,13 +677,14 @@ class IPBlockForm {
$flags[] = 'anononly';
if( $this->BlockCreateAccount )
$flags[] = 'nocreate';
- if( !$this->BlockEnableAutoblock )
+ if( !$this->BlockEnableAutoblock && !IP::isIPAddress( $this->BlockAddress ) )
+ // Same as anononly, this is not displayed when blocking an IP address
$flags[] = 'noautoblock';
- if ( $this->BlockEmail )
+ if( $this->BlockEmail )
$flags[] = 'noemail';
- if ( !$this->BlockAllowUsertalk && $wgBlockAllowsUTEdit )
+ if( !$this->BlockAllowUsertalk && $wgBlockAllowsUTEdit )
$flags[] = 'nousertalk';
- if ( $this->BlockHideName )
+ if( $this->BlockHideName )
$flags[] = 'hiddenname';
return implode( ',', $flags );
}
@@ -619,10 +701,18 @@ class IPBlockForm {
$links[] = $this->getContribsLink( $skin );
$links[] = $this->getUnblockLink( $skin );
$links[] = $this->getBlockListLink( $skin );
- $links[] = $skin->makeLink ( 'MediaWiki:Ipbreason-dropdown', wfMsgHtml( 'ipb-edit-dropdown' ) );
+ 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
@@ -645,13 +735,21 @@ class IPBlockForm {
*/
private function getUnblockLink( $skin ) {
$list = SpecialPage::getTitleFor( 'Ipblocklist' );
+ $query = array( 'action' => 'unblock' );
+
if( $this->BlockAddress ) {
- $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) );
- return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock-addr', $addr ),
- 'action=unblock&ip=' . urlencode( $this->BlockAddress ) );
+ $addr = strtr( $this->BlockAddress, '_', ' ' );
+ $message = wfMsg( 'ipb-unblock-addr', $addr );
+ $query['ip'] = $this->BlockAddress;
} else {
- return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-unblock' ), 'action=unblock' );
+ $message = wfMsg( 'ipb-unblock' );
}
+ return $skin->linkKnown(
+ $list,
+ htmlspecialchars( $message ),
+ array(),
+ $query
+ );
}
/**
@@ -662,23 +760,32 @@ class IPBlockForm {
*/
private function getBlockListLink( $skin ) {
$list = SpecialPage::getTitleFor( 'Ipblocklist' );
+ $query = array();
+
if( $this->BlockAddress ) {
- $addr = htmlspecialchars( strtr( $this->BlockAddress, '_', ' ' ) );
- return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist-addr', $addr ),
- 'ip=' . urlencode( $this->BlockAddress ) );
+ $addr = strtr( $this->BlockAddress, '_', ' ' );
+ $message = wfMsg( 'ipb-blocklist-addr', $addr );
+ $query['ip'] = $this->BlockAddress;
} else {
- return $skin->makeKnownLinkObj( $list, wfMsgHtml( 'ipb-blocklist' ) );
+ $message = wfMsg( 'ipb-blocklist' );
}
+
+ return $skin->linkKnown(
+ $list,
+ htmlspecialchars( $message ),
+ array(),
+ $query
+ );
}
-
+
/**
- * Block a list of selected users
- * @param array $users
- * @param string $reason
- * @param string $tag replaces user pages
- * @param string $talkTag replaces user talk pages
- * @returns array, list of html-safe usernames
- */
+ * Block a list of selected users
+ * @param array $users
+ * @param string $reason
+ * @param string $tag replaces user pages
+ * @param string $talkTag replaces user talk pages
+ * @returns array, list of html-safe usernames
+ */
public static function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) {
global $wgUser;
$counter = $blockSize = 0;
@@ -695,7 +802,7 @@ class IPBlockForm {
}
$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() )) ) {
+ if( is_null( $u ) || ( !$u->getId() && !IP::isIPAddress( $u->getName() ) ) ) {
continue;
}
$userTitle = $u->getUserPage();
@@ -734,10 +841,10 @@ class IPBlockForm {
$log->addEntry( 'block', $userTitle, $reason, $logParams );
}
# Tag userpage! (check length to avoid mistakes)
- if( strlen($tag) > 2 ) {
+ if( strlen( $tag ) > 2 ) {
$userpage->doEdit( $tag, $reason, EDIT_MINOR );
}
- if( strlen($talkTag) > 2 ) {
+ if( strlen( $talkTag ) > 2 ) {
$usertalk->doEdit( $talkTag, $reason, EDIT_MINOR );
}
}
diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php
index db466c14..8ee5467a 100644
--- a/includes/specials/SpecialBooksources.php
+++ b/includes/specials/SpecialBooksources.php
@@ -6,7 +6,7 @@
*
* @author Rob Church <robchur@gmail.com>
* @todo Validate ISBNs using the standard check-digit method
- * @ingroup SpecialPages
+ * @ingroup SpecialPage
*/
class SpecialBookSources extends SpecialPage {
@@ -35,7 +35,7 @@ class SpecialBookSources extends SpecialPage {
$wgOut->addHTML( $this->makeForm() );
if( strlen( $this->isbn ) > 0 ) {
if( !self::isValidISBN( $this->isbn ) ) {
- $wgOut->wrapWikiMsg( '<div class="error">$1</div>', 'booksources-invalid-isbn' );
+ $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1</div>", 'booksources-invalid-isbn' );
}
$this->showList();
}
diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php
index 0a16e6de..b6ae2ada 100644
--- a/includes/specials/SpecialBrokenRedirects.php
+++ b/includes/specials/SpecialBrokenRedirects.php
@@ -33,9 +33,9 @@ class BrokenRedirectsPage extends PageQueryPage {
rd_namespace,
rd_title
FROM $redirect AS rd
- JOIN $page p1 ON (rd.rd_from=p1.page_id)
+ 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
+ WHERE rd_namespace >= 0
AND p2.page_namespace IS NULL";
return $sql;
}
@@ -45,7 +45,7 @@ class BrokenRedirectsPage extends PageQueryPage {
}
function formatResult( $skin, $result ) {
- global $wgUser, $wgContLang;
+ global $wgUser, $wgContLang, $wgLang;
$fromObj = Title::makeTitle( $result->namespace, $result->title );
if ( isset( $result->rd_title ) ) {
@@ -61,21 +61,43 @@ class BrokenRedirectsPage extends PageQueryPage {
// $toObj may very easily be false if the $result list is cached
if ( !is_object( $toObj ) ) {
- return '<s>' . $skin->makeLinkObj( $fromObj ) . '</s>';
+ return '<s>' . $skin->link( $fromObj ) . '</s>';
}
- $from = $skin->makeKnownLinkObj( $fromObj ,'', 'redirect=no' );
- $edit = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-edit' ), 'action=edit' );
- $to = $skin->makeBrokenLinkObj( $toObj );
+ $from = $skin->linkKnown(
+ $fromObj,
+ null,
+ array(),
+ array( 'redirect' => 'no' )
+ );
+ $links = array();
+ $links[] = $skin->linkKnown(
+ $fromObj,
+ wfMsgHtml( 'brokenredirects-edit' ),
+ array(),
+ array( 'action' => 'edit' )
+ );
+ $to = $skin->link(
+ $toObj,
+ null,
+ array(),
+ array(),
+ array( 'broken' )
+ );
$arr = $wgContLang->getArrow();
- $out = "{$from} {$edit}";
+ $out = $from . wfMsg( 'word-separator' );
if( $wgUser->isAllowed( 'delete' ) ) {
- $delete = $skin->makeKnownLinkObj( $fromObj, wfMsgHtml( 'brokenredirects-delete' ), 'action=delete' );
- $out .= " {$delete}";
+ $links[] = $skin->linkKnown(
+ $fromObj,
+ wfMsgHtml( 'brokenredirects-delete' ),
+ array(),
+ array( 'action' => 'delete' )
+ );
}
+ $out .= wfMsg( 'parentheses', $wgLang->pipeList( $links ) );
$out .= " {$arr} {$to}";
return $out;
}
diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php
index c6e73f2b..a649eafd 100644
--- a/includes/specials/SpecialCategories.php
+++ b/includes/specials/SpecialCategories.php
@@ -13,9 +13,10 @@ function wfSpecialCategories( $par=null ) {
$from = $par;
}
$cap = new CategoryPager( $from );
+ $cap->doQuery();
$wgOut->addHTML(
XML::openElement( 'div', array('class' => 'mw-spcontent') ) .
- wfMsgExt( 'categoriespagetext', array( 'parse' ) ) .
+ wfMsgExt( 'categoriespagetext', array( 'parse' ), $cap->getNumRows() ) .
$cap->getStartForm( $from ) .
$cap->getNavigationBar() .
'<ul>' . $cap->getBody() . '</ul>' .
@@ -35,10 +36,7 @@ class CategoryPager extends AlphabeticPager {
parent::__construct();
$from = str_replace( ' ', '_', $from );
if( $from !== '' ) {
- global $wgCapitalLinks, $wgContLang;
- if( $wgCapitalLinks ) {
- $from = $wgContLang->ucfirst( $from );
- }
+ $from = Title::capitalize( $from, NS_CATEGORY );
$this->mOffset = $from;
}
}
@@ -74,9 +72,6 @@ class CategoryPager extends AlphabeticPager {
/* Override getBody to apply LinksBatch on resultset before actually outputting anything. */
public function getBody() {
- if (!$this->mQueryDone) {
- $this->doQuery();
- }
$batch = new LinkBatch;
$this->mResult->rewind();
@@ -92,7 +87,7 @@ class CategoryPager extends AlphabeticPager {
function formatRow($result) {
global $wgLang;
$title = Title::makeTitle( NS_CATEGORY, $result->cat_title );
- $titleText = $this->getSkin()->makeLinkObj( $title, htmlspecialchars( $title->getText() ) );
+ $titleText = $this->getSkin()->link( $title, htmlspecialchars( $title->getText() ) );
$count = wfMsgExt( 'nmembers', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $result->cat_pages ) );
return Xml::tags('li', null, "$titleText ($count)" ) . "\n";
diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php
index 9c6f857d..372a574c 100644
--- a/includes/specials/SpecialConfirmemail.php
+++ b/includes/specials/SpecialConfirmemail.php
@@ -34,10 +34,13 @@ class EmailConfirmation extends UnlistedSpecialPage {
}
} else {
$title = SpecialPage::getTitleFor( 'Userlogin' );
- $self = SpecialPage::getTitleFor( 'Confirmemail' );
$skin = $wgUser->getSkin();
- $llink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'loginreqlink' ),
- 'returnto=' . $self->getPrefixedUrl() );
+ $llink = $skin->linkKnown(
+ $title,
+ wfMsgHtml( 'loginreqlink' ),
+ array(),
+ array( 'returnto' => $this->getTitle()->getPrefixedText() )
+ );
$wgOut->addHTML( wfMsgWikiHtml( 'confirmemail_needlogin', $llink ) );
}
} else {
@@ -68,11 +71,10 @@ class EmailConfirmation extends UnlistedSpecialPage {
$wgOut->addWikiMsg( 'emailauthenticated', $time, $d, $t );
}
if( $wgUser->isEmailConfirmationPending() ) {
- $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">$1</div>", 'confirmemail_pending' );
+ $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1</div>", 'confirmemail_pending' );
}
$wgOut->addWikiMsg( 'confirmemail_text' );
- $self = SpecialPage::getTitleFor( 'Confirmemail' );
- $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $self->getLocalUrl() ) );
+ $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) );
$form .= Xml::hidden( 'token', $wgUser->editToken() );
$form .= Xml::submitButton( wfMsg( 'confirmemail_send' ) );
$form .= Xml::closeElement( 'form' );
diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php
index 9263336e..392f4332 100644
--- a/includes/specials/SpecialContributions.php
+++ b/includes/specials/SpecialContributions.php
@@ -39,7 +39,7 @@ class SpecialContributions extends SpecialPage {
return;
}
- $this->opts['limit'] = $wgRequest->getInt( 'limit', 50 );
+ $this->opts['limit'] = $wgRequest->getInt( 'limit', $wgUser->getOption('rclimit') );
$this->opts['target'] = $target;
$nt = Title::makeTitleSafe( NS_USER, $target );
@@ -89,104 +89,161 @@ class SpecialContributions extends SpecialPage {
return $this->feed( $feedType );
}
- wfRunHooks( 'SpecialContributionsBeforeMainOutput', $id );
+ if ( wfRunHooks( 'SpecialContributionsBeforeMainOutput', array( $id ) ) ) {
- $wgOut->addHTML( $this->getForm() );
+ $wgOut->addHTML( $this->getForm() );
- $pager = new ContribsPager( $target, $this->opts['namespace'], $this->opts['year'], $this->opts['month'] );
- if( !$pager->getNumRows() ) {
- $wgOut->addWikiMsg( 'nocontribs', $target );
- return;
- }
+ $pager = new ContribsPager( $target, $this->opts['namespace'], $this->opts['year'], $this->opts['month'] );
+ if( !$pager->getNumRows() ) {
+ $wgOut->addWikiMsg( 'nocontribs', $target );
+ } else {
+ # Show a message about slave lag, if applicable
+ if( ( $lag = $pager->getDatabase()->getLag() ) > 0 )
+ $wgOut->showLagWarning( $lag );
+
+ $wgOut->addHTML(
+ '<p>' . $pager->getNavigationBar() . '</p>' .
+ $pager->getBody() .
+ '<p>' . $pager->getNavigationBar() . '</p>'
+ );
+ }
- # Show a message about slave lag, if applicable
- if( ( $lag = $pager->getDatabase()->getLag() ) > 0 )
- $wgOut->showLagWarning( $lag );
- $wgOut->addHTML(
- '<p>' . $pager->getNavigationBar() . '</p>' .
- $pager->getBody() .
- '<p>' . $pager->getNavigationBar() . '</p>'
- );
+ # Show the appropriate "footer" message - WHOIS tools, etc.
+ if( $target != 'newbies' ) {
+ $message = 'sp-contributions-footer';
+ if ( IP::isIPAddress( $target ) ) {
+ $message = 'sp-contributions-footer-anon';
+ } else {
+ $user = User::newFromName( $target );
+ if ( !$user || $user->isAnon() ) {
+ // No message for non-existing users
+ return;
+ }
+ }
- # If there were contributions, and it was a valid user or IP, show
- # the appropriate "footer" message - WHOIS tools, etc.
- if( $target != 'newbies' ) {
- $message = IP::isIPAddress( $target ) ?
- 'sp-contributions-footer-anon' : 'sp-contributions-footer';
-
- $text = wfMsgNoTrans( $message, $target );
- if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
- $wgOut->addHTML( '<div class="mw-contributions-footer">' );
- $wgOut->addWikiText( $text );
- $wgOut->addHTML( '</div>' );
+ $text = wfMsgNoTrans( $message, $target );
+ if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
+ $wgOut->wrapWikiMsg(
+ "<div class='mw-contributions-footer'>\n$1\n</div>",
+ array( $message, $target ) );
+ }
}
}
}
protected function setSyndicated() {
global $wgOut;
- $queryParams = array(
- 'namespace' => $this->opts['namespace'],
- 'target' => $this->opts['target']
- );
$wgOut->setSyndicated( true );
- $wgOut->setFeedAppendQuery( wfArrayToCGI( $queryParams ) );
+ $wgOut->setFeedAppendQuery( wfArrayToCGI( $this->opts ) );
}
/**
- * Generates the subheading with links
- * @param Title $nt Title object for the target
- * @param integer $id User ID for the target
- * @return String: appropriately-escaped HTML to be output literally
- */
+ * Generates the subheading with links
+ * @param Title $nt @see Title object for the target
+ * @param integer $id 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.
+ */
protected function contributionsSub( $nt, $id ) {
- global $wgSysopUserBans, $wgLang, $wgUser;
+ global $wgSysopUserBans, $wgLang, $wgUser, $wgOut;
$sk = $wgUser->getSkin();
- if( 0 == $id ) {
- $user = $nt->getText();
+ if ( $id === null ) {
+ $user = htmlspecialchars( $nt->getText() );
} else {
- $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) );
+ $user = $sk->link( $nt, htmlspecialchars( $nt->getText() ) );
}
+ $userObj = User::newFromName( $nt->getText(), /* check for username validity not needed */ false );
$talk = $nt->getTalkPage();
if( $talk ) {
# Talk page link
- $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) );
- if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && IP::isIPAddress( $nt->getText() ) ) ) {
- # Block link
- if( $wgUser->isAllowed( 'block' ) )
- $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip',
- $nt->getDBkey() ), wfMsgHtml( 'blocklink' ) );
+ $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( 'BlockList' ),
+ 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->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() );
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Log' ),
+ wfMsgHtml( 'sp-contributions-blocklog' ),
+ array(),
+ array(
+ 'type' => 'block',
+ 'page' => $nt->getPrefixedText()
+ )
+ );
}
# Other logs link
- $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ), wfMsg( 'sp-contributions-logs' ),
- 'user=' . $nt->getPartialUrl() );
+ $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->makeKnownLinkObj( SpecialPage::getTitleFor( 'DeletedContributions',
- $nt->getDBkey() ), wfMsgHtml( 'deletedcontributions' ) );
+ $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( 0 !== $id && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) {
- $tools[] = $sk->makeKnownLinkObj(
+ if( $id !== null && $userrightsPage->userCanChangeRights( User::newFromId( $id ) ) ) {
+ $tools[] = $sk->linkKnown(
SpecialPage::getTitleFor( 'Userrights', $nt->getDBkey() ),
- wfMsgHtml( 'userrights' )
+ wfMsgHtml( 'sp-contributions-userrights' )
);
}
wfRunHooks( 'ContributionsToolLinks', array( $id, $nt, &$tools ) );
-
+
$links = $wgLang->pipeList( $tools );
+
+ // Show a note if the user is blocked and display the last block log entry.
+ if ( $userObj->isBlocked() ) {
+ LogEventsList::showLogExtract(
+ $wgOut,
+ 'block',
+ $nt->getPrefixedText(),
+ '',
+ array(
+ 'lim' => 1,
+ 'showIfEmpty' => false,
+ 'msgKey' => array(
+ 'sp-contributions-blocked-notice',
+ $nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
+ ),
+ 'offset' => '' # don't use $wgRequest parameter offset
+ )
+ );
+ }
}
-
+
// Old message 'contribsub' had one parameter, but that doesn't work for
// languages that want to put the "for" bit right after $user but before
// $links. If 'contribsub' is around, use it for reverse compatibility,
@@ -203,9 +260,9 @@ class SpecialContributions extends SpecialPage {
* @param $this->opts Array: the options to be included.
*/
protected function getForm() {
- global $wgScript, $wgTitle;
+ global $wgScript;
- $this->opts['title'] = $wgTitle->getPrefixedText();
+ $this->opts['title'] = $this->getTitle()->getPrefixedText();
if( !isset( $this->opts['target'] ) ) {
$this->opts['target'] = '';
} else {
@@ -249,11 +306,14 @@ class SpecialContributions extends SpecialPage {
$f .= '<fieldset>' .
Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) .
- Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parseinline' ) ),
+ Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parsemag' ) ),
'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ? true : false ) . '<br />' .
- Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parseinline' ) ),
+ Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parsemag' ) ),
'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ? true : false ) . ' ' .
- Xml::input( 'target', 20, $this->opts['target']) . ' '.
+ Html::input( 'target', $this->opts['target'], 'text', array(
+ 'size' => '20',
+ 'required' => ''
+ ) + ( $this->opts['target'] ? array() : array( 'autofocus' ) ) ) . ' '.
'<span style="white-space: nowrap">' .
Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
Xml::namespaceSelector( $this->opts['namespace'], '' ) .
@@ -268,7 +328,7 @@ class SpecialContributions extends SpecialPage {
$explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' );
if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) )
- $f .= "<p>{$explain}</p>";
+ $f .= "<p id='mw-sp-contributions-explain'>{$explain}</p>";
$f .= '</fieldset>' .
Xml::closeElement( 'form' );
@@ -341,7 +401,7 @@ class SpecialContributions extends SpecialPage {
$comments
);
} else {
- return NULL;
+ return null;
}
}
@@ -371,9 +431,13 @@ class ContribsPager extends ReverseChronologicalPager {
function __construct( $target, $namespace = false, $year = false, $month = false, $tagFilter = false ) {
parent::__construct();
- foreach( explode( ' ', 'uctop diff newarticle rollbacklink diff hist newpageletter minoreditletter' ) as $msg ) {
- $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') );
+
+ $msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' );
+
+ foreach( $msgs as $msg ) {
+ $this->messages[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) );
}
+
$this->target = $target;
$this->namespace = $namespace;
$this->tagFilter = $tagFilter;
@@ -395,8 +459,11 @@ class ContribsPager extends ReverseChronologicalPager {
$conds = array_merge( $userCond, $this->getNamespaceCond() );
// Paranoia: avoid brute force searches (bug 17342)
- if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
- $conds[] = 'rev_deleted & ' . Revision::DELETED_USER . ' = 0';
+ if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
+ $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::DELETED_USER) . ' = 0';
+ } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ $conds[] = $this->mDb->bitAnd('rev_deleted',Revision::SUPPRESSED_USER) .
+ ' != ' . Revision::SUPPRESSED_USER;
}
$join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' );
@@ -411,14 +478,16 @@ class ContribsPager extends ReverseChronologicalPager {
'options' => array( 'USE INDEX' => array('revision' => $index) ),
'join_conds' => $join_cond
);
-
- ChangeTags::modifyDisplayQuery( $queryInfo['tables'],
- $queryInfo['fields'],
- $queryInfo['conds'],
- $queryInfo['join_conds'],
- $queryInfo['options'],
- $this->tagFilter );
-
+
+ ChangeTags::modifyDisplayQuery(
+ $queryInfo['tables'],
+ $queryInfo['fields'],
+ $queryInfo['conds'],
+ $queryInfo['join_conds'],
+ $queryInfo['options'],
+ $this->tagFilter
+ );
+
wfRunHooks( 'ContribsPager::getQueryInfo', array( &$this, &$queryInfo ) );
return $queryInfo;
}
@@ -473,7 +542,7 @@ class ContribsPager extends ReverseChronologicalPager {
* @todo This would probably look a lot nicer in a table.
*/
function formatRow( $row ) {
- global $wgLang, $wgUser, $wgContLang;
+ global $wgUser, $wgLang, $wgContLang;
wfProfileIn( __METHOD__ );
$sk = $this->getSkin();
@@ -482,60 +551,101 @@ class ContribsPager extends ReverseChronologicalPager {
$page = Title::newFromRow( $row );
$page->resetArticleId( $row->rev_page ); // use process cache
- $link = $sk->makeLinkObj( $page, $page->getPrefixedText(), $page->isRedirect() ? 'redirect=no' : '' );
+ $link = $sk->link(
+ $page,
+ htmlspecialchars( $page->getPrefixedText() ),
+ array(),
+ $page->isRedirect() ? array( 'redirect' => 'no' ) : array()
+ );
# Mark current revisions
$difftext = $topmarktext = '';
if( $row->rev_id == $row->page_latest ) {
- $topmarktext .= '<strong>' . $this->messages['uctop'] . '</strong>';
- if( !$row->page_is_new ) {
- $difftext .= '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'], 'diff=0' ) . ')';
- # Add rollback link
- if( $page->quickUserCan( 'rollback') && $page->quickUserCan( 'edit' ) ) {
- $topmarktext .= ' '.$sk->generateRollback( $rev );
- }
- } else {
- $difftext .= $this->messages['newarticle'];
+ $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>';
+ # Add rollback link
+ if( !$row->page_is_new && $page->quickUserCan( 'rollback' )
+ && $page->quickUserCan( 'edit' ) )
+ {
+ $topmarktext .= ' '.$sk->generateRollback( $rev );
}
}
# Is there a visible previous revision?
- if( $rev->userCan(Revision::DELETED_TEXT) ) {
- $difftext = '(' . $sk->makeKnownLinkObj( $page, $this->messages['diff'],
- 'diff=prev&oldid='.$row->rev_id ) . ')';
+ if( $rev->userCan( Revision::DELETED_TEXT ) && $rev->getParentId() !== 0 ) {
+ $difftext = $sk->linkKnown(
+ $page,
+ $this->messages['diff'],
+ array(),
+ array(
+ 'diff' => 'prev',
+ 'oldid' => $row->rev_id
+ )
+ );
} else {
- $difftext = '(' . $this->messages['diff'] . ')';
+ $difftext = $this->messages['diff'];
}
- $histlink = '('.$sk->makeKnownLinkObj( $page, $this->messages['hist'], 'action=history' ) . ')';
+ $histlink = $sk->linkKnown(
+ $page,
+ $this->messages['hist'],
+ array(),
+ array( 'action' => 'history' )
+ );
$comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true );
$date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true );
- $d = $sk->makeKnownLinkObj( $page, $date, 'oldid='.intval($row->rev_id) );
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $d = '<span class="history-deleted">' . $date . '</span>';
+ } else {
+ $d = $sk->linkKnown(
+ $page,
+ htmlspecialchars($date),
+ array(),
+ array( 'oldid' => intval( $row->rev_id ) )
+ );
+ }
if( $this->target == 'newbies' ) {
$userlink = ' . . ' . $sk->userLink( $row->rev_user, $row->rev_user_text );
- $userlink .= ' (' . $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) . ') ';
+ $userlink .= ' ' . wfMsg( 'parentheses', $sk->userTalkLink( $row->rev_user, $row->rev_user_text ) ) . ' ';
} else {
$userlink = '';
}
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $d = '<span class="history-deleted">' . $d . '</span>';
- }
-
if( $rev->getParentId() === 0 ) {
- $nflag = '<span class="newpage">' . $this->messages['newpageletter'] . '</span>';
+ $nflag = ChangesList::flag( 'newpage' );
} else {
$nflag = '';
}
if( $rev->isMinor() ) {
- $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> ';
+ $mflag = ChangesList::flag( 'minor' );
} else {
$mflag = '';
}
- $ret = "{$d} {$histlink} {$difftext} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $ret .= ' ' . wfMsgHtml( 'deletedrev' );
+ // Don't show useless link to people who cannot hide revisions
+ $canHide = $wgUser->isAllowed( 'deleterevision' );
+ if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
+ if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ $del = $this->mSkin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+ } else {
+ $query = array(
+ 'type' => 'revision',
+ 'target' => $page->getPrefixedDbkey(),
+ 'ids' => $rev->getId()
+ );
+ $del = $this->mSkin->revDeleteLink( $query,
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
+ }
+ $del .= ' ';
+ } else {
+ $del = '';
+ }
+
+ $diffHistLinks = '(' . $difftext . $this->messages['pipe-separator'] . $histlink . ')';
+ $ret = "{$del}{$d} {$diffHistLinks} {$nflag}{$mflag} {$link}{$userlink} {$comment} {$topmarktext}";
+
+ # Denote if username is redacted for this edit
+ if( $rev->isDeleted( Revision::DELETED_USER ) ) {
+ $ret .= " <strong>" . wfMsgHtml('rev-deleted-user-contribs') . "</strong>";
}
# Tags, if any.
diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php
index 67b05ca1..8884bb22 100644
--- a/includes/specials/SpecialDeletedContributions.php
+++ b/includes/specials/SpecialDeletedContributions.php
@@ -11,8 +11,9 @@ class DeletedContribsPager extends IndexPager {
function __construct( $target, $namespace = false ) {
parent::__construct();
- foreach( explode( ' ', 'deletionlog undeletebtn minoreditletter diff' ) as $msg ) {
- $this->messages[$msg] = wfMsgExt( $msg, array( 'escape') );
+ $msgs = array( 'deletionlog', 'undeleteviewlink', 'diff' );
+ foreach( $msgs as $msg ) {
+ $this->messages[$msg] = wfMsgExt( $msg, array( 'escapenoentities') );
}
$this->target = $target;
$this->namespace = $namespace;
@@ -30,8 +31,11 @@ class DeletedContribsPager extends IndexPager {
list( $index, $userCond ) = $this->getUserCond();
$conds = array_merge( $userCond, $this->getNamespaceCond() );
// Paranoia: avoid brute force searches (bug 17792)
- if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
- $conds[] = 'ar_deleted & ' . Revision::DELETED_USER . ' = 0';
+ if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
+ $conds[] = $this->mDb->bitAnd('ar_deleted',Revision::DELETED_USER) . ' = 0';
+ } else if( !$wgUser->isAllowed( 'suppressrevision' ) ) {
+ $conds[] = $this->mDb->bitAnd('ar_deleted',Revision::SUPPRESSED_USER) .
+ ' != ' . Revision::SUPPRESSED_USER;
}
return array(
'tables' => array( 'archive' ),
@@ -71,9 +75,10 @@ class DeletedContribsPager extends IndexPager {
if ( isset( $this->mNavigationBar ) ) {
return $this->mNavigationBar;
}
+ $fmtLimit = $wgLang->formatNum( $this->mLimit );
$linkTexts = array(
- 'prev' => wfMsgHtml( 'pager-newer-n', $this->mLimit ),
- 'next' => wfMsgHtml( 'pager-older-n', $this->mLimit ),
+ 'prev' => wfMsgExt( 'pager-newer-n', array( 'escape', 'parsemag' ), $fmtLimit ),
+ 'next' => wfMsgExt( 'pager-older-n', array( 'escape', 'parsemag' ), $fmtLimit ),
'first' => wfMsgHtml( 'histlast' ),
'last' => wfMsgHtml( 'histfirst' )
);
@@ -83,7 +88,7 @@ class DeletedContribsPager extends IndexPager {
$limits = $wgLang->pipeList( $limitLinks );
$this->mNavigationBar = "(" . $wgLang->pipeList( array( $pagingLinks['first'], $pagingLinks['last'] ) ) . ") " .
- wfMsgExt( 'viewprevnext', array( 'parsemag' ), $pagingLinks['prev'], $pagingLinks['next'], $limits );
+ wfMsgExt( 'viewprevnext', array( 'parsemag', 'escape', 'replaceafter' ), $pagingLinks['prev'], $pagingLinks['next'], $limits );
return $this->mNavigationBar;
}
@@ -106,10 +111,9 @@ class DeletedContribsPager extends IndexPager {
* @todo This would probably look a lot nicer in a table.
*/
function formatRow( $row ) {
+ global $wgUser, $wgLang;
wfProfileIn( __METHOD__ );
- global $wgLang, $wgUser;
-
$sk = $this->getSkin();
$rev = new Revision( array(
@@ -119,7 +123,7 @@ class DeletedContribsPager extends IndexPager {
'user_text' => $row->ar_user_text,
'timestamp' => $row->ar_timestamp,
'minor_edit' => $row->ar_minor_edit,
- 'deleted' => $row->ar_deleted,
+ 'deleted' => $row->ar_deleted,
) );
$page = Title::makeTitle( $row->ar_namespace, $row->ar_title );
@@ -127,50 +131,96 @@ class DeletedContribsPager extends IndexPager {
$undelete = SpecialPage::getTitleFor( 'Undelete' );
$logs = SpecialPage::getTitleFor( 'Log' );
- $dellog = $sk->makeKnownLinkObj( $logs,
+ $dellog = $sk->linkKnown(
+ $logs,
$this->messages['deletionlog'],
- 'type=delete&page=' . $page->getPrefixedUrl() );
-
- $reviewlink = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
- $this->messages['undeletebtn'] );
+ array(),
+ array(
+ 'type' => 'delete',
+ 'page' => $page->getPrefixedText()
+ )
+ );
- $link = $sk->makeKnownLinkObj( $undelete,
- htmlspecialchars( $page->getPrefixedText() ),
- 'target=' . $page->getPrefixedUrl() .
- '&timestamp=' . $rev->getTimestamp() );
+ $reviewlink = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Undelete', $page->getPrefixedDBkey() ),
+ $this->messages['undeleteviewlink']
+ );
- $last = $sk->makeKnownLinkObj( $undelete,
- $this->messages['diff'],
- "target=" . $page->getPrefixedUrl() .
- "&timestamp=" . $rev->getTimestamp() .
- "&diff=prev" );
+ if( $wgUser->isAllowed('deletedtext') ) {
+ $last = $sk->linkKnown(
+ $undelete,
+ $this->messages['diff'],
+ array(),
+ array(
+ 'target' => $page->getPrefixedText(),
+ 'timestamp' => $rev->getTimestamp(),
+ 'diff' => 'prev'
+ )
+ );
+ } else {
+ $last = $this->messages['diff'];
+ }
$comment = $sk->revComment( $rev );
- $d = $wgLang->timeanddate( $rev->getTimestamp(), true );
+ $date = htmlspecialchars( $wgLang->timeanddate( $rev->getTimestamp(), true ) );
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $d = '<span class="history-deleted">' . $d . '</span>';
+ if( !$wgUser->isAllowed('undelete') || !$rev->userCan(Revision::DELETED_TEXT) ) {
+ $link = $date; // unusable link
} else {
- $link = $sk->makeKnownLinkObj( $undelete, $d,
- 'target=' . $page->getPrefixedUrl() .
- '&timestamp=' . $rev->getTimestamp() );
+ $link = $sk->linkKnown(
+ $undelete,
+ $date,
+ array(),
+ array(
+ 'target' => $page->getPrefixedText(),
+ 'timestamp' => $rev->getTimestamp()
+ )
+ );
+ }
+ // Style deleted items
+ if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
}
- $pagelink = $sk->makeLinkObj( $page );
+ $pagelink = $sk->link( $page );
if( $rev->isMinor() ) {
- $mflag = '<span class="minor">' . $this->messages['minoreditletter'] . '</span> ';
+ $mflag = ChangesList::flag( 'minor' );
} else {
$mflag = '';
}
+
+ // Revision delete link
+ $canHide = $wgUser->isAllowed( 'deleterevision' );
+ if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
+ if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
+ $del = $this->mSkin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
+ } else {
+ $query = array(
+ 'type' => 'archive',
+ 'target' => $page->getPrefixedDbkey(),
+ 'ids' => $rev->getTimestamp() );
+ $del = $this->mSkin->revDeleteLink( $query,
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide ) . ' ';
+ }
+ } else {
+ $del = '';
+ }
-
- $ret = "{$link} ($last) ({$dellog}) ({$reviewlink}) . . {$mflag} {$pagelink} {$comment}";
- if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
- $ret .= ' ' . wfMsgHtml( 'deletedrev' );
+ $tools = Html::rawElement(
+ 'span',
+ 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>";
}
- $ret = "<li>$ret</li>\n";
+ $ret = Html::rawElement( 'li', array(), $ret ) . "\n";
wfProfileOut( __METHOD__ );
return $ret;
@@ -208,7 +258,7 @@ class DeletedContributionsPage extends SpecialPage {
return;
}
- global $wgUser, $wgOut, $wgLang, $wgRequest;
+ global $wgOut, $wgLang, $wgRequest;
$wgOut->setPageTitle( wfMsgExt( 'deletedcontributions-title', array( 'parsemag' ) ) );
@@ -248,7 +298,7 @@ class DeletedContributionsPage extends SpecialPage {
$pager = new DeletedContribsPager( $target, $options['namespace'] );
if ( !$pager->getNumRows() ) {
- $wgOut->addWikiText( wfMsg( 'nocontribs' ) );
+ $wgOut->addWikiMsg( 'nocontribs' );
return;
}
@@ -271,50 +321,112 @@ class DeletedContributionsPage extends SpecialPage {
$text = wfMsgNoTrans( $message, $target );
if( !wfEmptyMsg( $message, $text ) && $text != '-' ) {
- $wgOut->addHTML( '<div class="mw-contributions-footer">' );
- $wgOut->addWikiText( $text );
- $wgOut->addHTML( '</div>' );
+ $wgOut->wrapWikiMsg( "<div class='mw-contributions-footer'>\n$1\n</div>", array( $message, $target ) );
}
}
}
/**
* Generates the subheading with links
- * @param $nt @see Title object for the target
+ * @param Title $nt @see Title object for the target
+ * @param integer $id 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.
*/
function getSubTitle( $nt, $id ) {
- global $wgSysopUserBans, $wgLang, $wgUser;
+ global $wgSysopUserBans, $wgLang, $wgUser, $wgOut;
$sk = $wgUser->getSkin();
- if ( 0 == $id ) {
- $user = $nt->getText();
+ if ( $id === null ) {
+ $user = htmlspecialchars( $nt->getText() );
} else {
- $user = $sk->makeLinkObj( $nt, htmlspecialchars( $nt->getText() ) );
+ $user = $sk->link( $nt, htmlspecialchars( $nt->getText() ) );
}
+ $userObj = User::newFromName( $nt->getText(), /* check for username validity not needed */ false );
$talk = $nt->getTalkPage();
if( $talk ) {
# Talk page link
- $tools[] = $sk->makeLinkObj( $talk, wfMsgHtml( 'talkpagelinktext' ) );
- if( ( $id != 0 && $wgSysopUserBans ) || ( $id == 0 && User::isIP( $nt->getText() ) ) ) {
- # Block link
- if( $wgUser->isAllowed( 'block' ) )
- $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Blockip', $nt->getDBkey() ),
- wfMsgHtml( 'blocklink' ) );
+ $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( 'BlockList' ),
+ 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->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'sp-contributions-blocklog' ), 'type=block&page=' . $nt->getPrefixedUrl() );
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Log' ),
+ wfMsgHtml( 'sp-contributions-blocklog' ),
+ array(),
+ array(
+ 'type' => 'block',
+ 'page' => $nt->getPrefixedText()
+ )
+ );
}
# Other logs link
- $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Log' ),
- wfMsgHtml( 'log' ), 'user=' . $nt->getPartialUrl() );
- # Link to undeleted contributions
- $tools[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'Contributions', $nt->getDBkey() ),
- wfMsgHtml( 'contributions' ) );
-
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Log' ),
+ wfMsgHtml( 'sp-contributions-logs' ),
+ array(),
+ array( 'user' => $nt->getText() )
+ );
+ # Link to contributions
+ $tools[] = $sk->linkKnown(
+ SpecialPage::getTitleFor( 'Contributions', $nt->getDBkey() ),
+ wfMsgHtml( 'sp-deletedcontributions-contribs' )
+ );
+
+ # 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 ) );
$links = $wgLang->pipeList( $tools );
+
+ // Show a note if the user is blocked and display the last block log entry.
+ if ( $userObj->isBlocked() ) {
+ LogEventsList::showLogExtract(
+ $wgOut,
+ 'block',
+ $nt->getPrefixedText(),
+ '',
+ array(
+ 'lim' => 1,
+ 'showIfEmpty' => false,
+ 'msgKey' => array(
+ 'sp-contributions-blocked-notice',
+ $nt->getText() # Support GENDER in 'sp-contributions-blocked-notice'
+ ),
+ 'offset' => '' # don't use $wgRequest parameter offset
+ )
+ );
+ }
}
// Old message 'contribsub' had one parameter, but that doesn't work for
@@ -333,9 +445,9 @@ class DeletedContributionsPage extends SpecialPage {
* @param $options Array: the options to be included.
*/
function getForm( $options ) {
- global $wgScript, $wgTitle, $wgRequest;
+ global $wgScript, $wgRequest;
- $options['title'] = $wgTitle->getPrefixedText();
+ $options['title'] = SpecialPage::getTitleFor( 'DeletedContributions' )->getPrefixedText();
if ( !isset( $options['target'] ) ) {
$options['target'] = '';
} else {
@@ -366,7 +478,10 @@ class DeletedContributionsPage extends SpecialPage {
$f .= Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) .
Xml::tags( 'label', array( 'for' => 'target' ), wfMsgExt( 'sp-contributions-username', 'parseinline' ) ) . ' ' .
- Xml::input( 'target', 20, $options['target']) . ' '.
+ Html::input( 'target', $options['target'], 'text', array(
+ 'size' => '20',
+ 'required' => ''
+ ) + ( $options['target'] ? array() : array( 'autofocus' ) ) ) . ' '.
Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' .
Xml::namespaceSelector( $options['namespace'], '' ) . ' ' .
Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) .
diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php
index 0a728b68..1941112a 100644
--- a/includes/specials/SpecialDisambiguations.php
+++ b/includes/specials/SpecialDisambiguations.php
@@ -88,7 +88,7 @@ class DisambiguationsPage extends PageQueryPage {
$dp = Title::makeTitle( $result->namespace, $result->title );
$from = $skin->link( $title );
- $edit = $skin->link( $title, "(".wfMsgHtml("qbedit").")", array(), array( 'redirect' => 'no', 'action' => 'edit' ) );
+ $edit = $skin->link( $title, wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ) , array(), array( 'redirect' => 'no', 'action' => 'edit' ) );
$arr = $wgContLang->getArrow();
$to = $skin->link( $dp );
diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php
index b1bad0c3..893fee9e 100644
--- a/includes/specials/SpecialDoubleRedirects.php
+++ b/includes/specials/SpecialDoubleRedirects.php
@@ -74,16 +74,34 @@ class DoubleRedirectsPage extends PageQueryPage {
}
}
if ( !$result ) {
- return '<s>' . $skin->makeLinkObj( $titleA, '', 'redirect=no' ) . '</s>';
+ return '<s>' . $skin->link( $titleA, null, array(), array( 'redirect' => 'no' ) ) . '</s>';
}
$titleB = Title::makeTitle( $result->nsb, $result->tb );
$titleC = Title::makeTitle( $result->nsc, $result->tc );
- $linkA = $skin->makeKnownLinkObj( $titleA, '', 'redirect=no' );
- $edit = $skin->makeBrokenLinkObj( $titleA, "(".wfMsg("qbedit").")" , 'redirect=no');
- $linkB = $skin->makeKnownLinkObj( $titleB, '', 'redirect=no' );
- $linkC = $skin->makeKnownLinkObj( $titleC );
+ $linkA = $skin->linkKnown(
+ $titleA,
+ null,
+ array(),
+ array( 'redirect' => 'no' )
+ );
+ $edit = $skin->linkKnown(
+ $titleA,
+ wfMsgExt( 'parentheses', array( 'escape' ), wfMsg( 'editlink' ) ),
+ array(),
+ array(
+ 'redirect' => 'no',
+ 'action' => 'edit'
+ )
+ );
+ $linkB = $skin->linkKnown(
+ $titleB,
+ null,
+ array(),
+ array( 'redirect' => 'no' )
+ );
+ $linkC = $skin->linkKnown( $titleC );
$arr = $wgContLang->getArrow() . $wgContLang->getDirMark();
return( "{$linkA} {$edit} {$arr} {$linkB} {$arr} {$linkC}" );
diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php
index 58e2514e..48088ded 100644
--- a/includes/specials/SpecialEmailuser.php
+++ b/includes/specials/SpecialEmailuser.php
@@ -48,6 +48,12 @@ function wfSpecialEmailuser( $par ) {
case 'mailnologin':
$wgOut->showErrorPage( 'mailnologin', 'mailnologintext' );
return;
+ default:
+ // It's a hook error
+ list( $title, $msg, $params ) = $error;
+ $wgOut->showErrorPage( $title, $msg, $params );
+ return;
+
}
}
@@ -256,7 +262,7 @@ class EmailUserForm {
}
static function validateEmailTarget ( $target ) {
- if ( "" == $target ) {
+ if ( $target == "" ) {
wfDebug( "Target is empty.\n" );
return "notarget";
}
@@ -268,7 +274,7 @@ class EmailUserForm {
}
$nu = User::newFromName( $nt->getText() );
- if( is_null( $nu ) || !$nu->getId() ) {
+ if( !$nu instanceof User || !$nu->getId() ) {
wfDebug( "Target is invalid user.\n" );
return "notarget";
} else if ( !$nu->isEmailConfirmed() ) {
@@ -284,6 +290,10 @@ class EmailUserForm {
static function getPermissionsError ( $user, $editToken ) {
if( !$user->canSendEmail() ) {
wfDebug( "User can't send.\n" );
+ // FIXME: this is also the error if user is in a group
+ // that is not allowed to send e-mail (no right
+ // 'sendemail'). Error messages should probably
+ // be more fine grained.
return "mailnologin";
}
@@ -297,12 +307,17 @@ class EmailUserForm {
return 'actionthrottledtext';
}
+ $hookErr = null;
+ wfRunHooks( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) );
+
+ if ($hookErr) {
+ return $hookErr;
+ }
+
if( !$user->matchEditToken( $editToken ) ) {
wfDebug( "Matching edit token failed.\n" );
return 'sessionfailure';
}
-
- return;
}
static function newFromURL( $target, $text, $subject, $cc_me )
diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php
index 8bf16a71..b9a44d48 100644
--- a/includes/specials/SpecialExport.php
+++ b/includes/specials/SpecialExport.php
@@ -44,17 +44,18 @@ class SpecialExport extends SpecialPage {
$this->templates = $wgRequest->getCheck( 'templates' );
$this->images = $wgRequest->getCheck( 'images' ); // Doesn't do anything yet
$this->pageLinkDepth = $this->validateLinkDepth(
- $wgRequest->getIntOrNull( 'pagelink-depth' ) );
+ $wgRequest->getIntOrNull( 'pagelink-depth' ) );
+ $nsindex = '';
if ( $wgRequest->getCheck( 'addcat' ) ) {
$page = $wgRequest->getText( 'pages' );
$catname = $wgRequest->getText( 'catname' );
- if ( $catname !== '' && $catname !== NULL && $catname !== false ) {
+ if ( $catname !== '' && $catname !== null && $catname !== false ) {
$t = Title::makeTitleSafe( NS_MAIN, $catname );
if ( $t ) {
/**
- * @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.
*/
@@ -65,15 +66,15 @@ class SpecialExport extends SpecialPage {
}
else if( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) {
$page = $wgRequest->getText( 'pages' );
- $nsindex = $wgRequest->getText( 'nsindex' );
+ $nsindex = $wgRequest->getText( 'nsindex', '' );
- if ( $nsindex !== '' && $nsindex !== NULL && $nsindex !== false ) {
+ if ( strval( $nsindex ) !== '' ) {
/**
- * Same implementation as above, so same @fixme
+ * Same implementation as above, so same @todo
*/
$nspages = $this->getPagesFromNamespace( $nsindex );
if ( $nspages ) $page .= "\n" . implode( "\n", $nspages );
- }
+ }
}
else if( $wgRequest->wasPosted() && $par == '' ) {
$page = $wgRequest->getText( 'pages' );
@@ -87,15 +88,15 @@ class SpecialExport extends SpecialPage {
$limit = $wgRequest->getInt( 'limit' );
$dir = $wgRequest->getVal( 'dir' );
$history = array(
- 'dir' => 'asc',
- 'offset' => false,
- 'limit' => $wgExportMaxHistory,
- );
+ 'dir' => 'asc',
+ 'offset' => false,
+ 'limit' => $wgExportMaxHistory,
+ );
$historyCheck = $wgRequest->getCheck( 'history' );
if ( $this->curonly ) {
$history = WikiExporter::CURRENT;
} elseif ( !$historyCheck ) {
- if ( $limit > 0 && $limit < $wgExportMaxHistory ) {
+ if ( $limit > 0 && ($wgExportMaxHistory == 0 || $limit < $wgExportMaxHistory ) ) {
$history['limit'] = $limit;
}
if ( !is_null( $offset ) ) {
@@ -146,12 +147,12 @@ class SpecialExport extends SpecialPage {
$wgOut->addWikiMsg( 'exporttext' );
$form = Xml::openElement( 'form', array( 'method' => 'post',
- 'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) );
+ 'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) );
$form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . '&nbsp;';
$form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '<br />';
if ( $wgExportFromNamespaces ) {
- $form .= Xml::namespaceSelector( '', null, 'nsindex', wfMsg( 'export-addnstext' ) ) . '&nbsp;';
+ $form .= Xml::namespaceSelector( $nsindex, null, 'nsindex', wfMsg( 'export-addnstext' ) ) . '&nbsp;';
$form .= Xml::submitButton( wfMsg( 'export-addns' ), array( 'name' => 'addns' ) ) . '<br />';
}
@@ -190,10 +191,22 @@ class SpecialExport extends SpecialPage {
private function doExport( $page, $history, $list_authors ) {
global $wgExportMaxHistory;
- /* Split up the input and look up linked pages */
- $inputPages = array_filter( explode( "\n", $page ), array( $this, 'filterPage' ) );
- $pageSet = array_flip( $inputPages );
+ $pageSet = array(); // Inverted index of all pages to look up
+
+ // Split up and normalize input
+ foreach( explode( "\n", $page ) as $pageName ) {
+ $pageName = trim( $pageName );
+ $title = Title::newFromText( $pageName );
+ if( $title && $title->getInterwiki() == '' && $title->getText() !== '' ) {
+ // Only record each page once!
+ $pageSet[$title->getPrefixedText()] = true;
+ }
+ }
+ // Set of original pages to pass on to further manipulation...
+ $inputPages = array_keys( $pageSet );
+
+ // Look up any linked pages if asked...
if( $this->templates ) {
$pageSet = $this->getTemplates( $inputPages, $pageSet );
}
@@ -210,7 +223,13 @@ class SpecialExport extends SpecialPage {
*/
$pages = array_keys( $pageSet );
-
+
+ // Normalize titles to the same format and remove dupes, see bug 17374
+ foreach( $pages as $k => $v ) {
+ $pages[$k] = str_replace( " ", "_", $v );
+ }
+ $pages = array_unique( $pages );
+
/* Ok, let's get to it... */
if( $history == WikiExporter::CURRENT ) {
$lb = false;
@@ -256,8 +275,7 @@ class SpecialExport extends SpecialPage {
$lb->closeAll();
}
}
-
-
+
private function getPagesFromCategory( $title ) {
global $wgContLang;
@@ -374,7 +392,7 @@ class SpecialExport extends SpecialPage {
$title = Title::newFromText( $page );
if( $title ) {
$pageSet[$title->getPrefixedText()] = true;
- /// @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 ),
@@ -382,7 +400,7 @@ class SpecialExport extends SpecialPage {
array_merge( $join,
array(
'page_namespace' => $title->getNamespace(),
- 'page_title' => $title->getDBKey() ) ),
+ 'page_title' => $title->getDBkey() ) ),
__METHOD__ );
foreach( $result as $row ) {
$template = Title::makeTitle( $row->namespace, $row->title );
@@ -392,12 +410,5 @@ class SpecialExport extends SpecialPage {
}
return $pageSet;
}
-
- /**
- * Callback function to remove empty strings from the pages array.
- */
- private function filterPage( $page ) {
- return $page !== '' && $page !== null;
- }
}
diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php
index afd5ad48..65d76a65 100644
--- a/includes/specials/SpecialFewestrevisions.php
+++ b/includes/specials/SpecialFewestrevisions.php
@@ -53,15 +53,26 @@ class FewestrevisionsPage extends QueryPage {
global $wgLang, $wgContLang;
$nt = Title::makeTitleSafe( $result->namespace, $result->title );
+ if( !$nt ) {
+ return '<!-- bad title -->';
+ }
+
$text = $wgContLang->convert( $nt->getPrefixedText() );
- $plink = $skin->makeKnownLinkObj( $nt, $text );
+ $plink = $skin->linkKnown(
+ $nt,
+ $text
+ );
- $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'),
+ $nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $result->value ) );
- $redirect = $result->redirect ? ' - ' . wfMsg( 'isredirect' ) : '';
- $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' ) . $redirect;
-
+ $redirect = $result->redirect ? ' - ' . wfMsgHtml( 'isredirect' ) : '';
+ $nlink = $skin->linkKnown(
+ $nt,
+ $nl,
+ array(),
+ array( 'action' => 'history' )
+ ) . $redirect;
return wfSpecialList( $plink, $nlink );
}
diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php
index 4fde0a60..0ed7020a 100644
--- a/includes/specials/SpecialFileDuplicateSearch.php
+++ b/includes/specials/SpecialFileDuplicateSearch.php
@@ -51,9 +51,12 @@ class FileDuplicateSearchPage extends QueryPage {
$nt = Title::makeTitle( NS_FILE, $result->title );
$text = $wgContLang->convert( $nt->getText() );
- $plink = $skin->makeLink( $nt->getPrefixedText(), $text );
+ $plink = $skin->link(
+ Title::newFromText( $nt->getPrefixedText() ),
+ $text
+ );
- $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text );
+ $user = $skin->link( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text );
$time = $wgLang->timeanddate( $result->img_timestamp );
return "$plink . . $user . . $time";
@@ -73,7 +76,7 @@ function wfSpecialFileDuplicateSearch( $par = null ) {
if( $title && $title->getText() != '' ) {
$dbr = wfGetDB( DB_SLAVE );
$image = $dbr->tableName( 'image' );
- $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDBKey() ) );
+ $encFilename = $dbr->addQuotes( htmlspecialchars( $title->getDBkey() ) );
$sql = "SELECT img_sha1 from $image where img_name = $encFilename";
$res = $dbr->query( $sql );
$row = $dbr->fetchRow( $res );
@@ -96,7 +99,7 @@ function wfSpecialFileDuplicateSearch( $par = null ) {
);
if( $hash != '' ) {
- $align = $wgContLang->isRtl() ? 'left' : 'right';
+ $align = $wgContLang->alignEnd();
# Show a thumbnail of the file
$img = wfFindFile( $title );
@@ -122,14 +125,14 @@ function wfSpecialFileDuplicateSearch( $par = null ) {
# Show a short summary
if( $count == 1 ) {
- $wgOut->addHTML( '<p class="mw-fileduplicatesearch-result-1">' .
- wfMsgHtml( 'fileduplicatesearch-result-1', $filename ) .
- '</p>'
+ $wgOut->wrapWikiMsg(
+ "<p class='mw-fileduplicatesearch-result-1'>\n$1\n</p>",
+ array( 'fileduplicatesearch-result-1', $filename )
);
} elseif ( $count > 1 ) {
- $wgOut->addHTML( '<p class="mw-fileduplicatesearch-result-n">' .
- wfMsgExt( 'fileduplicatesearch-result-n', array( 'parseinline' ), $filename, $wgLang->formatNum( $count - 1 ) ) .
- '</p>'
+ $wgOut->wrapWikiMsg(
+ "<p class='mw-fileduplicatesearch-result-n'>\n$1\n</p>",
+ array( 'fileduplicatesearch-result-n', $filename, $wgLang->formatNum( $count - 1 ) )
);
}
}
diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php
index 4a724b1f..8bc1c68b 100644
--- a/includes/specials/SpecialFilepath.php
+++ b/includes/specials/SpecialFilepath.php
@@ -37,13 +37,13 @@ class FilepathForm {
}
function execute() {
- global $wgOut, $wgTitle, $wgScript;
+ global $wgOut, $wgScript;
$wgOut->addHTML(
Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'filepath' ) ) .
- Xml::hidden( 'title', $wgTitle->getPrefixedText() ) .
+ Xml::hidden( 'title', SpecialPage::getTitleFor( 'Filepath' )->getPrefixedText() ) .
Xml::inputLabel( wfMsg( 'filepath-page' ), 'file', 'file', 25, is_object( $this->mTitle ) ? $this->mTitle->getText() : '' ) . ' ' .
Xml::submitButton( wfMsg( 'filepath-submit' ) ) . "\n" .
Xml::closeElement( 'fieldset' ) .
diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php
index 457e03b4..6beeab7f 100644
--- a/includes/specials/SpecialImport.php
+++ b/includes/specials/SpecialImport.php
@@ -132,11 +132,11 @@ class SpecialImport extends SpecialPage {
}
private function showForm() {
- global $wgUser, $wgOut, $wgRequest, $wgTitle, $wgImportSources, $wgExportMaxLinkDepth;
+ global $wgUser, $wgOut, $wgRequest, $wgImportSources, $wgExportMaxLinkDepth;
if( !$wgUser->isAllowed( 'import' ) && !$wgUser->isAllowed( 'importupload' ) )
return $wgOut->permissionRequired( 'import' );
- $action = $wgTitle->getLocalUrl( 'action=submit' );
+ $action = $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) );
if( $wgUser->isAllowed( 'importupload' ) ) {
$wgOut->addWikiMsg( "importtext" );
@@ -273,7 +273,7 @@ class SpecialImport extends SpecialPage {
* @ingroup SpecialPage
*/
class ImportReporter {
- private $reason=false;
+ private $reason=false;
function __construct( $importer, $upload, $interwiki , $reason=false ) {
$importer->setPageOutCallback( array( $this, 'reportPage' ) );
@@ -299,7 +299,7 @@ class ImportReporter {
$contentCount = $wgContLang->formatNum( $successCount );
if( $successCount > 0 ) {
- $wgOut->addHTML( "<li>" . $skin->makeKnownLinkObj( $title ) . " " .
+ $wgOut->addHTML( "<li>" . $skin->linkKnown( $title ) . " " .
wfMsgExt( 'import-revision-count', array( 'parsemag', 'escape' ), $localCount ) .
"</li>\n"
);
@@ -309,7 +309,7 @@ class ImportReporter {
$detail = wfMsgExt( 'import-logentry-upload-detail', array( 'content', 'parsemag' ),
$contentCount );
if ( $this->reason ) {
- $detail .= wfMsgForContent( 'colon-separator' ) . $this->reason;
+ $detail .= wfMsgForContent( 'colon-separator' ) . $this->reason;
}
$log->addEntry( 'upload', $title, $detail );
} else {
@@ -318,7 +318,7 @@ class ImportReporter {
$detail = wfMsgExt( 'import-logentry-interwiki-detail', array( 'content', 'parsemag' ),
$contentCount, $interwiki );
if ( $this->reason ) {
- $detail .= wfMsgForContent( 'colon-separator' ) . $this->reason;
+ $detail .= wfMsgForContent( 'colon-separator' ) . $this->reason;
}
$log->addEntry( 'interwiki', $title, $detail );
}
@@ -333,7 +333,8 @@ class ImportReporter {
$article->updateRevisionOn( $dbw, $nullRevision );
wfRunHooks( 'NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser) );
} else {
- $wgOut->addHTML( '<li>' . wfMsgHtml( 'import-nonewrevisions' ) . '</li>' );
+ $wgOut->addHTML( "<li>" . $skin->linkKnown( $title ) . " " .
+ wfMsgHtml( 'import-nonewrevisions' ) . "</li>\n" );
}
}
diff --git a/includes/specials/SpecialIpblocklist.php b/includes/specials/SpecialIpblocklist.php
index 4ba1c811..dfdcf1a7 100644
--- a/includes/specials/SpecialIpblocklist.php
+++ b/includes/specials/SpecialIpblocklist.php
@@ -5,12 +5,13 @@
*/
/**
+ * @param $ip part of title: Special:Ipblocklist/<ip>.
* @todo document
*/
-function wfSpecialIpblocklist() {
+function wfSpecialIpblocklist( $ip = '' ) {
global $wgUser, $wgOut, $wgRequest;
-
- $ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $wgRequest->getVal( 'ip' ) ) );
+ $ip = $wgRequest->getVal( 'ip', $ip );
+ $ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $ip ) );
$id = $wgRequest->getVal( 'id' );
$reason = $wgRequest->getText( 'wpUnblockReason' );
$action = $wgRequest->getText( 'action' );
@@ -94,7 +95,7 @@ class IPUnblockForm {
$titleObj = SpecialPage::getTitleFor( "Ipblocklist" );
$action = $titleObj->getLocalURL( "action=submit" );
- if ( "" != $err ) {
+ if ( $err != "" ) {
$wgOut->setSubtitle( wfMsg( "formerror" ) );
$wgOut->addWikiText( Xml::tags( 'span', array( 'class' => 'error' ), $err ) . "\n" );
}
@@ -184,8 +185,7 @@ class IPUnblockForm {
if ( !$block ) {
return array('ipb_cant_unblock', htmlspecialchars($id));
}
- if( $block->mRangeStart != $block->mRangeEnd
- && !strstr( $ip, "/" ) ) {
+ 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;
@@ -221,8 +221,7 @@ class IPUnblockForm {
function doSubmit() {
global $wgOut, $wgUser;
$retval = self::doUnblock($this->id, $this->ip, $this->reason, $range, $wgUser);
- if(!empty($retval))
- {
+ if( !empty($retval) ) {
$key = array_shift($retval);
$this->showForm(wfMsgReal($key, $retval));
return;
@@ -237,7 +236,7 @@ class IPUnblockForm {
global $wgOut, $wgUser;
$wgOut->setPagetitle( wfMsg( "ipblocklist" ) );
- if ( "" != $msg ) {
+ if ( $msg != "" ) {
$wgOut->setSubtitle( $msg );
}
@@ -264,10 +263,9 @@ class IPUnblockForm {
// Fixme -- encapsulate this sort of query-building.
$dbr = wfGetDB( DB_SLAVE );
$encIp = $dbr->addQuotes( IP::sanitizeIP($this->ip) );
- $encRange = $dbr->addQuotes( "$range%" );
$encAddr = $dbr->addQuotes( $iaddr );
$conds[] = "(ipb_address = $encIp) OR
- (ipb_range_start LIKE $encRange AND
+ (ipb_range_start" . $dbr->buildLike( $range, $dbr->anyString() ) . " AND
ipb_range_start <= $encAddr
AND ipb_range_end >= $encAddr)";
} else {
@@ -299,25 +297,48 @@ class IPUnblockForm {
$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(
- $this->searchForm() .
$pager->getNavigationBar() .
Xml::tags( 'ul', null, $pager->getBody() ) .
$pager->getNavigationBar()
);
} elseif ( $this->ip != '') {
- $wgOut->addHTML( $this->searchForm() );
$wgOut->addWikiMsg( 'ipblocklist-no-results' );
} else {
- $wgOut->addHTML( $this->searchForm() );
$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 $wgTitle, $wgScript, $wgRequest, $wgLang;
+ global $wgScript, $wgRequest, $wgLang;
$showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) );
$nondefaults = array();
@@ -345,7 +366,7 @@ class IPUnblockForm {
return
Xml::tags( 'form', array( 'action' => $wgScript ),
- Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) .
+ Xml::hidden( 'title', SpecialPage::getTitleFor( 'Ipblocklist' )->getPrefixedDbKey() ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) .
Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) .
@@ -366,7 +387,7 @@ class IPUnblockForm {
global $wgUser;
$sk = $wgUser->getSkin();
$params = $override + $options;
- $ipblocklist = SpecialPage::getTitleFor( 'IPBlockList' );
+ $ipblocklist = SpecialPage::getTitleFor( 'Ipblocklist' );
return $sk->link( $ipblocklist, htmlspecialchars( $title ),
( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) );
}
@@ -386,11 +407,10 @@ class IPUnblockForm {
if( is_null( $msg ) ) {
$msg = array();
$keys = array( 'infiniteblock', 'expiringblock', 'unblocklink', 'change-blocklink',
- 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock', 'blocklist-nousertalk' );
+ 'anononlyblock', 'createaccountblock', 'noautoblockblock', 'emailblock', 'blocklist-nousertalk', 'blocklistline' );
foreach( $keys as $key ) {
$msg[$key] = wfMsgHtml( $key );
}
- $msg['blocklistline'] = wfMsg( 'blocklistline' );
}
# Prepare links to the blocker's user and talk pages
@@ -407,7 +427,7 @@ class IPUnblockForm {
. $sk->userToolLinks( $block->mUser, $block->mAddress, false, Linker::TOOL_LINKS_NOBLOCK );
}
- $formattedTime = $wgLang->timeanddate( $block->mTimestamp, true );
+ $formattedTime = htmlspecialchars( $wgLang->timeanddate( $block->mTimestamp, true ) );
$properties = array();
$properties[] = Block::formatExpiry( $block->mExpiry );
@@ -445,7 +465,7 @@ class IPUnblockForm {
# Create changeblocklink for all blocks with exception of autoblocks
if( !$block->mAuto ) {
- $changeblocklink = wfMsg( 'pipe-separator' ) .
+ $changeblocklink = wfMsgExt( 'pipe-separator', 'escapenoentities' ) .
$sk->link( SpecialPage::getTitleFor( 'Blockip', $block->mAddress ),
$msg['change-blocklink'],
array(), array(), 'known' );
@@ -453,7 +473,7 @@ class IPUnblockForm {
$toolLinks = "($unblocklink$changeblocklink)";
}
- $comment = $sk->commentBlock( $block->mReason );
+ $comment = $sk->commentBlock( htmlspecialchars($block->mReason) );
$s = "{$line} $comment";
if ( $block->mHideName )
diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php
index 267ef690..5913f4b4 100644
--- a/includes/specials/SpecialLinkSearch.php
+++ b/includes/specials/SpecialLinkSearch.php
@@ -9,9 +9,7 @@
/**
* Special:LinkSearch to search the external-links table.
- * @ingroup SpecialPage
*/
-
function wfSpecialLinkSearch( $par ) {
list( $limit, $offset ) = wfCheckLimits();
@@ -48,7 +46,7 @@ function wfSpecialLinkSearch( $par ) {
$self = Title::makeTitle( NS_SPECIAL, 'Linksearch' );
- $wgOut->addWikiText( wfMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols) . '</nowiki>' ) );
+ $wgOut->addWikiMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols ) . '</nowiki>' );
$s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) .
Xml::hidden( 'title', $self->getPrefixedDbKey() ) .
'<fieldset>' .
@@ -96,11 +94,11 @@ class LinkSearchPage extends QueryPage {
*/
static function mungeQuery( $query , $prot ) {
$field = 'el_index';
- $rv = LinkFilter::makeLike( $query , $prot );
+ $rv = LinkFilter::makeLikeArray( $query , $prot );
if ($rv === false) {
//makeLike 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)) {
- $rv = $prot . rtrim($query, " \t*") . '%';
+ $rv = array( $prot . rtrim($query, " \t*"), $dbr->anyString() );
$field = 'el_to';
}
}
@@ -125,8 +123,8 @@ class LinkSearchPage extends QueryPage {
/* strip everything past first wildcard, so that index-based-only lookup would be done */
list( $munged, $clause ) = self::mungeQuery( $this->mQuery, $this->mProt );
- $stripped = substr($munged,0,strpos($munged,'%')+1);
- $encSearch = $dbr->addQuotes( $stripped );
+ $stripped = LinkFilter::keepOneWildcard( $munged );
+ $like = $dbr->buildLike( $stripped );
$encSQL = '';
if ( isset ($this->mNs) && !$wgMiserMode )
@@ -144,14 +142,14 @@ class LinkSearchPage extends QueryPage {
$externallinks $use_index
WHERE
page_id=el_from
- AND $clause LIKE $encSearch
+ AND $clause $like
$encSQL";
}
function formatResult( $skin, $result ) {
$title = Title::makeTitle( $result->namespace, $result->title );
$url = $result->url;
- $pageLink = $skin->makeKnownLinkObj( $title );
+ $pageLink = $skin->linkKnown( $title );
$urlLink = $skin->makeExternalLink( $url, $url );
return wfMsgHtml( 'linksearch-line', $urlLink, $pageLink );
@@ -164,7 +162,7 @@ class LinkSearchPage extends QueryPage {
global $wgOut;
list( $this->mMungedQuery, $clause ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt );
if( $this->mMungedQuery === false ) {
- $wgOut->addWikiText( wfMsg( 'linksearch-error' ) );
+ $wgOut->addWikiMsg( 'linksearch-error' );
} else {
// For debugging
// Generates invalid xhtml with patterns that contain --
diff --git a/includes/specials/SpecialListUserRestrictions.php b/includes/specials/SpecialListUserRestrictions.php
deleted file mode 100644
index 98e7111f..00000000
--- a/includes/specials/SpecialListUserRestrictions.php
+++ /dev/null
@@ -1,162 +0,0 @@
-<?php
-
-function wfSpecialListUserRestrictions() {
- global $wgOut, $wgRequest;
-
- $wgOut->addWikiMsg( 'listuserrestrictions-intro' );
- $f = new SpecialListUserRestrictionsForm();
- $wgOut->addHTML( $f->getHTML() );
-
- if( !mt_rand( 0, 10 ) )
- UserRestriction::purgeExpired();
- $pager = new UserRestrictionsPager( $f->getConds() );
- if( $pager->getNumRows() )
- $wgOut->addHTML( $pager->getNavigationBar() .
- Xml::tags( 'ul', null, $pager->getBody() ) .
- $pager->getNavigationBar()
- );
- elseif( $f->getConds() )
- $wgOut->addWikiMsg( 'listuserrestrictions-notfound' );
- else
- $wgOut->addWikiMsg( 'listuserrestrictions-empty' );
-}
-
-class SpecialListUserRestrictionsForm {
- public function getHTML() {
- global $wgRequest, $wgScript, $wgTitle;
- $action = htmlspecialchars( $wgScript );
- $s = '';
- $s .= Xml::fieldset( wfMsg( 'listuserrestrictions-legend' ) );
- $s .= "<form action=\"{$action}\">";
- $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() );
- $s .= Xml::label( wfMsgHtml( 'listuserrestrictions-type' ), 'type' ) . '&nbsp;' .
- self::typeSelector( 'type', $wgRequest->getVal( 'type' ), 'type' );
- $s .= '&nbsp;';
- $s .= Xml::inputLabel( wfMsgHtml( 'listuserrestrictions-user' ), 'user', 'user',
- false, $wgRequest->getVal( 'user' ) );
- $s .= '<p>';
- $s .= Xml::label( wfMsgHtml( 'listuserrestrictions-namespace' ), 'namespace' ) . '&nbsp;' .
- Xml::namespaceSelector( $wgRequest->getVal( 'namespace' ), '', 'namespace' );
- $s .= '&nbsp;';
- $s .= Xml::inputLabel( wfMsgHtml( 'listuserrestrictions-page' ), 'page', 'page',
- false, $wgRequest->getVal( 'page' ) );
- $s .= Xml::submitButton( wfMsg( 'listuserrestrictions-submit' ) );
- $s .= "</p></form></fieldset>";
- return $s;
- }
-
- public static function typeSelector( $name = 'type', $value = '', $id = false ) {
- $s = new XmlSelect( $name, $id, $value );
- $s->addOption( wfMsg( 'userrestrictiontype-none' ), '' );
- $s->addOption( wfMsg( 'userrestrictiontype-page' ), UserRestriction::PAGE );
- $s->addOption( wfMsg( 'userrestrictiontype-namespace' ), UserRestriction::NAMESPACE );
- return $s->getHTML();
- }
-
- public function getConds() {
- global $wgRequest;
- $conds = array();
-
- $type = $wgRequest->getVal( 'type' );
- if( in_array( $type, array( UserRestriction::PAGE, UserRestriction::NAMESPACE ) ) )
- $conds['ur_type'] = $type;
-
- $user = $wgRequest->getVal( 'user' );
- if( $user )
- $conds['ur_user_text'] = $user;
-
- $namespace = $wgRequest->getVal( 'namespace' );
- if( $namespace || $namespace === '0' )
- $conds['ur_namespace'] = $namespace;
-
- $page = $wgRequest->getVal( 'page' );
- $title = Title::newFromText( $page );
- if( $title ) {
- $conds['ur_page_namespace'] = $title->getNamespace();
- $conds['ur_page_title'] = $title->getDBKey();
- }
-
- return $conds;
- }
-}
-
-class UserRestrictionsPager extends ReverseChronologicalPager {
- public $mConds;
-
- public function __construct( $conds = array() ) {
- $this->mConds = $conds;
- parent::__construct();
- }
-
- public function getStartBody() {
- # Copied from Special:Ipblocklist
- wfProfileIn( __METHOD__ );
- # Do a link batch query
- $this->mResult->seek( 0 );
- $lb = new LinkBatch;
-
- # 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->ur_by_text );
- $lb->add( NS_USER, $name );
- $lb->add( NS_USER_TALK, $name );
- $name = str_replace( ' ', '_', $row->ur_user_text );
- $lb->add( NS_USER, $name );
- $lb->add( NS_USER_TALK, $name );
- if( $row->ur_type == UserRestriction::PAGE )
- $lb->add( $row->ur_page_namespace, $row->ur_page_title );
- }
- $lb->execute();
- wfProfileOut( __METHOD__ );
- return '';
- }
-
- public function getQueryInfo() {
- return array(
- 'tables' => 'user_restrictions',
- 'fields' => '*',
- 'conds' => $this->mConds,
- );
- }
-
- public function formatRow( $row ) {
- return self::formatRestriction( UserRestriction::newFromRow( $row ) );
- }
-
- // Split off for use on Special:RestrictUser
- public static function formatRestriction( $r ) {
- global $wgUser, $wgLang;
- $sk = $wgUser->getSkin();
- $timestamp = $wgLang->timeanddate( $r->getTimestamp(), true );
- $blockerlink = $sk->userLink( $r->getBlockerId(), $r->getBlockerText() ) .
- $sk->userToolLinks( $r->getBlockerId(), $r->getBlockerText() );
- $subjlink = $sk->userLink( $r->getSubjectId(), $r->getSubjectText() ) .
- $sk->userToolLinks( $r->getSubjectId(), $r->getSubjectText() );
- $expiry = is_numeric( $r->getExpiry() ) ?
- wfMsg( 'listuserrestrictions-row-expiry', $wgLang->timeanddate( $r->getExpiry() ) ) :
- wfMsg( 'ipbinfinite' );
- $msg = '';
- if( $r->isNamespace() ) {
- $msg = wfMsgHtml( 'listuserrestrictions-row-ns', $subjlink,
- $wgLang->getDisplayNsText( $r->getNamespace() ), $expiry );
- }
- if( $r->isPage() ) {
- $pagelink = $sk->link( $r->getPage() );
- $msg = wfMsgHtml( 'listuserrestrictions-row-page', $subjlink,
- $pagelink, $expiry );
- }
- $reason = $sk->commentBlock( $r->getReason() );
- $removelink = '';
- if( $wgUser->isAllowed( 'restrict' ) ) {
- $removelink = '(' . $sk->link( SpecialPage::getTitleFor( 'RemoveRestrictions' ),
- wfMsgHtml( 'listuserrestrictions-remove' ), array(), array( 'id' => $r->getId() ) ) . ')';
- }
- return "<li>{$timestamp}, {$blockerlink} {$msg} {$reason} {$removelink}</li>\n";
- }
-
- public function getIndexField() {
- return 'ur_timestamp';
- }
-}
diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php
index e15b6959..b9332422 100644
--- a/includes/specials/SpecialListfiles.php
+++ b/includes/specials/SpecialListfiles.php
@@ -34,13 +34,11 @@ class ImageListPager extends TablePager {
}
$search = $wgRequest->getText( 'ilsearch' );
if ( $search != '' && !$wgMiserMode ) {
- $nt = Title::newFromUrl( $search );
+ $nt = Title::newFromURL( $search );
if( $nt ) {
$dbr = wfGetDB( DB_SLAVE );
- $m = $dbr->strencode( strtolower( $nt->getDBkey() ) );
- $m = str_replace( "%", "\\%", $m );
- $m = str_replace( "_", "\\_", $m );
- $this->mQueryConds = array( "LOWER(img_name) LIKE '%{$m}%'" );
+ $this->mQueryConds = array( 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(),
+ strtolower( $nt->getDBkey() ), $dbr->anyString() ) );
}
}
@@ -127,21 +125,23 @@ class ImageListPager extends TablePager {
global $wgLang;
switch ( $field ) {
case 'img_timestamp':
- return $wgLang->timeanddate( $value, true );
+ return htmlspecialchars( $wgLang->timeanddate( $value, true ) );
case 'img_name':
static $imgfile = null;
if ( $imgfile === null ) $imgfile = wfMsg( 'imgfile' );
$name = $this->mCurrentRow->img_name;
- $link = $this->getSkin()->makeKnownLinkObj( Title::makeTitle( NS_FILE, $name ), $value );
+ $link = $this->getSkin()->linkKnown( Title::makeTitle( NS_FILE, $name ), $value );
$image = wfLocalFile( $value );
$url = $image->getURL();
$download = Xml::element('a', array( 'href' => $url ), $imgfile );
return "$link ($download)";
case 'img_user_text':
if ( $this->mCurrentRow->img_user ) {
- $link = $this->getSkin()->makeLinkObj( Title::makeTitle( NS_USER, $value ),
- htmlspecialchars( $value ) );
+ $link = $this->getSkin()->link(
+ Title::makeTitle( NS_USER, $value ),
+ htmlspecialchars( $value )
+ );
} else {
$link = htmlspecialchars( $value );
}
@@ -156,10 +156,10 @@ class ImageListPager extends TablePager {
}
function getForm() {
- global $wgRequest, $wgMiserMode;
+ global $wgRequest, $wgScript, $wgMiserMode;
$search = $wgRequest->getText( 'ilsearch' );
- $s = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $this->getTitle()->getLocalURL(), 'id' => 'mw-listfiles-form' ) ) .
+ $s = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', null, wfMsg( 'listfiles' ) ) .
Xml::tags( 'label', null, wfMsgHtml( 'table_pager_limit', $this->getLimitSelect() ) );
diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php
index d1fc0818..83724a4f 100644
--- a/includes/specials/SpecialListgrouprights.php
+++ b/includes/specials/SpecialListgrouprights.php
@@ -25,14 +25,15 @@ class SpecialListGroupRights extends SpecialPage {
*/
public function execute( $par ) {
global $wgOut, $wgImplicitGroups, $wgMessageCache;
- global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups;
+ global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups;
+ global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
$wgMessageCache->loadAllMessages();
$this->setHeaders();
$this->outputHeader();
$wgOut->addHTML(
- Xml::openElement( 'table', array( 'class' => 'mw-listgrouprights-table' ) ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable mw-listgrouprights-table' ) ) .
'<tr>' .
Xml::element( 'th', null, wfMsg( 'listgrouprights-group' ) ) .
Xml::element( 'th', null, wfMsg( 'listgrouprights-rights' ) ) .
@@ -40,7 +41,7 @@ class SpecialListGroupRights extends SpecialPage {
);
foreach( $wgGroupPermissions as $group => $permissions ) {
- $groupname = ( $group == '*' ) ? 'all' : htmlspecialchars( $group ); // Replace * with a more descriptive groupname
+ $groupname = ( $group == '*' ) ? 'all' : $group; // Replace * with a more descriptive groupname
$msg = wfMsg( 'group-' . $groupname );
if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) {
@@ -58,23 +59,41 @@ class SpecialListGroupRights extends SpecialPage {
if( $group == '*' ) {
// Do not make a link for the generic * group
- $grouppage = $groupnameLocalized;
+ $grouppage = htmlspecialchars($groupnameLocalized);
} else {
- $grouppage = $this->skin->makeLink( $grouppageLocalized, $groupnameLocalized );
+ $grouppage = $this->skin->link(
+ Title::newFromText( $grouppageLocalized ),
+ htmlspecialchars($groupnameLocalized)
+ );
}
if ( $group === 'user' ) {
// Link to Special:listusers for implicit group 'user'
- $grouplink = '<br />' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), '' );
+ $grouplink = '<br />' . $this->skin->link(
+ SpecialPage::getTitleFor( 'Listusers' ),
+ wfMsgHtml( 'listgrouprights-members' ),
+ array(),
+ array(),
+ array( 'known', 'noclasses' )
+ );
} elseif ( !in_array( $group, $wgImplicitGroups ) ) {
- $grouplink = '<br />' . $this->skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Listusers' ), wfMsgHtml( 'listgrouprights-members' ), 'group=' . $group );
+ $grouplink = '<br />' . $this->skin->link(
+ SpecialPage::getTitleFor( 'Listusers' ),
+ wfMsgHtml( 'listgrouprights-members' ),
+ array(),
+ array( 'group' => $group ),
+ array( 'known', 'noclasses' )
+ );
} else {
// No link to Special:listusers for other implicit groups as they are unlistable
$grouplink = '';
}
+ $revoke = isset( $wgRevokePermissions[$group] ) ? $wgRevokePermissions[$group] : array();
$addgroups = isset( $wgAddGroups[$group] ) ? $wgAddGroups[$group] : array();
$removegroups = isset( $wgRemoveGroups[$group] ) ? $wgRemoveGroups[$group] : array();
+ $addgroupsSelf = isset( $wgGroupsAddToSelf[$group] ) ? $wgGroupsAddToSelf[$group] : array();
+ $removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array();
$wgOut->addHTML(
'<tr>
@@ -82,30 +101,47 @@ class SpecialListGroupRights extends SpecialPage {
$grouppage . $grouplink .
'</td>
<td>' .
- self::formatPermissions( $permissions, $addgroups, $removegroups ) .
+ self::formatPermissions( $permissions, $revoke, $addgroups, $removegroups, $addgroupsSelf, $removegroupsSelf ) .
'</td>
</tr>'
);
}
$wgOut->addHTML(
- Xml::closeElement( 'table' ) . "\n"
+ Xml::closeElement( 'table' ) . "\n<br /><hr />\n"
);
+ $wgOut->wrapWikiMsg( "<div class=\"mw-listgrouprights-key\">\n$1\n</div>", 'listgrouprights-key' );
}
/**
* Create a user-readable list of permissions from the given array.
*
* @param $permissions Array of permission => bool (from $wgGroupPermissions items)
+ * @param $revoke Array of permission => bool (from $wgRevokePermissions items)
+ * @param $add Array of groups this group is allowed to add or true
+ * @param $remove Array of groups this group is allowed to remove or true
+ * @param $addSelf Array of groups this group is allowed to add to self or true
+ * @param $removeSelf Array of group this group is allowed to remove from self or true
* @return string List of all granted permissions, separated by comma separator
*/
- private static function formatPermissions( $permissions, $add, $remove ) {
+ private static function formatPermissions( $permissions, $revoke, $add, $remove, $addSelf, $removeSelf ) {
global $wgLang;
+
$r = array();
foreach( $permissions as $permission => $granted ) {
- if ( $granted ) {
+ //show as granted only if it isn't revoked to prevent duplicate display of permissions
+ if( $granted && ( !isset( $revoke[$permission] ) || !$revoke[$permission] ) ) {
$description = wfMsgExt( 'listgrouprights-right-display', array( 'parseinline' ),
User::getRightDescription( $permission ),
- $permission
+ '<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
+ );
+ $r[] = $description;
+ }
+ }
+ foreach( $revoke as $permission => $revoked ) {
+ if( $revoked ) {
+ $description = wfMsgExt( 'listgrouprights-right-revoked', array( 'parseinline' ),
+ User::getRightDescription( $permission ),
+ '<span class="mw-listgrouprights-right-name">' . $permission . '</span>'
);
$r[] = $description;
}
@@ -114,13 +150,27 @@ class SpecialListGroupRights extends SpecialPage {
if( $add === true ){
$r[] = wfMsgExt( 'listgrouprights-addgroup-all', array( 'escape' ) );
} else if( 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 ) ) {
+ $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 ) ) {
+ $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 ) ) {
+ $removeSelf = array_values( array_unique( $removeSelf ) );
+ $r[] = wfMsgExt( 'listgrouprights-removegroup-self', array( 'parseinline' ), $wgLang->listToText( array_map( array( 'User', 'makeGroupLinkWiki' ), $removeSelf ) ), count( $removeSelf ) );
+ }
if( empty( $r ) ) {
return '';
} else {
diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php
index 9555bd16..bf594070 100644
--- a/includes/specials/SpecialListredirects.php
+++ b/includes/specials/SpecialListredirects.php
@@ -32,7 +32,12 @@ class ListredirectsPage extends QueryPage {
# Make a link to the redirect itself
$rd_title = Title::makeTitle( $result->namespace, $result->title );
- $rd_link = $skin->makeLinkObj( $rd_title, '', 'redirect=no' );
+ $rd_link = $skin->link(
+ $rd_title,
+ null,
+ array(),
+ array( 'redirect' => 'no' )
+ );
# Find out where the redirect leads
$revision = Revision::newFromTitle( $rd_title );
@@ -41,7 +46,7 @@ class ListredirectsPage extends QueryPage {
$target = Title::newFromRedirect( $revision->getText() );
if( $target ) {
$arr = $wgContLang->getArrow() . $wgContLang->getDirMark();
- $targetLink = $skin->makeLinkObj( $target );
+ $targetLink = $skin->link( $target );
return "$rd_link $arr $targetLink";
} else {
return "<s>$rd_link</s>";
diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php
index aa057801..bdb59980 100644
--- a/includes/specials/SpecialListusers.php
+++ b/includes/specials/SpecialListusers.php
@@ -71,10 +71,12 @@ class UsersPager extends AlphabeticPager {
}
function getQueryInfo() {
+ global $wgUser;
$dbr = wfGetDB( DB_SLAVE );
$conds = array();
// Don't show hidden names
- $conds[] = 'ipb_deleted IS NULL OR ipb_deleted = 0';
+ if( !$wgUser->isAllowed('hideuser') )
+ $conds[] = 'ipb_deleted IS NULL';
if( $this->requestedGroup != '' ) {
$conds['ug_group'] = $this->requestedGroup;
$useIndex = '';
@@ -84,7 +86,7 @@ class UsersPager extends AlphabeticPager {
if( $this->requestedUser != '' ) {
# Sorted either by account creation or name
if( $this->creationSort ) {
- $conds[] = 'user_id >= ' . User::idFromName( $this->requestedUser );
+ $conds[] = 'user_id >= ' . intval( User::idFromName( $this->requestedUser ) );
} else {
$conds[] = 'user_name >= ' . $dbr->addQuotes( $this->requestedUser );
}
@@ -97,14 +99,16 @@ class UsersPager extends AlphabeticPager {
$query = array(
'tables' => " $user $useIndex LEFT JOIN $user_groups ON user_id=ug_user
- LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_auto=0 ",
+ LEFT JOIN $ipblocks ON user_id=ipb_user AND ipb_deleted=1 AND ipb_auto=0 ",
'fields' => array(
$this->creationSort ? 'MAX(user_name) AS user_name' : 'user_name',
$this->creationSort ? 'user_id' : 'MAX(user_id) AS user_id',
'MAX(user_editcount) AS edits',
'COUNT(ug_group) AS numgroups',
- 'MAX(ug_group) AS singlegroup',
- 'MIN(user_registration) AS creation'),
+ 'MAX(ug_group) AS singlegroup', // the usergroup if there is only one
+ 'MIN(user_registration) AS creation',
+ 'MAX(ipb_deleted) AS ipb_deleted' // block/hide status
+ ),
'options' => array('GROUP BY' => $this->creationSort ? 'user_id' : 'user_name'),
'conds' => $conds
);
@@ -117,7 +121,7 @@ class UsersPager extends AlphabeticPager {
global $wgLang;
$userPage = Title::makeTitle( NS_USER, $row->user_name );
- $name = $this->getSkin()->makeLinkObj( $userPage, htmlspecialchars( $userPage->getText() ) );
+ $name = $this->getSkin()->link( $userPage, htmlspecialchars( $userPage->getText() ) );
if( $row->numgroups > 1 || ( $this->requestedGroup && $row->numgroups == 1 ) ) {
$list = array();
@@ -131,11 +135,14 @@ class UsersPager extends AlphabeticPager {
}
$item = wfSpecialList( $name, $groups );
+ if( $row->ipb_deleted ) {
+ $item = "<span class=\"deleted\">$item</span>";
+ }
global $wgEdititis;
if ( $wgEdititis ) {
$editCount = $wgLang->formatNum( $row->edits );
- $edits = ' [' . wfMsgExt( 'usereditcount', 'parsemag', $editCount ) . ']';
+ $edits = ' [' . wfMsgExt( 'usereditcount', array( 'parsemag', 'escape' ), $editCount ) . ']';
} else {
$edits = '';
}
@@ -145,7 +152,8 @@ class UsersPager extends AlphabeticPager {
if( $row->creation ) {
$d = $wgLang->date( wfTimestamp( TS_MW, $row->creation ), true );
$t = $wgLang->time( wfTimestamp( TS_MW, $row->creation ), true );
- $created = ' (' . wfMsgHtml( 'usercreated', $d, $t ) . ')';
+ $created = ' (' . wfMsg( 'usercreated', $d, $t ) . ')';
+ $created = htmlspecialchars( $created );
}
wfRunHooks( 'SpecialListusersFormatRow', array( &$item, $row ) );
@@ -185,11 +193,11 @@ class UsersPager extends AlphabeticPager {
Xml::option( wfMsg( 'group-all' ), '' );
foreach( $this->getAllGroups() as $group => $groupText )
$out .= Xml::option( $groupText, $group, $group == $this->requestedGroup );
- $out .= Xml::closeElement( 'select' ) . '<br/>';
+ $out .= Xml::closeElement( 'select' ) . '<br />';
$out .= Xml::checkLabel( wfMsg('listusers-editsonly'), 'editsOnly', 'editsOnly', $this->editsOnly );
$out .= '&nbsp;';
$out .= Xml::checkLabel( wfMsg('listusers-creationsort'), 'creationSort', 'creationSort', $this->creationSort );
- $out .= '<br/>';
+ $out .= '<br />';
wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) );
@@ -233,7 +241,7 @@ class UsersPager extends AlphabeticPager {
/**
* Get a list of groups the specified user belongs to
*
- * @param int $uid
+ * @param $uid Integer: user id
* @return array
*/
protected static function getGroups( $uid ) {
@@ -245,13 +253,13 @@ class UsersPager extends AlphabeticPager {
/**
* Format a link to a group description page
*
- * @param string $group
+ * @param $group String: group name
* @return string
*/
protected static function buildGroupLink( $group ) {
static $cache = array();
if( !isset( $cache[$group] ) )
- $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupMember( $group ) );
+ $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupMember( $group ) ) );
return $cache[$group];
}
}
diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php
index 5859d5b2..8c701dd6 100644
--- a/includes/specials/SpecialLockdb.php
+++ b/includes/specials/SpecialLockdb.php
@@ -53,7 +53,7 @@ class DBLockForm {
$wgOut->setPagetitle( wfMsg( 'lockdb' ) );
$wgOut->addWikiMsg( 'lockdbtext' );
- if ( "" != $err ) {
+ if ( $err != "" ) {
$wgOut->setSubtitle( wfMsg( 'formerror' ) );
$wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" );
}
@@ -65,7 +65,7 @@ class DBLockForm {
$reason = htmlspecialchars( $this->reason );
$token = htmlspecialchars( $wgUser->editToken() );
- $wgOut->addHTML( <<<END
+ $wgOut->addHTML( <<<HTML
<form id="lockdb" method="post" action="{$action}">
{$elr}:
<textarea name="wpLockReason" rows="10" cols="60" wrap="virtual">{$reason}</textarea>
@@ -85,7 +85,7 @@ class DBLockForm {
</table>
<input type="hidden" name="wpEditToken" value="{$token}" />
</form>
-END
+HTML
);
}
diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php
index 2382344b..d1ccc8c4 100644
--- a/includes/specials/SpecialLog.php
+++ b/includes/specials/SpecialLog.php
@@ -52,9 +52,19 @@ function wfSpecialLog( $par = '' ) {
$y = '';
$m = '';
}
+ # Handle type-specific inputs
+ $qc = array();
+ if( $type == 'suppress' ) {
+ $offender = User::newFromName( $wgRequest->getVal('offender'), false );
+ if( $offender && $offender->getId() > 0 ) {
+ $qc = array( 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() );
+ } else if( $offender && IP::isIPAddress( $offender->getName() ) ) {
+ $qc = array( 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() );
+ }
+ }
# Create a LogPager item to get the results and a LogEventsList item to format them...
$loglist = new LogEventsList( $wgUser->getSkin(), $wgOut, 0 );
- $pager = new LogPager( $loglist, $type, $user, $title, $pattern, array(), $y, $m, $tagFilter );
+ $pager = new LogPager( $loglist, $type, $user, $title, $pattern, $qc, $y, $m, $tagFilter );
# Set title and add header
$loglist->showHeader( $pager->getType() );
# Show form options
diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php
index cdfde24e..dafe003e 100644
--- a/includes/specials/SpecialMIMEsearch.php
+++ b/includes/specials/SpecialMIMEsearch.php
@@ -65,15 +65,20 @@ class MIMEsearchPage extends QueryPage {
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = $wgContLang->convert( $nt->getText() );
- $plink = $skin->makeLink( $nt->getPrefixedText(), $text );
+ $plink = $skin->link(
+ Title::newFromText( $nt->getPrefixedText() ),
+ htmlspecialchars( $text )
+ );
$download = $skin->makeMediaLinkObj( $nt, wfMsgHtml( 'download' ) );
$bytes = wfMsgExt( 'nbytes', array( 'parsemag', 'escape'),
$wgLang->formatNum( $result->img_size ) );
- $dimensions = wfMsgHtml( 'widthheight', $wgLang->formatNum( $result->img_width ),
- $wgLang->formatNum( $result->img_height ) );
- $user = $skin->makeLinkObj( Title::makeTitle( NS_USER, $result->img_user_text ), $result->img_user_text );
- $time = $wgLang->timeanddate( $result->img_timestamp );
+ $dimensions = htmlspecialchars( wfMsg( 'widthheight',
+ $wgLang->formatNum( $result->img_width ),
+ $wgLang->formatNum( $result->img_height )
+ ) );
+ $user = $skin->link( Title::makeTitle( NS_USER, $result->img_user_text ), htmlspecialchars( $result->img_user_text ) );
+ $time = htmlspecialchars( $wgLang->timeanddate( $result->img_timestamp ) );
return "($download) $plink . . $dimensions . . $bytes . . $user . . $time";
}
@@ -83,13 +88,14 @@ class MIMEsearchPage extends QueryPage {
* Output the HTML search form, and constructs the MIMEsearchPage object.
*/
function wfSpecialMIMEsearch( $par = null ) {
- global $wgRequest, $wgTitle, $wgOut;
+ global $wgRequest, $wgOut;
$mime = isset( $par ) ? $par : $wgRequest->getText( 'mime' );
$wgOut->addHTML(
- Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => $wgTitle->getLocalUrl() ) ) .
+ Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) .
Xml::openElement( 'fieldset' ) .
+ Xml::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) .
Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) .
Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' .
Xml::submitButton( wfMsg( 'ilsubmit' ) ) .
diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php
index c51ce7c3..1b4ef30c 100644
--- a/includes/specials/SpecialMergeHistory.php
+++ b/includes/specials/SpecialMergeHistory.php
@@ -67,7 +67,7 @@ class MergehistoryForm {
}
function execute() {
- global $wgOut, $wgUser;
+ global $wgOut;
$wgOut->setPagetitle( wfMsgHtml( "mergehistory" ) );
@@ -155,7 +155,7 @@ class MergehistoryForm {
$haveRevisions = $revisions && $revisions->getNumRows() > 0;
$titleObj = SpecialPage::getTitleFor( "Mergehistory" );
- $action = $titleObj->getLocalURL( "action=submit" );
+ $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) );
$wgOut->addHTML( $top );
@@ -218,7 +218,7 @@ class MergehistoryForm {
}
function formatRevisionRow( $row ) {
- global $wgUser, $wgLang;
+ global $wgLang;
$rev = new Revision( $row );
@@ -228,8 +228,12 @@ class MergehistoryForm {
$ts = wfTimestamp( TS_MW, $row->rev_timestamp );
$checkBox = Xml::radio( "mergepoint", $ts, false );
- $pageLink = $this->sk->makeKnownLinkObj( $rev->getTitle(),
- htmlspecialchars( $wgLang->timeanddate( $ts ) ), 'oldid=' . $rev->getId() );
+ $pageLink = $this->sk->linkKnown(
+ $rev->getTitle(),
+ htmlspecialchars( $wgLang->timeanddate( $ts ) ),
+ array(),
+ array( 'oldid' => $rev->getId() )
+ );
if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
$pageLink = '<span class="history-deleted">' . $pageLink . '</span>';
}
@@ -238,8 +242,15 @@ class MergehistoryForm {
if( !$rev->userCan( Revision::DELETED_TEXT ) )
$last = $this->message['last'];
else if( isset($this->prevId[$row->rev_id]) )
- $last = $this->sk->makeKnownLinkObj( $rev->getTitle(), $this->message['last'],
- "diff=" . $row->rev_id . "&oldid=" . $this->prevId[$row->rev_id] );
+ $last = $this->sk->linkKnown(
+ $rev->getTitle(),
+ $this->message['last'],
+ array(),
+ array(
+ 'diff' => $row->rev_id,
+ 'oldid' => $this->prevId[$row->rev_id]
+ )
+ );
$userLink = $this->sk->revUserTools( $rev );
@@ -261,8 +272,15 @@ class MergehistoryForm {
if( !$this->userCan($row, Revision::DELETED_TEXT) ) {
return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
} else {
- $link = $this->sk->makeKnownLinkObj( $titleObj,
- $wgLang->timeanddate( $ts, true ), "target=$target&timestamp=$ts" );
+ $link = $this->sk->linkKnown(
+ $titleObj,
+ $wgLang->timeanddate( $ts, true ),
+ array(),
+ array(
+ 'target' => $target,
+ 'timestamp' => $ts
+ )
+ );
if( $this->isDeleted($row, Revision::DELETED_TEXT) )
$link = '<span class="history-deleted">' . $link . '</span>';
return $link;
@@ -270,7 +288,7 @@ class MergehistoryForm {
}
function merge() {
- global $wgOut, $wgUser;
+ global $wgOut;
# Get the titles directly from the IDs, in case the target page params
# were spoofed. The queries are done based on the IDs, so it's best to
# keep it consistent...
diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php
index 078489bd..f112ae17 100644
--- a/includes/specials/SpecialMostlinked.php
+++ b/includes/specials/SpecialMostlinked.php
@@ -22,22 +22,35 @@ class MostlinkedPage extends QueryPage {
function isExpensive() { return true; }
function isSyndicated() { return false; }
- /**
- * Note: Getting page_namespace only works if $this->isCached() is 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,
- page_namespace
+ COUNT(*) AS value
FROM $pagelinks
LEFT JOIN $page ON pl_namespace=page_namespace AND pl_title=page_title
- GROUP BY pl_namespace, pl_title, page_namespace
- HAVING COUNT(*) > 1";
+ GROUP BY pl_namespace, pl_title
+ HAVING COUNT(*) > $cutoff";
}
/**
@@ -57,12 +70,13 @@ class MostlinkedPage extends QueryPage {
* Make a link to "what links here" for the specified title
*
* @param $title Title being queried
+ * @param $caption String: text to display on the link
* @param $skin Skin to use
- * @return string
+ * @return String
*/
function makeWlhLink( &$title, $caption, &$skin ) {
$wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedDBkey() );
- return $skin->makeKnownLinkObj( $wlh, $caption );
+ return $skin->linkKnown( $wlh, $caption );
}
/**
@@ -75,7 +89,10 @@ class MostlinkedPage extends QueryPage {
function formatResult( $skin, $result ) {
global $wgLang;
$title = Title::makeTitleSafe( $result->namespace, $result->title );
- $link = $skin->makeLinkObj( $title );
+ if ( !$title ) {
+ return '<!-- ' . htmlspecialchars( "Invalid title: [[$title]]" ) . ' -->';
+ }
+ $link = $skin->link( $title );
$wlh = $this->makeWlhLink( $title,
wfMsgExt( 'nlinks', array( 'parsemag', 'escape'),
$wgLang->formatNum( $result->value ) ), $skin );
diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php
index ab250675..20a35c97 100644
--- a/includes/specials/SpecialMostlinkedcategories.php
+++ b/includes/specials/SpecialMostlinkedcategories.php
@@ -58,7 +58,7 @@ class MostlinkedCategoriesPage extends QueryPage {
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = $wgContLang->convert( $nt->getText() );
- $plink = $skin->makeLinkObj( $nt, htmlspecialchars( $text ) );
+ $plink = $skin->link( $nt, htmlspecialchars( $text ) );
$nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'),
$wgLang->formatNum( $result->value ) );
diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php
index 2d398a38..71a6b539 100644
--- a/includes/specials/SpecialMostlinkedtemplates.php
+++ b/includes/specials/SpecialMostlinkedtemplates.php
@@ -16,7 +16,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Name of the report
*
- * @return string
+ * @return String
*/
public function getName() {
return 'Mostlinkedtemplates';
@@ -25,7 +25,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Is this report expensive, i.e should it be cached?
*
- * @return bool
+ * @return Boolean
*/
public function isExpensive() {
return true;
@@ -34,7 +34,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Is there a feed available?
*
- * @return bool
+ * @return Boolean
*/
public function isSyndicated() {
return false;
@@ -43,7 +43,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Sort the results in descending order?
*
- * @return bool
+ * @return Boolean
*/
public function sortDescending() {
return true;
@@ -52,7 +52,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Generate SQL for the report
*
- * @return string
+ * @return String
*/
public function getSql() {
$dbr = wfGetDB( DB_SLAVE );
@@ -70,8 +70,8 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Pre-cache page existence to speed up link generation
*
- * @param Database $dbr Database connection
- * @param int $res Result pointer
+ * @param $db Database connection
+ * @param $res ResultWrapper
*/
public function preprocessResults( $db, $res ) {
$batch = new LinkBatch();
@@ -86,16 +86,15 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Format a result row
*
- * @param Skin $skin Skin to use for UI elements
- * @param object $result Result row
- * @return string
+ * @param $skin Skin to use for UI elements
+ * @param $result Result row
+ * @return String
*/
public function formatResult( $skin, $result ) {
$title = Title::makeTitleSafe( $result->namespace, $result->title );
- $skin->link( $title );
return wfSpecialList(
- $skin->makeLinkObj( $title ),
+ $skin->link( $title ),
$this->makeWlhLink( $title, $skin, $result )
);
}
@@ -103,10 +102,10 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Make a "what links here" link for a given title
*
- * @param Title $title Title to make the link for
- * @param Skin $skin Skin to use
- * @param object $result Result row
- * @return string
+ * @param $title Title to make the link for
+ * @param $skin Skin to use
+ * @param $result Result row
+ * @return String
*/
private function makeWlhLink( $title, $skin, $result ) {
global $wgLang;
@@ -120,7 +119,7 @@ class SpecialMostlinkedtemplates extends QueryPage {
/**
* Execution function
*
- * @param mixed $par Parameters passed to the page
+ * @param $par Mixed: parameters passed to the page
*/
function wfSpecialMostlinkedtemplates( $par = false ) {
list( $limit, $offset ) = wfCheckLimits();
diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php
index f5a0f8c0..414e8d97 100644
--- a/includes/specials/SpecialMostrevisions.php
+++ b/includes/specials/SpecialMostrevisions.php
@@ -42,11 +42,16 @@ class MostrevisionsPage extends QueryPage {
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = $wgContLang->convert( $nt->getPrefixedText() );
- $plink = $skin->makeKnownLinkObj( $nt, $text );
+ $plink = $skin->linkKnown( $nt, $text );
$nl = wfMsgExt( 'nrevisions', array( 'parsemag', 'escape'),
$wgLang->formatNum( $result->value ) );
- $nlink = $skin->makeKnownLinkObj( $nt, $nl, 'action=history' );
+ $nlink = $skin->linkKnown(
+ $nt,
+ $nl,
+ array(),
+ array( 'action' => 'history' )
+ );
return wfSpecialList($plink, $nlink);
}
diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php
index 8fcf33a9..02197b19 100644
--- a/includes/specials/SpecialMovepage.php
+++ b/includes/specials/SpecialMovepage.php
@@ -17,7 +17,9 @@ function wfSpecialMovepage( $par = null ) {
}
$target = isset( $par ) ? $par : $wgRequest->getVal( 'target' );
- $oldTitleText = $wgRequest->getText( 'wpOldTitle', $target );
+
+ // Yes, the use of getVal() and getText() is wanted, see bug 20365
+ $oldTitleText = $wgRequest->getVal( 'wpOldTitle', $target );
$newTitleText = $wgRequest->getText( 'wpNewTitle' );
$oldTitle = Title::newFromText( $oldTitleText );
@@ -56,12 +58,12 @@ function wfSpecialMovepage( $par = null ) {
class MovePageForm {
var $oldTitle, $newTitle; # Objects
var $reason; # Text input
- var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect; # Checks
+ var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks
private $watch = false;
function __construct( $oldTitle, $newTitle ) {
- global $wgRequest;
+ global $wgRequest, $wgUser;
$target = isset($par) ? $par : $wgRequest->getVal( 'target' );
$this->oldTitle = $oldTitle;
$this->newTitle = $newTitle;
@@ -77,7 +79,8 @@ class MovePageForm {
}
$this->moveSubpages = $wgRequest->getBool( 'wpMovesubpages', false );
$this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' );
- $this->watch = $wgRequest->getCheck( 'wpWatch' );
+ $this->moveOverShared = $wgRequest->getBool( 'wpMoveOverSharedFile', false );
+ $this->watch = $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn();
}
/**
@@ -87,11 +90,11 @@ class MovePageForm {
* OutputPage::wrapWikiMsg().
*/
function showForm( $err ) {
- global $wgOut, $wgUser, $wgFixDoubleRedirects;
+ global $wgOut, $wgUser, $wgContLang, $wgFixDoubleRedirects;
$skin = $wgUser->getSkin();
- $oldTitleLink = $skin->makeLinkObj( $this->oldTitle );
+ $oldTitleLink = $skin->link( $this->oldTitle );
$wgOut->setPagetitle( wfMsg( 'move-page', $this->oldTitle->getPrefixedText() ) );
$wgOut->setSubtitle( wfMsg( 'move-page-backlink', $oldTitleLink ) );
@@ -128,12 +131,21 @@ class MovePageForm {
</tr>";
$err = '';
} else {
+ if ($this->oldTitle->getNamespace() == NS_USER && !$this->oldTitle->isSubpage() ) {
+ $wgOut->wrapWikiMsg( "<div class=\"error mw-moveuserpage-warning\">\n$1\n</div>", 'moveuserpage-warning' );
+ }
$wgOut->addWikiMsg( 'movepagetext' );
$movepagebtn = wfMsg( 'movepagebtn' );
$submitVar = 'wpMove';
$confirm = false;
}
+ if ( !empty($err) && $err[0] == 'file-exists-sharedrepo' && $wgUser->isAllowed( 'reupload-shared' ) ) {
+ $wgOut->addWikiMsg( 'move-over-sharedrepo', $newTitle->getPrefixedText() );
+ $submitVar = 'wpMoveOverSharedFile';
+ $err = '';
+ }
+
$oldTalk = $this->oldTitle->getTalkPage();
$considerTalk = ( !$this->oldTitle->isTalkPage() && $oldTalk->exists() );
@@ -166,6 +178,22 @@ class MovePageForm {
}
}
+ if ( $this->oldTitle->isProtected( 'move' ) ) {
+ # Is the title semi-protected?
+ if ( $this->oldTitle->isSemiProtected( 'move' ) ) {
+ $noticeMsg = 'semiprotectedpagemovewarning';
+ $classes[] = 'mw-textarea-sprotected';
+ } else {
+ # Then it must be protected based on static groups (regular)
+ $noticeMsg = 'protectedpagemovewarning';
+ $classes[] = 'mw-textarea-protected';
+ }
+ $wgOut->addHTML( "<div class='mw-warning-with-logexcerpt'>\n" );
+ $wgOut->addWikiMsg( $noticeMsg );
+ LogEventsList::showLogExtract( $wgOut, 'protect', $this->oldTitle->getPrefixedText(), '', array( 'lim' => 1 ) );
+ $wgOut->addHTML( "</div>\n" );
+ }
+
$wgOut->addHTML(
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) .
Xml::openElement( 'fieldset' ) .
@@ -184,7 +212,7 @@ class MovePageForm {
Xml::label( wfMsg( 'newtitle' ), 'wpNewTitle' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'wpNewTitle', 40, $newTitle->getPrefixedText(), array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) .
+ Xml::input( 'wpNewTitle', 40, $wgContLang->recodeForEdit( $newTitle->getPrefixedText() ), array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) .
Xml::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) .
"</td>
</tr>
@@ -193,7 +221,8 @@ class MovePageForm {
Xml::label( wfMsg( 'movereason' ), 'wpReason' ) .
"</td>
<td class='mw-input'>" .
- Xml::tags( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2 ), htmlspecialchars( $this->reason ) ) .
+ Html::element( 'textarea', array( 'name' => 'wpReason', 'id' => 'wpReason', 'cols' => 60, 'rows' => 2,
+ 'maxlength' => 200 ), $this->reason ) .
"</td>
</tr>"
);
@@ -242,34 +271,43 @@ class MovePageForm {
<tr>
<td></td>
<td class=\"mw-input\">" .
- Xml::checkLabel( wfMsgExt(
+ Xml::check(
+ 'wpMovesubpages',
+ # Don't check the box if we only have talk subpages to
+ # move and we aren't moving the talk page.
+ $this->moveSubpages && ($this->oldTitle->hasSubpages() || $this->moveTalk),
+ array( 'id' => 'wpMovesubpages' )
+ ) . '&nbsp;' .
+ Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ),
+ wfMsgExt(
( $this->oldTitle->hasSubpages()
? 'move-subpages'
: 'move-talk-subpages' ),
- array( 'parsemag' ),
+ array( 'parseinline' ),
$wgLang->formatNum( $wgMaximumMovedPages ),
# $2 to allow use of PLURAL in message.
$wgMaximumMovedPages
- ),
- 'wpMovesubpages', 'wpMovesubpages',
- # Don't check the box if we only have talk subpages to
- # move and we aren't moving the talk page.
- $this->moveSubpages && ($this->oldTitle->hasSubpages() || $this->moveTalk)
+ )
) .
"</td>
</tr>"
);
}
- $watchChecked = $this->watch || $wgUser->getBoolOption( 'watchmoves' )
- || $this->oldTitle->userIsWatching();
- $wgOut->addHTML( "
+ $watchChecked = $wgUser->isLoggedIn() && ($this->watch || $wgUser->getBoolOption( 'watchmoves' )
+ || $this->oldTitle->userIsWatching());
+ # Don't allow watching if user is not logged in
+ if( $wgUser->isLoggedIn() ) {
+ $wgOut->addHTML( "
<tr>
<td></td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'move-watch' ), 'wpWatch', 'watch', $watchChecked ) .
"</td>
- </tr>
+ </tr>");
+ }
+
+ $wgOut->addHTML( "
{$confirm}
<tr>
<td>&nbsp;</td>
@@ -329,12 +367,24 @@ class MovePageForm {
return;
}
+ # Show a warning if the target file exists on a shared repo
+ if ( $nt->getNamespace() == NS_FILE
+ && !( $this->moveOverShared && $wgUser->isAllowed( 'reupload-shared' ) )
+ && !RepoGroup::singleton()->getLocalRepo()->findFile( $nt )
+ && wfFindFile( $nt ) )
+ {
+ $this->showForm( array('file-exists-sharedrepo') );
+ return;
+
+ }
+
if ( $wgUser->isAllowed( 'suppressredirect' ) ) {
$createRedirect = $this->leaveRedirect;
} else {
$createRedirect = true;
}
+ # 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
@@ -393,7 +443,7 @@ class MovePageForm {
)
) ) {
$conds = array(
- 'page_title LIKE '.$dbr->addQuotes( $dbr->escapeLike( $ot->getDBkey() ) . '/%' )
+ 'page_title' . $dbr->buildLike( $ot->getDBkey() . '/', $dbr->anyString() )
.' OR page_title = ' . $dbr->addQuotes( $ot->getDBkey() )
);
$conds['page_namespace'] = array();
@@ -406,7 +456,7 @@ class MovePageForm {
} elseif( $this->moveTalk ) {
$conds = array(
'page_namespace' => $ot->getTalkPage()->getNamespace(),
- 'page_title' => $ot->getDBKey()
+ 'page_title' => $ot->getDBkey()
);
} else {
# Skip the query
@@ -428,15 +478,15 @@ class MovePageForm {
$skin = $wgUser->getSkin();
$count = 1;
foreach( $extraPages as $oldSubpage ) {
- if( $oldSubpage->getArticleId() == $ot->getArticleId() ) {
+ if( $ot->equals( $oldSubpage ) ) {
# Already did this one.
continue;
}
$newPageName = preg_replace(
- '#^'.preg_quote( $ot->getDBKey(), '#' ).'#',
- $nt->getDBKey(),
- $oldSubpage->getDBKey()
+ '#^'.preg_quote( $ot->getDBkey(), '#' ).'#',
+ StringUtils::escapeRegexReplacement( $nt->getDBkey() ), # bug 21234
+ $oldSubpage->getDBkey()
);
if( $oldSubpage->isTalkPage() ) {
$newNs = $nt->getTalkPage()->getNamespace();
@@ -447,7 +497,7 @@ class MovePageForm {
# be longer than 255 characters.
$newSubpage = Title::makeTitleSafe( $newNs, $newPageName );
if( !$newSubpage ) {
- $oldLink = $skin->makeKnownLinkObj( $oldSubpage );
+ $oldLink = $skin->linkKnown( $oldSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink,
htmlspecialchars(Title::makeName( $newNs, $newPageName )));
continue;
@@ -455,7 +505,7 @@ class MovePageForm {
# This was copy-pasted from Renameuser, bleh.
if ( $newSubpage->exists() && !$oldSubpage->isValidMoveTarget( $newSubpage ) ) {
- $link = $skin->makeKnownLinkObj( $newSubpage );
+ $link = $skin->linkKnown( $newSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-exists', $link );
} else {
$success = $oldSubpage->moveTo( $newSubpage, true, $this->reason, $createRedirect );
@@ -463,21 +513,26 @@ class MovePageForm {
if ( $this->fixRedirects ) {
DoubleRedirectJob::fixRedirects( 'move', $oldSubpage, $newSubpage );
}
- $oldLink = $skin->makeKnownLinkObj( $oldSubpage, '', 'redirect=no' );
- $newLink = $skin->makeKnownLinkObj( $newSubpage );
+ $oldLink = $skin->linkKnown(
+ $oldSubpage,
+ null,
+ array(),
+ array( 'redirect' => 'no' )
+ );
+ $newLink = $skin->linkKnown( $newSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-moved', $oldLink, $newLink );
+ ++$count;
+ if( $count >= $wgMaximumMovedPages ) {
+ $extraOutput []= wfMsgExt( 'movepage-max-pages', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgMaximumMovedPages ) );
+ break;
+ }
} else {
- $oldLink = $skin->makeKnownLinkObj( $oldSubpage );
- $newLink = $skin->makeLinkObj( $newSubpage );
+ $oldLink = $skin->linkKnown( $oldSubpage );
+ $newLink = $skin->link( $newSubpage );
$extraOutput []= wfMsgHtml( 'movepage-page-unmoved', $oldLink, $newLink );
}
}
- ++$count;
- if( $count >= $wgMaximumMovedPages ) {
- $extraOutput []= wfMsgExt( 'movepage-max-pages', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgMaximumMovedPages ) );
- break;
- }
}
if( $extraOutput !== array() ) {
@@ -485,17 +540,24 @@ class MovePageForm {
}
# Deal with watches (we don't watch subpages)
- if( $this->watch ) {
+ if( $this->watch && $wgUser->isLoggedIn() ) {
$wgUser->addWatch( $ot );
$wgUser->addWatch( $nt );
} else {
$wgUser->removeWatch( $ot );
$wgUser->removeWatch( $nt );
}
+
+ # 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.
+ if( $ot->getNamespace() == NS_FILE ) {
+ RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $ot );
+ }
}
function showLogFragment( $title, &$out ) {
- $out->addHTML( Xml::element( 'h2', NULL, LogPage::logName( 'move' ) ) );
+ $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'move' ) ) );
LogEventsList::showLogExtract( $out, 'move', $title->getPrefixedText() );
}
diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php
index 575e37a7..a39b56ee 100644
--- a/includes/specials/SpecialNewimages.php
+++ b/includes/specials/SpecialNewimages.php
@@ -40,7 +40,8 @@ function wfSpecialNewimages( $par, $specialPage ) {
if ($hidebotsql) {
$sql .= "$hidebotsql WHERE ug_group IS NULL";
}
- $sql .= ' ORDER BY img_timestamp DESC LIMIT 1';
+ $sql .= ' ORDER BY img_timestamp DESC';
+ $sql = $dbr->limitResult($sql, 1, false);
$res = $dbr->query( $sql, __FUNCTION__ );
$row = $dbr->fetchRow( $res );
if( $row !== false ) {
@@ -64,13 +65,12 @@ function wfSpecialNewimages( $par, $specialPage ) {
}
$where = array();
- $searchpar = '';
+ $searchpar = array();
if ( $wpIlMatch != '' && !$wgMiserMode) {
- $nt = Title::newFromUrl( $wpIlMatch );
+ $nt = Title::newFromURL( $wpIlMatch );
if( $nt ) {
- $m = $dbr->escapeLike( strtolower( $nt->getDBkey() ) );
- $where[] = "LOWER(img_name) LIKE '%{$m}%'";
- $searchpar = '&wpIlMatch=' . urlencode( $wpIlMatch );
+ $where[] = 'LOWER(img_name) ' . $dbr->buildLike( $dbr->anyString(), strtolower( $nt->getDBkey() ), $dbr->anyString() );
+ $searchpar['wpIlMatch'] = $wpIlMatch;
}
}
@@ -93,7 +93,7 @@ function wfSpecialNewimages( $par, $specialPage ) {
$sql .= ' WHERE ' . $dbr->makeList( $where, LIST_AND );
}
$sql.=' ORDER BY img_timestamp '. ( $invertSort ? '' : ' DESC' );
- $sql.=' LIMIT ' . ( $limit + 1 );
+ $sql = $dbr->limitResult($sql, ( $limit + 1 ), false);
$res = $dbr->query( $sql, __FUNCTION__ );
/**
@@ -125,9 +125,9 @@ function wfSpecialNewimages( $par, $specialPage ) {
$ut = $s->img_user_text;
$nt = Title::newFromText( $name, NS_FILE );
- $ul = $sk->makeLinkObj( Title::makeTitle( NS_USER, $ut ), $ut );
+ $ul = $sk->link( Title::makeTitle( NS_USER, $ut ), $ut );
- $gallery->add( $nt, "$ul<br />\n<i>".$wgLang->timeanddate( $s->img_timestamp, true )."</i><br />\n" );
+ $gallery->add( $nt, "$ul<br />\n<i>".htmlspecialchars($wgLang->timeanddate( $s->img_timestamp, true ))."</i><br />\n" );
$timestamp = wfTimestamp( TS_MW, $s->img_timestamp );
if( empty( $firstTimestamp ) ) {
@@ -162,29 +162,72 @@ function wfSpecialNewimages( $par, $specialPage ) {
# If we change bot visibility, this needs to be carried along.
if( !$hidebots ) {
- $botpar = '&hidebots=0';
+ $botpar = array( 'hidebots' => 0 );
} else {
- $botpar = '';
+ $botpar = array();
}
$now = wfTimestampNow();
$d = $wgLang->date( $now, true );
$t = $wgLang->time( $now, true );
- $dateLink = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml( 'sp-newimages-showfrom', $d, $t ),
- 'from='.$now.$botpar.$searchpar );
-
- $botLink = $sk->makeKnownLinkObj($titleObj, wfMsgHtml( 'showhidebots',
- ($hidebots ? wfMsgHtml('show') : wfMsgHtml('hide'))),'hidebots='.($hidebots ? '0' : '1').$searchpar);
-
+ $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
+ );
+
+ $showhide = $hidebots ? wfMsg( 'show' ) : wfMsg( 'hide' );
+
+ $botLink = $sk->linkKnown(
+ $titleObj,
+ htmlspecialchars( wfMsg( 'showhidebots', $showhide ) ),
+ array(),
+ $query
+ );
$opts = array( 'parsemag', 'escapenoentities' );
$prevLink = wfMsgExt( 'pager-newer-n', $opts, $wgLang->formatNum( $limit ) );
if( $firstTimestamp && $firstTimestamp != $latestTimestamp ) {
- $prevLink = $sk->makeKnownLinkObj( $titleObj, $prevLink, 'from=' . $firstTimestamp . $botpar . $searchpar );
+ $query = array_merge(
+ array( 'from' => $firstTimestamp ),
+ $botpar,
+ $searchpar
+ );
+
+ $prevLink = $sk->linkKnown(
+ $titleObj,
+ $prevLink,
+ array(),
+ $query
+ );
}
$nextLink = wfMsgExt( 'pager-older-n', $opts, $wgLang->formatNum( $limit ) );
if( $shownImages > $limit && $lastTimestamp ) {
- $nextLink = $sk->makeKnownLinkObj( $titleObj, $nextLink, 'until=' . $lastTimestamp.$botpar.$searchpar );
+ $query = array_merge(
+ array( 'until' => $lastTimestamp ),
+ $botpar,
+ $searchpar
+ );
+
+ $nextLink = $sk->linkKnown(
+ $titleObj,
+ $nextLink,
+ array(),
+ $query
+ );
+
}
$prevnext = '<p>' . $botLink . ' '. wfMsgHtml( 'viewprevnext', $prevLink, $nextLink, $dateLink ) .'</p>';
diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php
index 886c41a2..903ddab0 100644
--- a/includes/specials/SpecialNewpages.php
+++ b/includes/specials/SpecialNewpages.php
@@ -89,7 +89,7 @@ class SpecialNewpages extends SpecialPage {
* @return string
*/
public function execute( $par ) {
- global $wgLang, $wgUser, $wgOut;
+ global $wgLang, $wgOut;
$this->setHeaders();
$this->outputHeader();
@@ -165,6 +165,7 @@ class SpecialNewpages extends SpecialPage {
$this->opts->consumeValue( 'offset' ); // don't carry offset, DWIW
$namespace = $this->opts->consumeValue( 'namespace' );
$username = $this->opts->consumeValue( 'username' );
+ $tagFilterVal = $this->opts->consumeValue( 'tagfilter' );
// Check username input validity
$ut = Title::makeTitleSafe( NS_USER, $username );
@@ -177,7 +178,7 @@ class SpecialNewpages extends SpecialPage {
}
$hidden = implode( "\n", $hidden );
- $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] );
+ $tagFilter = ChangeTags::buildTagFilterSelector( $tagFilterVal );
if ($tagFilter)
list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter;
@@ -231,12 +232,8 @@ class SpecialNewpages extends SpecialPage {
protected function setSyndicated() {
global $wgOut;
- $queryParams = array(
- 'namespace' => $this->opts->getValue( 'namespace' ),
- 'username' => $this->opts->getValue( 'username' )
- );
$wgOut->setSyndicated( true );
- $wgOut->setFeedAppendQuery( wfArrayToCGI( $queryParams ) );
+ $wgOut->setFeedAppendQuery( wfArrayToCGI( $this->opts->getAllValues() ) );
}
/**
@@ -247,17 +244,32 @@ class SpecialNewpages extends SpecialPage {
* @return string
*/
public function formatRow( $result ) {
- global $wgLang, $wgContLang, $wgUser;
+ global $wgLang, $wgContLang;
$classes = array();
$dm = $wgContLang->getDirMark();
$title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title );
- $time = $wgLang->timeAndDate( $result->rc_timestamp, true );
- $query = $this->patrollable( $result ) ? "rcid={$result->rc_id}&redirect=no" : 'redirect=no';
- $plink = $this->skin->makeKnownLinkObj( $title, '', $query );
- $hist = $this->skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' );
+ $time = htmlspecialchars( $wgLang->timeAndDate( $result->rc_timestamp, true ) );
+
+ $query = array( 'redirect' => 'no' );
+
+ if( $this->patrollable( $result ) )
+ $query['rcid'] = $result->rc_id;
+
+ $plink = $this->skin->linkKnown(
+ $title,
+ null,
+ array(),
+ $query
+ );
+ $hist = $this->skin->linkKnown(
+ $title,
+ wfMsgHtml( 'hist' ),
+ array(),
+ array( 'action' => 'history' )
+ );
$length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
$wgLang->formatNum( $result->length ) );
$ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' .
@@ -345,7 +357,7 @@ class SpecialNewpages extends SpecialPage {
$this->feedItemAuthor( $row ),
$comments);
} else {
- return NULL;
+ return null;
}
}
diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php
index eb572736..88b90bc3 100644
--- a/includes/specials/SpecialPopularpages.php
+++ b/includes/specials/SpecialPopularpages.php
@@ -48,9 +48,15 @@ class PopularPagesPage extends QueryPage {
function formatResult( $skin, $result ) {
global $wgLang, $wgContLang;
$title = Title::makeTitle( $result->namespace, $result->title );
- $link = $skin->makeKnownLinkObj( $title, htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) ) );
- $nv = wfMsgExt( 'nviews', array( 'parsemag', 'escape'),
- $wgLang->formatNum( $result->value ) );
+ $link = $skin->linkKnown(
+ $title,
+ htmlspecialchars( $wgContLang->convert( $title->getPrefixedText() ) )
+ );
+ $nv = wfMsgExt(
+ 'nviews',
+ array( 'parsemag', 'escape'),
+ $wgLang->formatNum( $result->value )
+ );
return wfSpecialList($link, $nv);
}
}
diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php
index 49c4f4e0..4c8bbb09 100644
--- a/includes/specials/SpecialPreferences.php
+++ b/includes/specials/SpecialPreferences.php
@@ -1,1308 +1,75 @@
<?php
-/**
- * Hold things related to displaying and saving user preferences.
- * @file
- * @ingroup SpecialPage
- */
-/**
- * Entry point that create the "Preferences" object
- */
-function wfSpecialPreferences() {
- global $wgRequest;
-
- $form = new PreferencesForm( $wgRequest );
- $form->execute();
-}
-
-/**
- * Preferences form handling
- * This object will show the preferences form and can save it as well.
- * @ingroup SpecialPage
- */
-class PreferencesForm {
- var $mQuickbar, $mStubs;
- var $mRows, $mCols, $mSkin, $mMath, $mDate, $mUserEmail, $mEmailFlag, $mNick;
- var $mUserLanguage, $mUserVariant;
- var $mSearch, $mRecent, $mRecentDays, $mTimeZone, $mHourDiff, $mSearchLines, $mSearchChars, $mAction;
- var $mReset, $mPosted, $mToggles, $mSearchNs, $mRealName, $mImageSize;
- var $mUnderline, $mWatchlistEdits, $mGender;
-
- /**
- * Constructor
- * Load some values
- */
- function __construct( &$request ) {
- global $wgContLang, $wgUser, $wgAllowRealName;
-
- $this->mQuickbar = $request->getVal( 'wpQuickbar' );
- $this->mStubs = $request->getVal( 'wpStubs' );
- $this->mRows = $request->getVal( 'wpRows' );
- $this->mCols = $request->getVal( 'wpCols' );
- $this->mSkin = Skin::normalizeKey( $request->getVal( 'wpSkin' ) );
- $this->mMath = $request->getVal( 'wpMath' );
- $this->mDate = $request->getVal( 'wpDate' );
- $this->mUserEmail = $request->getVal( 'wpUserEmail' );
- $this->mRealName = $wgAllowRealName ? $request->getVal( 'wpRealName' ) : '';
- $this->mEmailFlag = $request->getCheck( 'wpEmailFlag' ) ? 0 : 1;
- $this->mNick = $request->getVal( 'wpNick' );
- $this->mUserLanguage = $request->getVal( 'wpUserLanguage' );
- $this->mUserVariant = $request->getVal( 'wpUserVariant' );
- $this->mSearch = $request->getVal( 'wpSearch' );
- $this->mRecent = $request->getVal( 'wpRecent' );
- $this->mRecentDays = $request->getVal( 'wpRecentDays' );
- $this->mTimeZone = $request->getVal( 'wpTimeZone' );
- $this->mHourDiff = $request->getVal( 'wpHourDiff' );
- $this->mSearchLines = $request->getVal( 'wpSearchLines' );
- $this->mSearchChars = $request->getVal( 'wpSearchChars' );
- $this->mImageSize = $request->getVal( 'wpImageSize' );
- $this->mThumbSize = $request->getInt( 'wpThumbSize' );
- $this->mUnderline = $request->getInt( 'wpOpunderline' );
- $this->mAction = $request->getVal( 'action' );
- $this->mReset = $request->getCheck( 'wpReset' );
- $this->mRestoreprefs = $request->getCheck( 'wpRestore' );
- $this->mPosted = $request->wasPosted();
- $this->mSuccess = $request->getCheck( 'success' );
- $this->mWatchlistDays = $request->getVal( 'wpWatchlistDays' );
- $this->mWatchlistEdits = $request->getVal( 'wpWatchlistEdits' );
- $this->mDisableMWSuggest = $request->getCheck( 'wpDisableMWSuggest' );
- $this->mGender = $request->getVal( 'wpGender' );
-
- $this->mSaveprefs = $request->getCheck( 'wpSaveprefs' ) &&
- $this->mPosted &&
- $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
-
- # User toggles (the big ugly unsorted list of checkboxes)
- $this->mToggles = array();
- if ( $this->mPosted ) {
- $togs = User::getToggles();
- foreach ( $togs as $tname ) {
- $this->mToggles[$tname] = $request->getCheck( "wpOp$tname" ) ? 1 : 0;
- }
- }
-
- $this->mUsedToggles = array();
-
- # Search namespace options
- # Note: namespaces don't necessarily have consecutive keys
- $this->mSearchNs = array();
- if ( $this->mPosted ) {
- $namespaces = $wgContLang->getNamespaces();
- foreach ( $namespaces as $i => $namespace ) {
- if ( $i >= 0 ) {
- $this->mSearchNs[$i] = $request->getCheck( "wpNs$i" ) ? 1 : 0;
- }
- }
- }
-
- # Validate language
- if ( !preg_match( '/^[a-z\-]*$/', $this->mUserLanguage ) ) {
- $this->mUserLanguage = 'nolanguage';
- }
-
- wfRunHooks( 'InitPreferencesForm', array( $this, $request ) );
+class SpecialPreferences extends SpecialPage {
+ function __construct() {
+ parent::__construct( 'Preferences' );
}
- function execute() {
- global $wgUser, $wgOut, $wgTitle;
+ function execute( $par ) {
+ global $wgOut, $wgUser, $wgRequest;
+
+ $this->setHeaders();
+ $this->outputHeader();
+ $wgOut->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
if ( $wgUser->isAnon() ) {
- $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext', array($wgTitle->getPrefixedDBkey()) );
+ $wgOut->showErrorPage( 'prefsnologin', 'prefsnologintext', array( $this->getTitle()->getPrefixedDBkey() ) );
return;
}
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
- if ( $this->mReset ) {
- $this->resetPrefs();
- $this->mainPrefsForm( 'reset', wfMsg( 'prefsreset' ) );
- } else if ( $this->mSaveprefs ) {
- $this->savePreferences();
- } else if ( $this->mRestoreprefs ) {
- $this->restorePreferences();
- } else {
- $this->resetPrefs();
- $this->mainPrefsForm( '' );
- }
- }
- /**
- * @access private
- */
- function validateInt( &$val, $min=0, $max=0x7fffffff ) {
- $val = intval($val);
- $val = min($val, $max);
- $val = max($val, $min);
- return $val;
- }
- /**
- * @access private
- */
- function validateFloat( &$val, $min, $max=0x7fffffff ) {
- $val = floatval( $val );
- $val = min( $val, $max );
- $val = max( $val, $min );
- return( $val );
- }
-
- /**
- * @access private
- */
- function validateIntOrNull( &$val, $min=0, $max=0x7fffffff ) {
- $val = trim($val);
- if($val === '') {
- return null;
- } else {
- return $this->validateInt( $val, $min, $max );
- }
- }
-
- /**
- * @access private
- */
- function validateDate( $val ) {
- global $wgLang, $wgContLang;
- if ( $val !== false && (
- in_array( $val, (array)$wgLang->getDatePreferences() ) ||
- in_array( $val, (array)$wgContLang->getDatePreferences() ) ) )
- {
- return $val;
- } else {
- return $wgLang->getDefaultDateFormat();
- }
- }
-
- /**
- * Used to validate the user inputed timezone before saving it as
- * 'timecorrection', will return 'System' if fed bogus data.
- * @access private
- * @param string $tz the user input Zoneinfo timezone
- * @param string $s the user input offset string
- * @return string
- */
- function validateTimeZone( $tz, $s ) {
- $data = explode( '|', $tz, 3 );
- switch ( $data[0] ) {
- case 'ZoneInfo':
- case 'System':
- return $tz;
- case 'Offset':
- default:
- $data = explode( ':', $s, 2 );
- $minDiff = 0;
- if( count( $data ) == 2 ) {
- $data[0] = intval( $data[0] );
- $data[1] = intval( $data[1] );
- $minDiff = abs( $data[0] ) * 60 + $data[1];
- if ( $data[0] < 0 ) $minDiff = -$minDiff;
- } else {
- $minDiff = intval( $data[0] ) * 60;
- }
-
- # Max is +14:00 and min is -12:00, see:
- # http://en.wikipedia.org/wiki/Timezone
- $minDiff = min( $minDiff, 840 ); # 14:00
- $minDiff = max( $minDiff, -720 ); # -12:00
- return 'Offset|'.$minDiff;
- }
- }
-
- function validateGender( $val ) {
- $valid = array( 'male', 'female', 'unknown' );
- if ( in_array($val, $valid) ) {
- return $val;
- } else {
- return User::getDefaultOption( 'gender' );
- }
- }
-
- /**
- * @access private
- */
- function savePreferences() {
- global $wgUser, $wgOut, $wgParser;
- global $wgEnableUserEmail, $wgEnableEmail;
- global $wgEmailAuthentication, $wgRCMaxAge;
- global $wgAuth, $wgEmailConfirmToEdit;
-
- $wgUser->setRealName( $this->mRealName );
- $oldOptions = $wgUser->mOptions;
-
- if( $wgUser->getOption( 'language' ) !== $this->mUserLanguage ) {
- $needRedirect = true;
- } else {
- $needRedirect = false;
- }
-
- # Validate the signature and clean it up as needed
- global $wgMaxSigChars;
- if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) {
- global $wgLang;
- $this->mainPrefsForm( 'error',
- wfMsgExt( 'badsiglength', 'parsemag', $wgLang->formatNum( $wgMaxSigChars ) ) );
+ if ( $par == 'reset' ) {
+ $this->showResetForm();
return;
- } elseif( $this->mToggles['fancysig'] ) {
- if( $wgParser->validateSig( $this->mNick ) !== false ) {
- $this->mNick = $wgParser->cleanSig( $this->mNick );
- } else {
- $this->mainPrefsForm( 'error', wfMsg( 'badsig' ) );
- return;
- }
- } else {
- // When no fancy sig used, make sure ~{3,5} get removed.
- $this->mNick = $wgParser->cleanSigInSig( $this->mNick );
- }
-
- $wgUser->setOption( 'language', $this->mUserLanguage );
- $wgUser->setOption( 'variant', $this->mUserVariant );
- $wgUser->setOption( 'nickname', $this->mNick );
- $wgUser->setOption( 'quickbar', $this->mQuickbar );
- global $wgAllowUserSkin;
- if( $wgAllowUserSkin ) {
- $wgUser->setOption( 'skin', $this->mSkin );
- }
- global $wgUseTeX;
- if( $wgUseTeX ) {
- $wgUser->setOption( 'math', $this->mMath );
- }
- $wgUser->setOption( 'date', $this->validateDate( $this->mDate ) );
- $wgUser->setOption( 'searchlimit', $this->validateIntOrNull( $this->mSearch ) );
- $wgUser->setOption( 'contextlines', $this->validateIntOrNull( $this->mSearchLines ) );
- $wgUser->setOption( 'contextchars', $this->validateIntOrNull( $this->mSearchChars ) );
- $wgUser->setOption( 'rclimit', $this->validateIntOrNull( $this->mRecent ) );
- $wgUser->setOption( 'rcdays', $this->validateInt($this->mRecentDays, 1, ceil($wgRCMaxAge / (3600*24))));
- $wgUser->setOption( 'wllimit', $this->validateIntOrNull( $this->mWatchlistEdits, 0, 1000 ) );
- $wgUser->setOption( 'rows', $this->validateInt( $this->mRows, 4, 1000 ) );
- $wgUser->setOption( 'cols', $this->validateInt( $this->mCols, 4, 1000 ) );
- $wgUser->setOption( 'stubthreshold', $this->validateIntOrNull( $this->mStubs ) );
- $wgUser->setOption( 'timecorrection', $this->validateTimeZone( $this->mTimeZone, $this->mHourDiff ) );
- $wgUser->setOption( 'imagesize', $this->mImageSize );
- $wgUser->setOption( 'thumbsize', $this->mThumbSize );
- $wgUser->setOption( 'underline', $this->validateInt($this->mUnderline, 0, 2) );
- $wgUser->setOption( 'watchlistdays', $this->validateFloat( $this->mWatchlistDays, 0, 7 ) );
- $wgUser->setOption( 'disablesuggest', $this->mDisableMWSuggest );
- $wgUser->setOption( 'gender', $this->validateGender( $this->mGender ) );
-
- # Set search namespace options
- foreach( $this->mSearchNs as $i => $value ) {
- $wgUser->setOption( "searchNs{$i}", $value );
- }
-
- if( $wgEnableEmail && $wgEnableUserEmail ) {
- $wgUser->setOption( 'disablemail', $this->mEmailFlag );
- }
-
- # Set user toggles
- foreach ( $this->mToggles as $tname => $tvalue ) {
- $wgUser->setOption( $tname, $tvalue );
- }
-
- $error = false;
- if( $wgEnableEmail ) {
- $newadr = $this->mUserEmail;
- $oldadr = $wgUser->getEmail();
- if( ($newadr != '') && ($newadr != $oldadr) ) {
- # the user has supplied a new email address on the login page
- if( $wgUser->isValidEmailAddr( $newadr ) ) {
- # new behaviour: set this new emailaddr from login-page into user database record
- $wgUser->setEmail( $newadr );
- # but flag as "dirty" = unauthenticated
- $wgUser->invalidateEmail();
- if ($wgEmailAuthentication) {
- # Mail a temporary password to the dirty address.
- # User can come back through the confirmation URL to re-enable email.
- $result = $wgUser->sendConfirmationMail();
- if( WikiError::isError( $result ) ) {
- $error = wfMsg( 'mailerror', htmlspecialchars( $result->getMessage() ) );
- } else {
- $error = wfMsg( 'eauthentsent', $wgUser->getName() );
- }
- }
- } else {
- $error = wfMsg( 'invalidemailaddress' );
- }
- } else {
- if( $wgEmailConfirmToEdit && empty( $newadr ) ) {
- $this->mainPrefsForm( 'error', wfMsg( 'noemailtitle' ) );
- return;
- }
- $wgUser->setEmail( $this->mUserEmail );
- }
- if( $oldadr != $newadr ) {
- wfRunHooks( 'PrefsEmailAudit', array( $wgUser, $oldadr, $newadr ) );
- }
}
+
+ $wgOut->addScriptFile( 'prefs.js' );
- if( !$wgAuth->updateExternalDB( $wgUser ) ){
- $this->mainPrefsForm( 'error', wfMsg( 'externaldberror' ) );
- return;
+ if ( $wgRequest->getCheck( 'success' ) ) {
+ $wgOut->wrapWikiMsg(
+ '<div class="successbox"><strong>$1</strong></div><div id="mw-pref-clear"></div>',
+ 'savedprefs'
+ );
}
-
- $msg = '';
- if ( !wfRunHooks( 'SavePreferences', array( $this, $wgUser, &$msg, $oldOptions ) ) ) {
- $this->mainPrefsForm( 'error', $msg );
- return;
+
+ if ( $wgRequest->getCheck( 'eauth' ) ) {
+ $wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1</div>",
+ 'eauthentsent', $wgUser->getName() );
}
- $wgUser->setCookies();
- $wgUser->saveSettings();
-
- if( $needRedirect && $error === false ) {
- $title = SpecialPage::getTitleFor( 'Preferences' );
- $wgOut->redirect( $title->getFullURL( 'success' ) );
- return;
- }
+ $htmlForm = Preferences::getFormObject( $wgUser );
+ $htmlForm->setSubmitCallback( array( 'Preferences', 'tryUISubmit' ) );
- $wgOut->parserOptions( ParserOptions::newFromUser( $wgUser ) );
- $this->mainPrefsForm( $error === false ? 'success' : 'error', $error);
+ $htmlForm->show();
}
- /**
- * @access private
- */
- function resetPrefs() {
- global $wgUser, $wgLang, $wgContLang, $wgContLanguageCode, $wgAllowRealName, $wgLocalTZoffset;
+ function showResetForm() {
+ global $wgOut;
- $this->mUserEmail = $wgUser->getEmail();
- $this->mUserEmailAuthenticationtimestamp = $wgUser->getEmailAuthenticationtimestamp();
- $this->mRealName = ($wgAllowRealName) ? $wgUser->getRealName() : '';
+ $wgOut->addWikiMsg( 'prefs-reset-intro' );
- # language value might be blank, default to content language
- $this->mUserLanguage = $wgUser->getOption( 'language', $wgContLanguageCode );
+ $htmlForm = new HTMLForm( array(), 'prefs-restore' );
- $this->mUserVariant = $wgUser->getOption( 'variant');
- $this->mEmailFlag = $wgUser->getOption( 'disablemail' ) == 1 ? 1 : 0;
- $this->mNick = $wgUser->getOption( 'nickname' );
+ $htmlForm->setSubmitText( wfMsg( 'restoreprefs' ) );
+ $htmlForm->setTitle( $this->getTitle( 'reset' ) );
+ $htmlForm->setSubmitCallback( array( __CLASS__, 'submitReset' ) );
+ $htmlForm->suppressReset();
- $this->mQuickbar = $wgUser->getOption( 'quickbar' );
- $this->mSkin = Skin::normalizeKey( $wgUser->getOption( 'skin' ) );
- $this->mMath = $wgUser->getOption( 'math' );
- $this->mDate = $wgUser->getDatePreference();
- $this->mRows = $wgUser->getOption( 'rows' );
- $this->mCols = $wgUser->getOption( 'cols' );
- $this->mStubs = $wgUser->getOption( 'stubthreshold' );
-
- $tz = $wgUser->getOption( 'timecorrection' );
- $data = explode( '|', $tz, 3 );
- $minDiff = null;
- switch ( $data[0] ) {
- case 'ZoneInfo':
- $this->mTimeZone = $tz;
- # Check if the specified TZ exists, and change to 'Offset' if
- # not.
- if ( !function_exists('timezone_open') || @timezone_open( $data[2] ) === false ) {
- $this->mTimeZone = 'Offset';
- $minDiff = intval( $data[1] );
- }
- break;
- case '':
- case 'System':
- $this->mTimeZone = 'System|'.$wgLocalTZoffset;
- break;
- case 'Offset':
- $this->mTimeZone = 'Offset';
- $minDiff = intval( $data[1] );
- break;
- default:
- $this->mTimeZone = 'Offset';
- $data = explode( ':', $tz, 2 );
- if( count( $data ) == 2 ) {
- $data[0] = intval( $data[0] );
- $data[1] = intval( $data[1] );
- $minDiff = abs( $data[0] ) * 60 + $data[1];
- if ( $data[0] < 0 ) $minDiff = -$minDiff;
- } else {
- $minDiff = intval( $data[0] ) * 60;
- }
- break;
- }
- if ( is_null( $minDiff ) ) {
- $this->mHourDiff = '';
- } else {
- $this->mHourDiff = sprintf( '%+03d:%02d', floor($minDiff/60), abs($minDiff)%60 );
- }
-
- $this->mSearch = $wgUser->getOption( 'searchlimit' );
- $this->mSearchLines = $wgUser->getOption( 'contextlines' );
- $this->mSearchChars = $wgUser->getOption( 'contextchars' );
- $this->mImageSize = $wgUser->getOption( 'imagesize' );
- $this->mThumbSize = $wgUser->getOption( 'thumbsize' );
- $this->mRecent = $wgUser->getOption( 'rclimit' );
- $this->mRecentDays = $wgUser->getOption( 'rcdays' );
- $this->mWatchlistEdits = $wgUser->getOption( 'wllimit' );
- $this->mUnderline = $wgUser->getOption( 'underline' );
- $this->mWatchlistDays = $wgUser->getOption( 'watchlistdays' );
- $this->mDisableMWSuggest = $wgUser->getBoolOption( 'disablesuggest' );
- $this->mGender = $wgUser->getOption( 'gender' );
-
- $togs = User::getToggles();
- foreach ( $togs as $tname ) {
- $this->mToggles[$tname] = $wgUser->getOption( $tname );
- }
-
- $namespaces = $wgContLang->getNamespaces();
- foreach ( $namespaces as $i => $namespace ) {
- if ( $i >= NS_MAIN ) {
- $this->mSearchNs[$i] = $wgUser->getOption( 'searchNs'.$i );
- }
- }
-
- wfRunHooks( 'ResetPreferences', array( $this, $wgUser ) );
+ $htmlForm->show();
}
-
- /**
- * @access private
- */
- function restorePreferences() {
+
+ static function submitReset( $formData ) {
global $wgUser, $wgOut;
- $wgUser->restoreOptions();
- $wgUser->setCookies();
+ $wgUser->resetOptions();
$wgUser->saveSettings();
- $title = SpecialPage::getTitleFor( 'Preferences' );
- $wgOut->redirect( $title->getFullURL( 'success' ) );
- }
-
- /**
- * @access private
- */
- function namespacesCheckboxes() {
- global $wgContLang;
-
- # Determine namespace checkboxes
- $namespaces = $wgContLang->getNamespaces();
- $r1 = null;
-
- foreach ( $namespaces as $i => $name ) {
- if ($i < 0)
- continue;
- $checked = $this->mSearchNs[$i] ? "checked='checked'" : '';
- $name = str_replace( '_', ' ', $namespaces[$i] );
-
- if ( empty($name) )
- $name = wfMsg( 'blanknamespace' );
-
- $r1 .= "<input type='checkbox' value='1' name='wpNs$i' id='wpNs$i' {$checked}/> <label for='wpNs$i'>{$name}</label><br />\n";
- }
- return $r1;
- }
-
-
- function getToggle( $tname, $trailer = false, $disabled = false ) {
- global $wgUser, $wgLang;
-
- $this->mUsedToggles[$tname] = true;
- $ttext = $wgLang->getUserToggle( $tname );
-
- $checked = $wgUser->getOption( $tname ) == 1 ? ' checked="checked"' : '';
- $disabled = $disabled ? ' disabled="disabled"' : '';
- $trailer = $trailer ? $trailer : '';
- return "<div class='toggle'><input type='checkbox' value='1' id=\"$tname\" name=\"wpOp$tname\"$checked$disabled />" .
- " <span class='toggletext'><label for=\"$tname\">$ttext</label>$trailer</span></div>\n";
- }
-
- function getToggles( $items ) {
- $out = "";
- foreach( $items as $item ) {
- if( $item === false )
- continue;
- if( is_array( $item ) ) {
- list( $key, $trailer ) = $item;
- } else {
- $key = $item;
- $trailer = false;
- }
- $out .= $this->getToggle( $key, $trailer );
- }
- return $out;
- }
-
- function addRow($td1, $td2) {
- return "<tr><td class='mw-label'>$td1</td><td class='mw-input'>$td2</td></tr>";
- }
-
- /**
- * Helper function for user information panel
- * @param $td1 label for an item
- * @param $td2 item or null
- * @param $td3 optional help or null
- * @return xhtml block
- */
- function tableRow( $td1, $td2 = null, $td3 = null ) {
-
- if ( is_null( $td3 ) ) {
- $td3 = '';
- } else {
- $td3 = Xml::tags( 'tr', null,
- Xml::tags( 'td', array( 'class' => 'pref-label', 'colspan' => '2' ), $td3 )
- );
- }
-
- if ( is_null( $td2 ) ) {
- $td1 = Xml::tags( 'td', array( 'class' => 'pref-label', 'colspan' => '2' ), $td1 );
- $td2 = '';
- } else {
- $td1 = Xml::tags( 'td', array( 'class' => 'pref-label' ), $td1 );
- $td2 = Xml::tags( 'td', array( 'class' => 'pref-input' ), $td2 );
- }
-
- return Xml::tags( 'tr', null, $td1 . $td2 ). $td3 . "\n";
-
- }
-
- /**
- * @access private
- */
- function mainPrefsForm( $status , $message = '' ) {
- global $wgUser, $wgOut, $wgLang, $wgContLang, $wgAuth;
- global $wgAllowRealName, $wgImageLimits, $wgThumbLimits;
- global $wgDisableLangConversion, $wgDisableTitleConversion;
- global $wgEnotifWatchlist, $wgEnotifUserTalk,$wgEnotifMinorEdits;
- global $wgRCShowWatchingUsers, $wgEnotifRevealEditorAddress;
- global $wgEnableEmail, $wgEnableUserEmail, $wgEmailAuthentication;
- global $wgContLanguageCode, $wgDefaultSkin, $wgCookieExpiration;
- global $wgEmailConfirmToEdit, $wgEnableMWSuggest, $wgLocalTZoffset;
-
- $wgOut->setPageTitle( wfMsg( 'preferences' ) );
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- $wgOut->addScriptFile( 'prefs.js' );
-
- $wgOut->disallowUserJs(); # Prevent hijacked user scripts from sniffing passwords etc.
-
- if ( $this->mSuccess || 'success' == $status ) {
- $wgOut->wrapWikiMsg( '<div class="successbox"><strong>$1</strong></div>', 'savedprefs' );
- } else if ( 'error' == $status ) {
- $wgOut->addWikiText( '<div class="errorbox"><strong>' . $message . '</strong></div>' );
- } else if ( '' != $status ) {
- $wgOut->addWikiText( $message . "\n----" );
- }
-
- $qbs = $wgLang->getQuickbarSettings();
- $mathopts = $wgLang->getMathNames();
- $dateopts = $wgLang->getDatePreferences();
- $togs = User::getToggles();
-
- $titleObj = SpecialPage::getTitleFor( 'Preferences' );
-
- # Pre-expire some toggles so they won't show if disabled
- $this->mUsedToggles[ 'shownumberswatching' ] = true;
- $this->mUsedToggles[ 'showupdated' ] = true;
- $this->mUsedToggles[ 'enotifwatchlistpages' ] = true;
- $this->mUsedToggles[ 'enotifusertalkpages' ] = true;
- $this->mUsedToggles[ 'enotifminoredits' ] = true;
- $this->mUsedToggles[ 'enotifrevealaddr' ] = true;
- $this->mUsedToggles[ 'ccmeonemails' ] = true;
- $this->mUsedToggles[ 'uselivepreview' ] = true;
- $this->mUsedToggles[ 'noconvertlink' ] = true;
-
-
- if ( !$this->mEmailFlag ) { $emfc = 'checked="checked"'; }
- else { $emfc = ''; }
-
-
- if ($wgEmailAuthentication && ($this->mUserEmail != '') ) {
- if( $wgUser->getEmailAuthenticationTimestamp() ) {
- // date and time are separate parameters to facilitate localisation.
- // $time is kept for backward compat reasons.
- // 'emailauthenticated' is also used in SpecialConfirmemail.php
- $time = $wgLang->timeAndDate( $wgUser->getEmailAuthenticationTimestamp(), true );
- $d = $wgLang->date( $wgUser->getEmailAuthenticationTimestamp(), true );
- $t = $wgLang->time( $wgUser->getEmailAuthenticationTimestamp(), true );
- $emailauthenticated = wfMsg('emailauthenticated', $time, $d, $t ).'<br />';
- $disableEmailPrefs = false;
- } else {
- $disableEmailPrefs = true;
- $skin = $wgUser->getSkin();
- $emailauthenticated = wfMsg('emailnotauthenticated').'<br />' .
- $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Confirmemail' ),
- wfMsg( 'emailconfirmlink' ) ) . '<br />';
- }
- } else {
- $emailauthenticated = '';
- $disableEmailPrefs = false;
- }
-
- if ($this->mUserEmail == '') {
- $emailauthenticated = wfMsg( 'noemailprefs' ) . '<br />';
- }
-
- $ps = $this->namespacesCheckboxes();
-
- $enotifwatchlistpages = ($wgEnotifWatchlist) ? $this->getToggle( 'enotifwatchlistpages', false, $disableEmailPrefs ) : '';
- $enotifusertalkpages = ($wgEnotifUserTalk) ? $this->getToggle( 'enotifusertalkpages', false, $disableEmailPrefs ) : '';
- $enotifminoredits = ($wgEnotifWatchlist && $wgEnotifMinorEdits) ? $this->getToggle( 'enotifminoredits', false, $disableEmailPrefs ) : '';
- $enotifrevealaddr = (($wgEnotifWatchlist || $wgEnotifUserTalk) && $wgEnotifRevealEditorAddress) ? $this->getToggle( 'enotifrevealaddr', false, $disableEmailPrefs ) : '';
-
- # </FIXME>
-
- $wgOut->addHTML(
- Xml::openElement( 'form', array(
- 'action' => $titleObj->getLocalUrl(),
- 'method' => 'post',
- 'id' => 'mw-preferences-form',
- ) ) .
- Xml::openElement( 'div', array( 'id' => 'preferences' ) )
- );
-
- # User data
-
- $wgOut->addHTML(
- Xml::fieldset( wfMsg('prefs-personal') ) .
- Xml::openElement( 'table' ) .
- $this->tableRow( Xml::element( 'h2', null, wfMsg( 'prefs-personal' ) ) )
- );
-
- # Get groups to which the user belongs
- $userEffectiveGroups = $wgUser->getEffectiveGroups();
- $userEffectiveGroupsArray = array();
- foreach( $userEffectiveGroups as $ueg ) {
- if( $ueg == '*' ) {
- // Skip the default * group, seems useless here
- continue;
- }
- $userEffectiveGroupsArray[] = User::makeGroupLinkHTML( $ueg );
- }
- asort( $userEffectiveGroupsArray );
-
- $sk = $wgUser->getSkin();
- $toolLinks = array();
- $toolLinks[] = $sk->makeKnownLinkObj( SpecialPage::getTitleFor( 'ListGroupRights' ), wfMsg( 'listgrouprights' ) );
- # At the moment one tool link only but be prepared for the future...
- # FIXME: Add a link to Special:Userrights for users who are allowed to use it.
- # $wgUser->isAllowed( 'userrights' ) seems to strict in some cases
-
- $userInformationHtml =
- $this->tableRow( wfMsgHtml( 'username' ), htmlspecialchars( $wgUser->getName() ) ) .
- $this->tableRow( wfMsgHtml( 'uid' ), htmlspecialchars( $wgUser->getId() ) ) .
-
- $this->tableRow(
- wfMsgExt( 'prefs-memberingroups', array( 'parseinline' ), count( $userEffectiveGroupsArray ) ),
- $wgLang->commaList( $userEffectiveGroupsArray ) .
- '<br />(' . $wgLang->pipeList( $toolLinks ) . ')'
- ) .
-
- $this->tableRow(
- wfMsgHtml( 'prefs-edits' ),
- $wgLang->formatNum( $wgUser->getEditCount() )
- );
-
- if( wfRunHooks( 'PreferencesUserInformationPanel', array( $this, &$userInformationHtml ) ) ) {
- $wgOut->addHTML( $userInformationHtml );
- }
-
- if ( $wgAllowRealName ) {
- $wgOut->addHTML(
- $this->tableRow(
- Xml::label( wfMsg('yourrealname'), 'wpRealName' ),
- Xml::input( 'wpRealName', 25, $this->mRealName, array( 'id' => 'wpRealName' ) ),
- Xml::tags('div', array( 'class' => 'prefsectiontip' ),
- wfMsgExt( 'prefs-help-realname', 'parseinline' )
- )
- )
- );
- }
- if ( $wgEnableEmail ) {
- $wgOut->addHTML(
- $this->tableRow(
- Xml::label( wfMsg('youremail'), 'wpUserEmail' ),
- Xml::input( 'wpUserEmail', 25, $this->mUserEmail, array( 'id' => 'wpUserEmail' ) ),
- Xml::tags('div', array( 'class' => 'prefsectiontip' ),
- wfMsgExt( $wgEmailConfirmToEdit ? 'prefs-help-email-required' : 'prefs-help-email', 'parseinline' )
- )
- )
- );
- }
-
- global $wgParser, $wgMaxSigChars;
- if( mb_strlen( $this->mNick ) > $wgMaxSigChars ) {
- $invalidSig = $this->tableRow(
- '&nbsp;',
- Xml::element( 'span', array( 'class' => 'error' ),
- wfMsgExt( 'badsiglength', 'parsemag', $wgLang->formatNum( $wgMaxSigChars ) ) )
- );
- } elseif( !empty( $this->mToggles['fancysig'] ) &&
- false === $wgParser->validateSig( $this->mNick ) ) {
- $invalidSig = $this->tableRow(
- '&nbsp;',
- Xml::element( 'span', array( 'class' => 'error' ), wfMsg( 'badsig' ) )
- );
- } else {
- $invalidSig = '';
- }
-
- $wgOut->addHTML(
- $this->tableRow(
- Xml::label( wfMsg( 'yournick' ), 'wpNick' ),
- Xml::input( 'wpNick', 25, $this->mNick,
- array(
- 'id' => 'wpNick',
- // Note: $wgMaxSigChars is enforced in Unicode characters,
- // both on the backend and now in the browser.
- // Badly-behaved requests may still try to submit
- // an overlong string, however.
- 'maxlength' => $wgMaxSigChars ) )
- ) .
- $invalidSig .
- $this->tableRow( '&nbsp;', $this->getToggle( 'fancysig' ) )
- );
-
- $gender = new XMLSelect( 'wpGender', 'wpGender', $this->mGender );
- $gender->addOption( wfMsg( 'gender-unknown' ), 'unknown' );
- $gender->addOption( wfMsg( 'gender-male' ), 'male' );
- $gender->addOption( wfMsg( 'gender-female' ), 'female' );
-
- $wgOut->addHTML(
- $this->tableRow(
- Xml::label( wfMsg( 'yourgender' ), 'wpGender' ),
- $gender->getHTML(),
- Xml::tags( 'div', array( 'class' => 'prefsectiontip' ),
- wfMsgExt( 'prefs-help-gender', 'parseinline' )
- )
- )
- );
-
- list( $lsLabel, $lsSelect) = Xml::languageSelector( $this->mUserLanguage, false );
- $wgOut->addHTML(
- $this->tableRow( $lsLabel, $lsSelect )
- );
-
- /* see if there are multiple language variants to choose from*/
- if(!$wgDisableLangConversion) {
- $variants = $wgContLang->getVariants();
- $variantArray = array();
-
- $languages = Language::getLanguageNames( true );
- foreach($variants as $v) {
- $v = str_replace( '_', '-', strtolower($v));
- if( array_key_exists( $v, $languages ) ) {
- // If it doesn't have a name, we'll pretend it doesn't exist
- $variantArray[$v] = $languages[$v];
- }
- }
-
- $options = "\n";
- foreach( $variantArray as $code => $name ) {
- $selected = ($code == $this->mUserVariant);
- $options .= Xml::option( "$code - $name", $code, $selected ) . "\n";
- }
-
- if(count($variantArray) > 1) {
- $wgOut->addHTML(
- $this->tableRow(
- Xml::label( wfMsg( 'yourvariant' ), 'wpUserVariant' ),
- Xml::tags( 'select',
- array( 'name' => 'wpUserVariant', 'id' => 'wpUserVariant' ),
- $options
- )
- )
- );
- }
-
- if(count($variantArray) > 1 && !$wgDisableLangConversion && !$wgDisableTitleConversion) {
- $wgOut->addHTML(
- Xml::tags( 'tr', null,
- Xml::tags( 'td', array( 'colspan' => '2' ),
- $this->getToggle( "noconvertlink" )
- )
- )
- );
- }
- }
-
- # Password
- if( $wgAuth->allowPasswordChange() ) {
- $link = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'ResetPass' ), wfMsgHtml( 'prefs-resetpass' ),
- array() , array( 'returnto' => SpecialPage::getTitleFor( 'Preferences' )->getPrefixedText() ) );
- $wgOut->addHTML(
- $this->tableRow( Xml::element( 'h2', null, wfMsg( 'changepassword' ) ) ) .
- $this->tableRow( '<ul><li>' . $link . '</li></ul>' ) );
- }
-
- # <FIXME>
- # Enotif
- if ( $wgEnableEmail ) {
-
- $moreEmail = '';
- if ($wgEnableUserEmail) {
- // fixme -- the "allowemail" pseudotoggle is a hacked-together
- // inversion for the "disableemail" preference.
- $emf = wfMsg( 'allowemail' );
- $disabled = $disableEmailPrefs ? ' disabled="disabled"' : '';
- $moreEmail =
- "<input type='checkbox' $emfc $disabled value='1' name='wpEmailFlag' id='wpEmailFlag' /> <label for='wpEmailFlag'>$emf</label>" .
- $this->getToggle( 'ccmeonemails', '', $disableEmailPrefs );
- }
-
-
- $wgOut->addHTML(
- $this->tableRow( Xml::element( 'h2', null, wfMsg( 'email' ) ) ) .
- $this->tableRow(
- $emailauthenticated.
- $enotifrevealaddr.
- $enotifwatchlistpages.
- $enotifusertalkpages.
- $enotifminoredits.
- $moreEmail
- )
- );
- }
- # </FIXME>
-
- $wgOut->addHTML(
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' )
- );
-
-
- # Quickbar
- #
- if ($this->mSkin == 'cologneblue' || $this->mSkin == 'standard') {
- $wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg( 'qbsettings' ) . "</legend>\n" );
- for ( $i = 0; $i < count( $qbs ); ++$i ) {
- if ( $i == $this->mQuickbar ) { $checked = ' checked="checked"'; }
- else { $checked = ""; }
- $wgOut->addHTML( "<div><label><input type='radio' name='wpQuickbar' value=\"$i\"$checked />{$qbs[$i]}</label></div>\n" );
- }
- $wgOut->addHTML( "</fieldset>\n\n" );
- } else {
- # Need to output a hidden option even if the relevant skin is not in use,
- # otherwise the preference will get reset to 0 on submit
- $wgOut->addHTML( Xml::hidden( 'wpQuickbar', $this->mQuickbar ) );
- }
-
- # Skin
- #
- global $wgAllowUserSkin;
- if( $wgAllowUserSkin ) {
- $wgOut->addHTML( "<fieldset>\n<legend>\n" . wfMsg( 'skin' ) . "</legend>\n" );
- $mptitle = Title::newMainPage();
- $previewtext = wfMsg( 'skin-preview' );
- # Only show members of Skin::getSkinNames() rather than
- # $skinNames (skins is all skin names from Language.php)
- $validSkinNames = Skin::getUsableSkins();
- # Sort by UI skin name. First though need to update validSkinNames as sometimes
- # the skinkey & UI skinname differ (e.g. "standard" skinkey is "Classic" in the UI).
- foreach ( $validSkinNames as $skinkey => &$skinname ) {
- $msgName = "skinname-{$skinkey}";
- $localisedSkinName = wfMsg( $msgName );
- if ( !wfEmptyMsg( $msgName, $localisedSkinName ) ) {
- $skinname = $localisedSkinName;
- }
- }
- asort($validSkinNames);
- foreach( $validSkinNames as $skinkey => $sn ) {
- $checked = $skinkey == $this->mSkin ? ' checked="checked"' : '';
- $mplink = htmlspecialchars( $mptitle->getLocalURL( "useskin=$skinkey" ) );
- $previewlink = "(<a target='_blank' href=\"$mplink\">$previewtext</a>)";
- $extraLinks = '';
- global $wgAllowUserCss, $wgAllowUserJs;
- if( $wgAllowUserCss ) {
- $cssPage = Title::makeTitleSafe( NS_USER, $wgUser->getName().'/'.$skinkey.'.css' );
- $customCSS = $sk->makeLinkObj( $cssPage, wfMsgExt('prefs-custom-css', array() ) );
- $extraLinks .= " ($customCSS)";
- }
- if( $wgAllowUserJs ) {
- $jsPage = Title::makeTitleSafe( NS_USER, $wgUser->getName().'/'.$skinkey.'.js' );
- $customJS = $sk->makeLinkObj( $jsPage, wfMsgHtml('prefs-custom-js') );
- $extraLinks .= " ($customJS)";
- }
- if( $skinkey == $wgDefaultSkin )
- $sn .= ' (' . wfMsg( 'default' ) . ')';
- $wgOut->addHTML( "<input type='radio' name='wpSkin' id=\"wpSkin$skinkey\" value=\"$skinkey\"$checked />
- <label for=\"wpSkin$skinkey\">{$sn}</label> $previewlink{$extraLinks}<br />\n" );
- }
- $wgOut->addHTML( "</fieldset>\n\n" );
- }
-
- # Math
- #
- global $wgUseTeX;
- if( $wgUseTeX ) {
- $wgOut->addHTML( "<fieldset>\n<legend>" . wfMsg('math') . '</legend>' );
- foreach ( $mathopts as $k => $v ) {
- $checked = ($k == $this->mMath);
- $wgOut->addHTML(
- Xml::openElement( 'div' ) .
- Xml::radioLabel( wfMsg( $v ), 'wpMath', $k, "mw-sp-math-$k", $checked ) .
- Xml::closeElement( 'div' ) . "\n"
- );
- }
- $wgOut->addHTML( "</fieldset>\n\n" );
- }
-
- # Files
- #
- $imageLimitOptions = null;
- foreach ( $wgImageLimits as $index => $limits ) {
- $selected = ($index == $this->mImageSize);
- $imageLimitOptions .= Xml::option( "{$limits[0]}×{$limits[1]}" .
- wfMsg('unit-pixel'), $index, $selected );
- }
-
- $imageThumbOptions = null;
- foreach ( $wgThumbLimits as $index => $size ) {
- $selected = ($index == $this->mThumbSize);
- $imageThumbOptions .= Xml::option($size . wfMsg('unit-pixel'), $index,
- $selected);
- }
-
- $imageSizeId = 'wpImageSize';
- $thumbSizeId = 'wpThumbSize';
- $wgOut->addHTML(
- Xml::fieldset( wfMsg( 'files' ) ) . "\n" .
- Xml::openElement( 'table' ) .
- '<tr>
- <td class="mw-label">' .
- Xml::label( wfMsg( 'imagemaxsize' ), $imageSizeId ) .
- '</td>
- <td class="mw-input">' .
- Xml::openElement( 'select', array( 'name' => $imageSizeId, 'id' => $imageSizeId ) ) .
- $imageLimitOptions .
- Xml::closeElement( 'select' ) .
- '</td>
- </tr><tr>
- <td class="mw-label">' .
- Xml::label( wfMsg( 'thumbsize' ), $thumbSizeId ) .
- '</td>
- <td class="mw-input">' .
- Xml::openElement( 'select', array( 'name' => $thumbSizeId, 'id' => $thumbSizeId ) ) .
- $imageThumbOptions .
- Xml::closeElement( 'select' ) .
- '</td>
- </tr>' .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' )
- );
-
- # Date format
- #
- # Date/Time
- #
-
- $wgOut->addHTML(
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'datetime' ) ) . "\n"
- );
-
- if ($dateopts) {
- $wgOut->addHTML(
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'dateformat' ) ) . "\n"
- );
- $idCnt = 0;
- $epoch = '20010115161234'; # Wikipedia day
- foreach( $dateopts as $key ) {
- if( $key == 'default' ) {
- $formatted = wfMsg( 'datedefault' );
- } else {
- $formatted = $wgLang->timeanddate( $epoch, false, $key );
- }
- $wgOut->addHTML(
- Xml::tags( 'div', null,
- Xml::radioLabel( $formatted, 'wpDate', $key, "wpDate$idCnt", $key == $this->mDate )
- ) . "\n"
- );
- $idCnt++;
- }
- $wgOut->addHTML( Xml::closeElement( 'fieldset' ) . "\n" );
- }
-
- $nowlocal = Xml::openElement( 'span', array( 'id' => 'wpLocalTime' ) ) .
- $wgLang->time( $now = wfTimestampNow(), true ) .
- Xml::closeElement( 'span' );
- $nowserver = $wgLang->time( $now, false ) .
- Xml::hidden( 'wpServerTime', substr( $now, 8, 2 ) * 60 + substr( $now, 10, 2 ) );
-
- $wgOut->addHTML(
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'timezonelegend' ) ) .
- Xml::openElement( 'table' ) .
- $this->addRow( wfMsg( 'servertime' ), $nowserver ) .
- $this->addRow( wfMsg( 'localtime' ), $nowlocal )
- );
- $opt = Xml::openElement( 'select', array(
- 'name' => 'wpTimeZone',
- 'id' => 'wpTimeZone',
- 'onchange' => 'javascript:updateTimezoneSelection(false)' ) );
- $opt .= Xml::option( wfMsg( 'timezoneuseserverdefault' ), "System|$wgLocalTZoffset", $this->mTimeZone === "System|$wgLocalTZoffset" );
- $opt .= Xml::option( wfMsg( 'timezoneuseoffset' ), 'Offset', $this->mTimeZone === 'Offset' );
-
- if ( function_exists( 'timezone_identifiers_list' ) ) {
- # Read timezone list
- $tzs = timezone_identifiers_list();
- sort( $tzs );
-
- # Precache localized region names
- $tzRegions = array();
- $tzRegions['Africa'] = wfMsg( 'timezoneregion-africa' );
- $tzRegions['America'] = wfMsg( 'timezoneregion-america' );
- $tzRegions['Antarctica'] = wfMsg( 'timezoneregion-antarctica' );
- $tzRegions['Arctic'] = wfMsg( 'timezoneregion-arctic' );
- $tzRegions['Asia'] = wfMsg( 'timezoneregion-asia' );
- $tzRegions['Atlantic'] = wfMsg( 'timezoneregion-atlantic' );
- $tzRegions['Australia'] = wfMsg( 'timezoneregion-australia' );
- $tzRegions['Europe'] = wfMsg( 'timezoneregion-europe' );
- $tzRegions['Indian'] = wfMsg( 'timezoneregion-indian' );
- $tzRegions['Pacific'] = wfMsg( 'timezoneregion-pacific' );
- asort( $tzRegions );
-
- $selZone = explode( '|', $this->mTimeZone, 3 );
- $selZone = ( $selZone[0] == 'ZoneInfo' ) ? $selZone[2] : null;
- $now = date_create( 'now' );
- $optgroup = '';
-
- foreach ( $tzs as $tz ) {
- $z = explode( '/', $tz, 2 );
-
- # timezone_identifiers_list() returns a number of
- # backwards-compatibility entries. This filters them out of the
- # list presented to the user.
- if ( count( $z ) != 2 || !array_key_exists( $z[0], $tzRegions ) )
- continue;
-
- # Localize region
- $z[0] = $tzRegions[$z[0]];
-
- # Create region groups
- if ( $optgroup != $z[0] ) {
- if ( $optgroup !== '' ) {
- $opt .= Xml::closeElement( 'optgroup' );
- }
- $optgroup = $z[0];
- $opt .= Xml::openElement( 'optgroup', array( 'label' => $z[0] ) ) . "\n";
- }
-
- $minDiff = floor( timezone_offset_get( timezone_open( $tz ), $now ) / 60 );
- $opt .= Xml::option( str_replace( '_', ' ', $z[0] . '/' . $z[1] ), "ZoneInfo|$minDiff|$tz", $selZone === $tz, array( 'label' => $z[1] ) ) . "\n";
- }
- if ( $optgroup !== '' ) $opt .= Xml::closeElement( 'optgroup' );
- }
- $opt .= Xml::closeElement( 'select' );
- $wgOut->addHTML(
- $this->addRow(
- Xml::label( wfMsg( 'timezoneselect' ), 'wpTimeZone' ),
- $opt )
- );
- $wgOut->addHTML(
- $this->addRow(
- Xml::label( wfMsg( 'timezoneoffset' ), 'wpHourDiff' ),
- Xml::input( 'wpHourDiff', 6, $this->mHourDiff, array(
- 'id' => 'wpHourDiff',
- 'onfocus' => 'javascript:updateTimezoneSelection(true)',
- 'onblur' => 'javascript:updateTimezoneSelection(false)' ) ) ) .
- "<tr>
- <td></td>
- <td class='mw-submit'>" .
- Xml::element( 'input',
- array( 'type' => 'button',
- 'value' => wfMsg( 'guesstimezone' ),
- 'onclick' => 'javascript:guessTimezone()',
- 'id' => 'guesstimezonebutton',
- 'style' => 'display:none;' ) ) .
- "</td>
- </tr>" .
- Xml::closeElement( 'table' ) .
- Xml::tags( 'div', array( 'class' => 'prefsectiontip' ), wfMsgExt( 'timezonetext', 'parseinline' ) ).
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'fieldset' ) . "\n\n"
- );
-
- # Editing
- #
- global $wgLivePreview;
- $wgOut->addHTML(
- Xml::fieldset( wfMsg( 'textboxsize' ) ) .
- wfMsgHTML( 'prefs-edit-boxsize' ) . ' ' .
- Xml::inputLabel( wfMsg( 'rows' ), 'wpRows', 'wpRows', 3, $this->mRows ) . ' ' .
- Xml::inputLabel( wfMsg( 'columns' ), 'wpCols', 'wpCols', 3, $this->mCols ) .
- $this->getToggles( array(
- 'editsection',
- 'editsectiononrightclick',
- 'editondblclick',
- 'editwidth',
- 'showtoolbar',
- 'previewonfirst',
- 'previewontop',
- 'minordefault',
- 'externaleditor',
- 'externaldiff',
- $wgLivePreview ? 'uselivepreview' : false,
- 'forceeditsummary',
- ) )
- );
-
- $wgOut->addHTML( Xml::closeElement( 'fieldset' ) );
-
- # Recent changes
- global $wgRCMaxAge, $wgUseRCPatrol;
- $wgOut->addHTML(
- Xml::fieldset( wfMsg( 'prefs-rc' ) ) .
- Xml::openElement( 'table' ) .
- '<tr>
- <td class="mw-label">' .
- Xml::label( wfMsg( 'recentchangesdays' ), 'wpRecentDays' ) .
- '</td>
- <td class="mw-input">' .
- Xml::input( 'wpRecentDays', 3, $this->mRecentDays, array( 'id' => 'wpRecentDays' ) ) . ' ' .
- wfMsgExt( 'recentchangesdays-max', 'parsemag',
- $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600 * 24 ) ) ) ) .
- '</td>
- </tr><tr>
- <td class="mw-label">' .
- Xml::label( wfMsg( 'recentchangescount' ), 'wpRecent' ) .
- '</td>
- <td class="mw-input">' .
- Xml::input( 'wpRecent', 3, $this->mRecent, array( 'id' => 'wpRecent' ) ) .
- '</td>
- </tr>' .
- Xml::closeElement( 'table' ) .
- '<br />'
- );
-
- $toggles[] = 'hideminor';
- if( $wgUseRCPatrol ) {
- $toggles[] = 'hidepatrolled';
- $toggles[] = 'newpageshidepatrolled';
- }
- if( $wgRCShowWatchingUsers ) $toggles[] = 'shownumberswatching';
- $toggles[] = 'usenewrc';
-
- $wgOut->addHTML(
- $this->getToggles( $toggles ) .
- Xml::closeElement( 'fieldset' )
- );
-
- # Watchlist
- $watchlistToggles = array( 'watchlisthideminor', 'watchlisthidebots', 'watchlisthideown',
- 'watchlisthideanons', 'watchlisthideliu' );
- if( $wgUseRCPatrol ) $watchlistToggles[] = 'watchlisthidepatrolled';
-
- $wgOut->addHTML(
- Xml::fieldset( wfMsg( 'prefs-watchlist' ) ) .
- Xml::inputLabel( wfMsg( 'prefs-watchlist-days' ), 'wpWatchlistDays', 'wpWatchlistDays', 3, $this->mWatchlistDays ) . ' ' .
- wfMsgHTML( 'prefs-watchlist-days-max' ) .
- '<br /><br />' .
- $this->getToggle( 'extendwatchlist' ) .
- Xml::inputLabel( wfMsg( 'prefs-watchlist-edits' ), 'wpWatchlistEdits', 'wpWatchlistEdits', 3, $this->mWatchlistEdits ) . ' ' .
- wfMsgHTML( 'prefs-watchlist-edits-max' ) .
- '<br /><br />' .
- $this->getToggles( $watchlistToggles )
- );
-
- if( $wgUser->isAllowed( 'createpage' ) || $wgUser->isAllowed( 'createtalk' ) ) {
- $wgOut->addHTML( $this->getToggle( 'watchcreations' ) );
- }
-
- foreach( array( 'edit' => 'watchdefault', 'move' => 'watchmoves', 'delete' => 'watchdeletion' ) as $action => $toggle ) {
- if( $wgUser->isAllowed( $action ) )
- $wgOut->addHTML( $this->getToggle( $toggle ) );
- }
- $this->mUsedToggles['watchcreations'] = true;
- $this->mUsedToggles['watchdefault'] = true;
- $this->mUsedToggles['watchmoves'] = true;
- $this->mUsedToggles['watchdeletion'] = true;
-
- $wgOut->addHTML( Xml::closeElement( 'fieldset' ) );
-
- # Search
- $mwsuggest = $wgEnableMWSuggest ?
- $this->addRow(
- Xml::label( wfMsg( 'mwsuggest-disable' ), 'wpDisableMWSuggest' ),
- Xml::check( 'wpDisableMWSuggest', $this->mDisableMWSuggest, array( 'id' => 'wpDisableMWSuggest' ) )
- ) : '';
- $wgOut->addHTML(
- // Elements for the search tab itself
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'searchresultshead' ) ) .
- // Elements for the search options in the search tab
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'prefs-searchoptions' ) ) .
- Xml::openElement( 'table' ) .
- $this->addRow(
- Xml::label( wfMsg( 'resultsperpage' ), 'wpSearch' ),
- Xml::input( 'wpSearch', 4, $this->mSearch, array( 'id' => 'wpSearch' ) )
- ) .
- $this->addRow(
- Xml::label( wfMsg( 'contextlines' ), 'wpSearchLines' ),
- Xml::input( 'wpSearchLines', 4, $this->mSearchLines, array( 'id' => 'wpSearchLines' ) )
- ) .
- $this->addRow(
- Xml::label( wfMsg( 'contextchars' ), 'wpSearchChars' ),
- Xml::input( 'wpSearchChars', 4, $this->mSearchChars, array( 'id' => 'wpSearchChars' ) )
- ) .
- $mwsuggest .
- Xml::closeElement( 'table' ) .
- Xml::closeElement( 'fieldset' ) .
- // Elements for the namespace options in the search tab
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'prefs-namespaces' ) ) .
- wfMsgExt( 'defaultns', array( 'parse' ) ) .
- $ps .
- Xml::closeElement( 'fieldset' ) .
- // End of the search tab
- Xml::closeElement( 'fieldset' )
- );
-
- # Misc
- #
- $uopt = $wgUser->getOption( 'underline' );
- $wgOut->addHTML(
- Xml::fieldset( wfMsg( 'prefs-misc' ) ) .
- Xml::openElement( 'table' ) .
- '<tr>
- <td class="mw-label">' .
- // Xml::label() cannot be used because 'stub-threshold' contains plain HTML
- Xml::tags( 'label', array( 'for' => 'wpStubs' ), wfMsg( 'stub-threshold' ) ) .
- '</td>
- <td class="mw-input">' .
- Xml::input( 'wpStubs', 6, $this->mStubs, array( 'id' => 'wpStubs' ) ) .
- '</td>
- </tr><tr>
- <td class="mw-label">' .
- Xml::label( wfMsg( 'tog-underline' ), 'wpOpunderline' ) .
- '</td>
- <td class="mw-input">' .
- Xml::openElement( 'select', array( 'id' => 'wpOpunderline', 'name' => 'wpOpunderline' ) ) .
- Xml::option( wfMsg ( 'underline-never' ), '0', $uopt == 0 ) .
- Xml::option( wfMsg ( 'underline-always' ), '1', $uopt == 1 ) .
- Xml::option( wfMsg ( 'underline-default' ), '2', $uopt == 2 ) .
- Xml::closeElement( 'select' ) .
- '</td>
- </tr>' .
- Xml::closeElement( 'table' )
- );
-
- # And now the rest = Misc.
- foreach ( $togs as $tname ) {
- if( !array_key_exists( $tname, $this->mUsedToggles ) ) {
- if( $tname == 'norollbackdiff' && $wgUser->isAllowed( 'rollback' ) )
- $wgOut->addHTML( $this->getToggle( $tname ) );
- else
- $wgOut->addHTML( $this->getToggle( $tname ) );
- }
- }
-
- $wgOut->addHTML( '</fieldset>' );
-
- wfRunHooks( 'RenderPreferencesForm', array( $this, $wgOut ) );
- $token = htmlspecialchars( $wgUser->editToken() );
- $skin = $wgUser->getSkin();
- $rtl = $wgContLang->isRTL() ? 'left' : 'right';
- $wgOut->addHTML( "
- <table id='prefsubmit' cellpadding='0' width='100%' style='background:none;'><tr>
- <td><input type='submit' name='wpSaveprefs' class='btnSavePrefs' value=\"" . wfMsgHtml( 'saveprefs' ) .
- '"'.$skin->tooltipAndAccesskey('save')." />
- <input type='submit' name='wpReset' value=\"" . wfMsgHtml( 'resetprefs' ) . "\" /></td>
- <td align='$rtl'><input type='submit' name='wpRestore' value=\"" . wfMsgHtml( 'restoreprefs' ) . "\" /></td>
- </tr></table>
+ $url = SpecialPage::getTitleFor( 'Preferences' )->getFullURL( 'success' );
- <input type='hidden' name='wpEditToken' value=\"{$token}\" />
- </div></form>\n" );
+ $wgOut->redirect( $url );
- $wgOut->addHTML( Xml::tags( 'div', array( 'class' => "prefcache" ),
- wfMsgExt( 'clearyourcache', 'parseinline' ) )
- );
+ return true;
}
}
diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php
index 680fe343..8b5f0c93 100644
--- a/includes/specials/SpecialPrefixindex.php
+++ b/includes/specials/SpecialPrefixindex.php
@@ -63,7 +63,7 @@ class SpecialPrefixindex extends SpecialAllpages {
Xml::label( wfMsg( 'allpagesprefix' ), 'nsfrom' ) .
"</td>
<td class='mw-input'>" .
- Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
+ Xml::input( 'prefix', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
"</td>
</tr>
<tr>
@@ -115,7 +115,7 @@ class SpecialPrefixindex extends SpecialAllpages {
array( 'page_namespace', 'page_title', 'page_is_redirect' ),
array(
'page_namespace' => $namespace,
- 'page_title LIKE \'' . $dbr->escapeLike( $prefixKey ) .'%\'',
+ 'page_title' . $dbr->buildLike( $prefixKey, $dbr->anyString() ),
'page_title >= ' . $dbr->addQuotes( $fromKey ),
),
__METHOD__,
@@ -136,7 +136,10 @@ class SpecialPrefixindex extends SpecialAllpages {
$t = Title::makeTitle( $s->page_namespace, $s->page_title );
if( $t ) {
$link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
- $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) .
+ $sk->linkKnown(
+ $t,
+ htmlspecialchars( $t->getText() )
+ ) .
($s->page_is_redirect ? '</div>' : '' );
} else {
$link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
@@ -170,17 +173,26 @@ class SpecialPrefixindex extends SpecialAllpages {
$nsForm .
'</td>
<td id="mw-prefixindex-nav-form">' .
- $sk->makeKnownLinkObj( $self, wfMsg ( 'allpages' ) );
+ $sk->linkKnown( $self, wfMsgHtml( 'allpages' ) );
if( isset( $res ) && $res && ( $n == $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
- $namespaceparam = $namespace ? "&namespace=$namespace" : "";
+ $query = array(
+ 'from' => $s->page_title,
+ 'prefix' => $prefix
+ );
+
+ if( $namespace ) {
+ $query['namespace'] = $namespace;
+ }
+
$out2 = $wgLang->pipeList( array(
$out2,
- $sk->makeKnownLinkObj(
+ $sk->linkKnown(
$self,
wfMsgHtml( 'nextpage', str_replace( '_',' ', htmlspecialchars( $s->page_title ) ) ),
- "from=" . wfUrlEncode( $s->page_title ) .
- "&prefix=" . wfUrlEncode( $prefix ) . $namespaceparam )
+ array(),
+ $query
+ )
) );
}
$out2 .= "</td></tr>" .
diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php
index a38a8cd1..8229770c 100644
--- a/includes/specials/SpecialProtectedpages.php
+++ b/includes/specials/SpecialProtectedpages.php
@@ -16,7 +16,7 @@ class ProtectedPagesForm {
public function showList( $msg = '' ) {
global $wgOut, $wgRequest;
- if( "" != $msg ) {
+ if( $msg != "" ) {
$wgOut->setSubtitle( $msg );
}
@@ -65,7 +65,7 @@ class ProtectedPagesForm {
$skin = $wgUser->getSkin();
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
- $link = $skin->makeLinkObj( $title );
+ $link = $skin->link( $title );
$description_items = array ();
@@ -86,7 +86,7 @@ class ProtectedPagesForm {
$expiry_description = wfMsg( 'protect-expiring' , $wgLang->timeanddate( $expiry ) ,
$wgLang->date( $expiry ) , $wgLang->time( $expiry ) );
- $description_items[] = $expiry_description;
+ $description_items[] = htmlspecialchars($expiry_description);
}
if(!is_null($size = $row->page_len)) {
@@ -95,17 +95,31 @@ class ProtectedPagesForm {
# Show a link to the change protection form for allowed users otherwise a link to the protection log
if( $wgUser->isAllowed( 'protect' ) ) {
- $changeProtection = ' (' . $skin->makeKnownLinkObj( $title, wfMsgHtml( 'protect_change' ),
- 'action=unprotect' ) . ')';
+ $changeProtection = ' (' . $skin->linkKnown(
+ $title,
+ wfMsgHtml( 'protect_change' ),
+ array(),
+ array( 'action' => 'unprotect' )
+ ) . ')';
} else {
$ltitle = SpecialPage::getTitleFor( 'Log' );
- $changeProtection = ' (' . $skin->makeKnownLinkObj( $ltitle, wfMsgHtml( 'protectlogpage' ),
- 'type=protect&page=' . $title->getPrefixedUrl() ) . ')';
+ $changeProtection = ' (' . $skin->linkKnown(
+ $ltitle,
+ wfMsgHtml( 'protectlogpage' ),
+ array(),
+ array(
+ 'type' => 'protect',
+ 'page' => $title->getPrefixedText()
+ )
+ ) . ')';
}
wfProfileOut( __METHOD__ );
- return '<li>' . wfSpecialList( $link . $stxt, implode( $description_items, ', ' ) ) . $changeProtection . "</li>\n";
+ return Html::rawElement(
+ 'li',
+ array(),
+ wfSpecialList( $link . $stxt, $wgLang->commaList( $description_items ) ) . $changeProtection ) . "\n";
}
/**
@@ -120,7 +134,7 @@ class ProtectedPagesForm {
*/
protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) {
global $wgScript;
- $title = SpecialPage::getTitleFor( 'ProtectedPages' );
+ $title = SpecialPage::getTitleFor( 'Protectedpages' );
return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
Xml::openElement( 'fieldset' ) .
Xml::element( 'legend', array(), wfMsg( 'protectedpages' ) ) .
@@ -128,10 +142,10 @@ class ProtectedPagesForm {
$this->getNamespaceMenu( $namespace ) . "&nbsp;\n" .
$this->getTypeMenu( $type ) . "&nbsp;\n" .
$this->getLevelMenu( $level ) . "&nbsp;\n" .
- "<br/><span style='white-space: nowrap'>" .
+ "<br /><span style='white-space: nowrap'>" .
$this->getExpiryCheck( $indefOnly ) . "&nbsp;\n" .
$this->getCascadeCheck( $cascadeOnly ) . "&nbsp;\n" .
- "</span><br/><span style='white-space: nowrap'>" .
+ "</span><br /><span style='white-space: nowrap'>" .
$this->getSizeLimit( $sizetype, $size ) . "&nbsp;\n" .
"</span>" .
"&nbsp;" . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
@@ -185,6 +199,8 @@ class ProtectedPagesForm {
}
/**
+ * Creates the input label of the restriction type
+ * @param $pr_type string Protection type
* @return string Formatted HTML
*/
protected function getTypeMenu( $pr_type ) {
@@ -213,6 +229,8 @@ class ProtectedPagesForm {
}
/**
+ * Creates the input label of the restriction level
+ * @param $pr_level string Protection level
* @return string Formatted HTML
*/
protected function getLevelMenu( $pr_level ) {
@@ -223,6 +241,7 @@ class ProtectedPagesForm {
// First pass to load the log names
foreach( $wgRestrictionLevels as $type ) {
+ // Messages used can be 'restriction-level-sysop' and 'restriction-level-autoconfirmed'
if( $type !='' && $type !='*') {
$text = wfMsg("restriction-level-$type");
$m[$text] = $type;
@@ -235,11 +254,11 @@ class ProtectedPagesForm {
$options[] = Xml::option( $text, $type, $selected );
}
- return
- Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . '&nbsp;' .
+ return "<span style='white-space: nowrap'>" .
+ Xml::label( wfMsg( 'restriction-level' ) , $this->IdLevel ) . ' ' .
Xml::tags( 'select',
array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ),
- implode( "\n", $options ) );
+ implode( "\n", $options ) ) . "</span>";
}
}
diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php
index 7e8126d9..d65b3f79 100644
--- a/includes/specials/SpecialProtectedtitles.php
+++ b/includes/specials/SpecialProtectedtitles.php
@@ -16,7 +16,7 @@ class ProtectedTitlesForm {
function showList( $msg = '' ) {
global $wgOut, $wgRequest;
- if ( "" != $msg ) {
+ if ( $msg != "" ) {
$wgOut->setSubtitle( $msg );
}
@@ -61,7 +61,7 @@ class ProtectedTitlesForm {
$skin = $wgUser->getSkin();
$title = Title::makeTitleSafe( $row->pt_namespace, $row->pt_title );
- $link = $skin->makeLinkObj( $title );
+ $link = $skin->link( $title );
$description_items = array ();
@@ -94,7 +94,7 @@ class ProtectedTitlesForm {
function showOptions( $namespace, $type='edit', $level, $sizetype, $size ) {
global $wgScript;
$action = htmlspecialchars( $wgScript );
- $title = SpecialPage::getTitleFor( 'ProtectedTitles' );
+ $title = SpecialPage::getTitleFor( 'Protectedtitles' );
$special = htmlspecialchars( $title->getPrefixedDBkey() );
return "<form action=\"$action\" method=\"get\">\n" .
'<fieldset>' .
diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php
index 31199b23..fd3f17f2 100644
--- a/includes/specials/SpecialRandompage.php
+++ b/includes/specials/SpecialRandompage.php
@@ -9,12 +9,12 @@
*/
class RandomPage extends SpecialPage {
private $namespaces; // namespaces to select pages from
+ protected $isRedir = false; // should the result be a redirect?
+ protected $extra = array(); // Extra SQL statements
- function __construct( $name = 'Randompage' ){
+ public function __construct( $name = 'Randompage' ){
global $wgContentNamespaces;
-
$this->namespaces = $wgContentNamespaces;
-
parent::__construct( $name );
}
@@ -28,22 +28,23 @@ class RandomPage extends SpecialPage {
}
// select redirects instead of normal pages?
- // Overriden by SpecialRandomredirect
public function isRedirect(){
- return false;
+ return $this->isRedir;
}
public function execute( $par ) {
global $wgOut, $wgContLang;
- if ($par)
+ if ($par) {
$this->setNamespace( $wgContLang->getNsIndex( $par ) );
+ }
$title = $this->getRandomTitle();
if( is_null( $title ) ) {
$this->setHeaders();
- $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages', $wgContLang->getNsText( $this->namespace ) );
+ $wgOut->addWikiMsg( strtolower( $this->mName ) . '-nopages',
+ $this->getNsList(), count( $this->namespaces ) );
return;
}
@@ -51,6 +52,23 @@ class RandomPage extends SpecialPage {
$wgOut->redirect( $title->getFullUrl( $query ) );
}
+ /**
+ * Get a comma-delimited list of namespaces we don't have
+ * any pages in
+ * @return String
+ */
+ private function getNsList() {
+ global $wgContLang;
+ $nsNames = array();
+ foreach( $this->namespaces as $n ) {
+ if( $n === NS_MAIN )
+ $nsNames[] = wfMsgForContent( 'blanknamespace' );
+ else
+ $nsNames[] = $wgContLang->getNsText( $n );
+ }
+ return $wgContLang->commaList( $nsNames );
+ }
+
/**
* Choose a random title.
@@ -58,6 +76,10 @@ class RandomPage extends SpecialPage {
*/
public function getRandomTitle() {
$randstr = wfRandom();
+ $title = null;
+ if ( !wfRunHooks( 'SpecialRandomGetRandomTitle', array( &$randstr, &$this->isRedir, &$this->namespaces, &$this->extra, &$title ) ) ) {
+ return $title;
+ }
$row = $this->selectRandomPageFromDB( $randstr );
/* If we picked a value that was higher than any in
@@ -78,8 +100,6 @@ class RandomPage extends SpecialPage {
private function selectRandomPageFromDB( $randstr ) {
global $wgExtraRandompageSQL;
- $fname = 'RandomPage::selectRandomPageFromDB';
-
$dbr = wfGetDB( DB_SLAVE );
$use_index = $dbr->useIndexClause( 'page_random' );
@@ -87,8 +107,17 @@ class RandomPage extends SpecialPage {
$ns = implode( ",", $this->namespaces );
$redirect = $this->isRedirect() ? 1 : 0;
-
- $extra = $wgExtraRandompageSQL ? "AND ($wgExtraRandompageSQL)" : "";
+
+ 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 )
@@ -98,7 +127,15 @@ class RandomPage extends SpecialPage {
ORDER BY page_random";
$sql = $dbr->limitResult( $sql, 1, 0 );
- $res = $dbr->query( $sql, $fname );
+ $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/SpecialRandomredirect.php b/includes/specials/SpecialRandomredirect.php
index 629d5b3c..28cb2aae 100644
--- a/includes/specials/SpecialRandomredirect.php
+++ b/includes/specials/SpecialRandomredirect.php
@@ -10,10 +10,7 @@
class SpecialRandomredirect extends RandomPage {
function __construct(){
parent::__construct( 'Randomredirect' );
+ $this->isRedir = true;
}
- // Override parent::isRedirect()
- public function isRedirect(){
- return true;
- }
}
diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php
index 91c0ecbe..283eeaf4 100644
--- a/includes/specials/SpecialRecentchanges.php
+++ b/includes/specials/SpecialRecentchanges.php
@@ -5,6 +5,8 @@
* @ingroup SpecialPage
*/
class SpecialRecentChanges extends SpecialPage {
+ var $rcOptions, $rcSubpage;
+
public function __construct() {
parent::__construct( 'Recentchanges' );
$this->includable( true );
@@ -40,7 +42,7 @@ class SpecialRecentChanges extends SpecialPage {
}
/**
- * Get a FormOptions object with options as specified by the user
+ * Create a FormOptions object with options as specified by the user
*
* @return FormOptions
*/
@@ -55,31 +57,45 @@ class SpecialRecentChanges extends SpecialPage {
$this->parseParameters( $parameters, $opts );
}
- $opts->validateIntBounds( 'limit', 0, 500 );
+ $opts->validateIntBounds( 'limit', 0, 5000 );
return $opts;
}
/**
- * Get a FormOptions object sepcific for feed requests
+ * Create a FormOptions object specific for feed requests and return it
*
* @return FormOptions
*/
public function feedSetup() {
global $wgFeedLimit, $wgRequest;
$opts = $this->getDefaultOptions();
- # Feed is cached on limit,hideminor; other params would randomly not work
- $opts->fetchValuesFromRequest( $wgRequest, array( 'limit', 'hideminor' ) );
+ # Feed is cached on limit,hideminor,namespace; other params would randomly not work
+ $opts->fetchValuesFromRequest( $wgRequest, array( 'limit', 'hideminor', 'namespace' ) );
$opts->validateIntBounds( 'limit', 0, $wgFeedLimit );
return $opts;
}
/**
+ * Get the current FormOptions for this request
+ */
+ public function getOptions() {
+ if ( $this->rcOptions === null ) {
+ global $wgRequest;
+ $feedFormat = $wgRequest->getVal( 'feed' );
+ $this->rcOptions = $feedFormat ? $this->feedSetup() : $this->setup( $this->rcSubpage );
+ }
+ return $this->rcOptions;
+ }
+
+
+ /**
* Main execution point
*
- * @param $parameters string
+ * @param $subpage string
*/
- public function execute( $parameters ) {
+ public function execute( $subpage ) {
global $wgRequest, $wgOut;
+ $this->rcSubpage = $subpage;
$feedFormat = $wgRequest->getVal( 'feed' );
# 10 seconds server-side caching max
@@ -90,12 +106,11 @@ class SpecialRecentChanges extends SpecialPage {
return;
}
- $opts = $feedFormat ? $this->feedSetup() : $this->setup( $parameters );
+ $opts = $this->getOptions();
$this->setHeaders();
$this->outputHeader();
// Fetch results, prepare a batch link existence check query
- $rows = array();
$conds = $this->buildMainQueryConds( $opts );
$rows = $this->doMainQuery( $conds, $opts );
if( $rows === false ){
@@ -114,10 +129,9 @@ class SpecialRecentChanges extends SpecialPage {
}
$batch->execute();
}
- $target = isset($opts['target']) ? $opts['target'] : ''; // RCL has targets
if( $feedFormat ) {
- list( $feed, $feedObj ) = $this->getFeedObject( $feedFormat );
- $feed->execute( $feedObj, $rows, $opts['limit'], $opts['hideminor'], $lastmod, $target );
+ list( $changesFeed, $formatter ) = $this->getFeedObject( $feedFormat );
+ $changesFeed->execute( $formatter, $rows, $lastmod, $opts );
} else {
$this->webOutput( $rows, $opts );
}
@@ -131,12 +145,12 @@ class SpecialRecentChanges extends SpecialPage {
* @return array
*/
public function getFeedObject( $feedFormat ){
- $feed = new ChangesFeed( $feedFormat, 'rcfeed' );
- $feedObj = $feed->getFeedObject(
+ $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' );
+ $formatter = $changesFeed->getFeedObject(
wfMsgForContent( 'recentchanges' ),
wfMsgForContent( 'recentchanges-feed-description' )
);
- return array( $feed, $feedObj );
+ return array( $changesFeed, $formatter );
}
/**
@@ -177,7 +191,7 @@ class SpecialRecentChanges extends SpecialPage {
public function checkLastModified( $feedFormat ) {
global $wgUseRCPatrol, $wgOut;
$dbr = wfGetDB( DB_SLAVE );
- $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __FUNCTION__ );
+ $lastmod = $dbr->selectField( 'recentchanges', 'MAX(rc_timestamp)', false, __METHOD__ );
if( $feedFormat || !$wgUseRCPatrol ) {
if( $lastmod && $wgOut->checkLastModified( $lastmod ) ) {
# Client cache fresh and headers sent, nothing more to do.
@@ -278,8 +292,6 @@ class SpecialRecentChanges extends SpecialPage {
$namespace = $opts['namespace'];
$invert = $opts['invert'];
- $join_conds = array();
-
// JOIN on watchlist for users
if( $uid ) {
$tables[] = 'watchlist';
@@ -293,20 +305,23 @@ class SpecialRecentChanges extends SpecialPage {
// Tag stuff.
$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']
- );
-
- wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) );
-
- // Is there either one namespace selected or excluded?
- // Tag filtering also has a better index.
- // Also, if this is "all" or main namespace, just use timestamp index.
- if( is_null($namespace) || $invert || $namespace == NS_MAIN || $opts['tagfilter'] ) {
+ ChangeTags::modifyDisplayQuery(
+ $tables, $fields, $conds, $join_conds, $query_options, $opts['tagfilter']
+ );
+
+ if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options ) ) )
+ 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)
+ // (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
+ || $opts['tagfilter'] != ''
+ || !$dbr->unionSupportsOrderAndLimit() )
+ {
$res = $dbr->select( $tables, '*', $conds, __METHOD__,
array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) +
$query_options,
@@ -318,17 +333,18 @@ class SpecialRecentChanges extends SpecialPage {
array( 'rc_new' => 1 ) + $conds,
__METHOD__,
array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
- 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ),
+ 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ),
$join_conds );
// Old pages
$sqlOld = $dbr->selectSQLText( $tables, '*',
array( 'rc_new' => 0 ) + $conds,
__METHOD__,
array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit,
- 'USE INDEX' => array('recentchanges' => 'new_name_timestamp') ),
+ 'USE INDEX' => array('recentchanges' => 'rc_timestamp') ),
$join_conds );
# Join the two fast queries, and sort the result set
- $sql = "($sqlNew) UNION ($sqlOld) ORDER BY rc_timestamp DESC LIMIT $limit";
+ $sql = $dbr->unionQueries(array($sqlNew, $sqlOld), false).' ORDER BY rc_timestamp DESC';
+ $sql = $dbr->limitResult($sql, $limit, false);
$res = $dbr->query( $sql, __METHOD__ );
}
@@ -353,7 +369,7 @@ class SpecialRecentChanges extends SpecialPage {
}
// And now for the content
- $wgOut->setSyndicated( true );
+ $wgOut->setFeedAppendQuery( $this->getFeedQuery() );
if( $wgAllowCategorizedRecentChanges ) {
$this->filterByCategories( $rows, $opts );
@@ -401,6 +417,14 @@ class SpecialRecentChanges extends SpecialPage {
}
/**
+ * Get the query string to append to feed link URLs.
+ * This is overridden by RCL to add the target parameter
+ */
+ public function getFeedQuery() {
+ return false;
+ }
+
+ /**
* Return the text to be displayed above the changes
*
* @param $opts FormOptions
@@ -413,7 +437,7 @@ class SpecialRecentChanges extends SpecialPage {
$defaults = $opts->getAllValues();
$nondefaults = $opts->getChangedValues();
- $opts->consumeValues( array( 'namespace', 'invert' ) );
+ $opts->consumeValues( array( 'namespace', 'invert', 'tagfilter' ) );
$panel = array();
$panel[] = $this->optionsPanel( $defaults, $nondefaults );
@@ -456,6 +480,8 @@ class SpecialRecentChanges extends SpecialPage {
Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) )
);
+ $wgOut->addHTML( ChangesList::flagLegend() );
+
$this->setBottomText( $wgOut, $opts );
}
@@ -597,8 +623,12 @@ class SpecialRecentChanges extends SpecialPage {
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' ) );
+ 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' ) );
+ }
}
/**
@@ -618,7 +648,9 @@ class SpecialRecentChanges extends SpecialPage {
if( $options['from'] ) {
$note .= wfMsgExt( 'rcnotefrom', array( 'parseinline' ),
$wgLang->formatNum( $options['limit'] ),
- $wgLang->timeanddate( $options['from'], true ) ) . '<br />';
+ $wgLang->timeanddate( $options['from'], true ),
+ $wgLang->date( $options['from'], true ),
+ $wgLang->time( $options['from'], true ) ) . '<br />';
}
# Sort data for display and make sure it's unique after we've added user data.
diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php
index c58ffff0..3b549843 100644
--- a/includes/specials/SpecialRecentchangeslinked.php
+++ b/includes/specials/SpecialRecentchangeslinked.php
@@ -5,6 +5,7 @@
* @ingroup SpecialPage
*/
class SpecialRecentchangeslinked extends SpecialRecentchanges {
+ var $rclTargetTitle;
function __construct(){
SpecialPage::SpecialPage( 'Recentchangeslinked' );
@@ -26,7 +27,6 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
public function feedSetup() {
global $wgRequest;
$opts = parent::feedSetup();
- # Feed is cached on limit,hideminor,target; other params would randomly not work
$opts['target'] = $wgRequest->getVal( 'target' );
return $opts;
}
@@ -34,8 +34,8 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
public function getFeedObject( $feedFormat ){
$feed = new ChangesFeed( $feedFormat, false );
$feedObj = $feed->getFeedObject(
- wfMsgForContent( 'recentchangeslinked-title', $this->mTargetTitle->getPrefixedText() ),
- wfMsgForContent( 'recentchangeslinked' )
+ wfMsgForContent( 'recentchangeslinked-title', $this->getTargetTitle()->getPrefixedText() ),
+ wfMsgForContent( 'recentchangeslinked-feed' )
);
return array( $feed, $feedObj );
}
@@ -52,10 +52,9 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
}
$title = Title::newFromURL( $target );
if( !$title || $title->getInterwiki() != '' ){
- $wgOut->wrapWikiMsg( '<div class="errorbox">$1</div><br clear="both" />', 'allpagesbadtitle' );
+ $wgOut->wrapWikiMsg( "<div class=\"errorbox\">\n$1</div><br style=\"clear: both\" />", 'allpagesbadtitle' );
return false;
}
- $this->mTargetTitle = $title;
$wgOut->setPageTitle( wfMsg( 'recentchangeslinked-title', $title->getPrefixedText() ) );
@@ -84,6 +83,11 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
$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' ) ) {
+ $tables[] = 'page';
+ $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id');
+ $select[] = 'page_latest';
+ }
ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds,
$query_options, $opts['tagfilter'] );
@@ -139,25 +143,37 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
}
}
- $subsql[] = $dbr->selectSQLText(
+ if( $dbr->unionSupportsOrderAndLimit())
+ $order = array( 'ORDER BY' => 'rc_timestamp DESC' );
+ else
+ $order = array();
+
+
+ $query = $dbr->selectSQLText(
array_merge( $tables, array( $link_table ) ),
$select,
$conds + $subconds,
__METHOD__,
- array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit ) + $query_options,
+ $order + $query_options,
$join_conds + array( $link_table => array( 'INNER JOIN', $subjoin ) )
);
+
+ if( $dbr->unionSupportsOrderAndLimit())
+ $query = $dbr->limitResult( $query, $limit );
+
+ $subsql[] = $query;
}
if( count($subsql) == 0 )
return false; // should never happen
- if( count($subsql) == 1 )
+ if( count($subsql) == 1 && $dbr->unionSupportsOrderAndLimit() )
$sql = $subsql[0];
else {
// need to resort and relimit after union
- $sql = "(" . implode( ") UNION (", $subsql ) . ") ORDER BY rc_timestamp DESC LIMIT {$limit}";
+ $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 )
@@ -167,10 +183,10 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
}
function getExtraOptions( $opts ){
- $opts->consumeValues( array( 'showlinkedto', 'target' ) );
+ $opts->consumeValues( array( 'showlinkedto', 'target', 'tagfilter' ) );
$extraOpts = array();
$extraOpts['namespace'] = $this->namespaceFilterForm( $opts );
- $extraOpts['target'] = array( wfMsg( 'recentchangeslinked-page' ),
+ $extraOpts['target'] = array( wfMsgHtml( 'recentchangeslinked-page' ),
Xml::input( 'target', 40, str_replace('_',' ',$opts['target']) ) .
Xml::check( 'showlinkedto', $opts['showlinkedto'], array('id' => 'showlinkedto') ) . ' ' .
Xml::label( wfMsg("recentchangeslinked-to"), 'showlinkedto' ) );
@@ -180,19 +196,37 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges {
return $extraOpts;
}
+ function getTargetTitle() {
+ if ( $this->rclTargetTitle === null ) {
+ $opts = $this->getOptions();
+ if ( isset( $opts['target'] ) && $opts['target'] !== '' ) {
+ $this->rclTargetTitle = Title::newFromText( $opts['target'] );
+ } else {
+ $this->rclTargetTitle = false;
+ }
+ }
+ return $this->rclTargetTitle;
+ }
+
function setTopText( OutputPage $out, FormOptions $opts ) {
global $wgUser;
$skin = $wgUser->getSkin();
- if( isset( $this->mTargetTitle ) && is_object( $this->mTargetTitle ) )
- $out->setSubtitle( wfMsg( 'recentchangeslinked-backlink', $skin->link( $this->mTargetTitle,
- $this->mTargetTitle->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) );
+ $target = $this->getTargetTitle();
+ if( $target )
+ $out->setSubtitle( wfMsg( 'recentchangeslinked-backlink', $skin->link( $target,
+ $target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) );
}
- function setBottomText( OutputPage $out, FormOptions $opts ){
- if( isset( $this->mTargetTitle ) && is_object( $this->mTargetTitle ) ){
- global $wgUser;
- $out->setFeedAppendQuery( "target=" . urlencode( $this->mTargetTitle->getPrefixedDBkey() ) );
+ public function getFeedQuery() {
+ $target = $this->getTargetTitle();
+ if( $target ) {
+ return "target=" . urlencode( $target->getPrefixedDBkey() );
+ } else {
+ return false;
}
+ }
+
+ function setBottomText( OutputPage $out, FormOptions $opts ) {
if( isset( $this->mResultEmpty ) && $this->mResultEmpty ){
$out->addWikiMsg( 'recentchangeslinked-noresult' );
}
diff --git a/includes/specials/SpecialRemoveRestrictions.php b/includes/specials/SpecialRemoveRestrictions.php
index ded6cbe3..a3428a5a 100644
--- a/includes/specials/SpecialRemoveRestrictions.php
+++ b/includes/specials/SpecialRemoveRestrictions.php
@@ -1,9 +1,9 @@
<?php
function wfSpecialRemoveRestrictions() {
- global $wgOut, $wgRequest, $wgUser, $wgLang, $wgTitle;
+ global $wgOut, $wgRequest, $wgUser, $wgLang;
$sk = $wgUser->getSkin();
-
+ $title = SpecialPage::getTitleFor( 'RemoveRestrictions' );
$id = $wgRequest->getVal( 'id' );
if( !is_numeric( $id ) ) {
$wgOut->addWikiMsg( 'removerestrictions-noid' );
@@ -36,17 +36,17 @@ function wfSpecialRemoveRestrictions() {
if( $result )
$wgOut->addHTML( '<strong class="success">' . wfMsgExt( 'removerestrictions-success',
'parseinline', $r->getSubjectText() ) . '</strong>' );
- $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl( array( 'id' => $id ) ),
+ $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $title->getLocalUrl( array( 'id' => $id ) ),
'method' => 'post' ) ) );
$wgOut->addHTML( Xml::buildForm( $form, 'removerestrictions-submit' ) );
$wgOut->addHTML( Xml::hidden( 'id', $r->getId() ) );
- $wgOut->addHTML( Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() ) );
+ $wgOut->addHTML( Xml::hidden( 'title', $title->getPrefixedDbKey() ) );
$wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) );
$wgOut->addHTML( "</form></fieldset>" );
}
function wfSpecialRemoveRestrictionsProcess( $r ) {
- global $wgUser, $wgRequest;
+ global $wgRequest;
$reason = $wgRequest->getVal( 'reason' );
$result = $r->delete();
$log = new LogPage( 'restrict' );
diff --git a/includes/specials/SpecialResetpass.php b/includes/specials/SpecialResetpass.php
index 059f8dbd..967d2119 100644
--- a/includes/specials/SpecialResetpass.php
+++ b/includes/specials/SpecialResetpass.php
@@ -37,6 +37,11 @@ class SpecialResetpass extends SpecialPage {
return;
}
+ if( $wgRequest->wasPosted() && $wgRequest->getBool( 'wpCancel' ) ) {
+ $this->doReturnTo();
+ return;
+ }
+
if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal('token') ) ) {
try {
$this->attemptReset( $this->mNewpass, $this->mRetype );
@@ -54,17 +59,22 @@ class SpecialResetpass extends SpecialPage {
$login = new LoginForm( new FauxRequest( $data, true ) );
$login->execute();
}
- $titleObj = Title::newFromText( $wgRequest->getVal( 'returnto' ) );
- if ( !$titleObj instanceof Title ) {
- $titleObj = Title::newMainPage();
- }
- $wgOut->redirect( $titleObj->getFullURL() );
+ $this->doReturnTo();
} catch( PasswordError $e ) {
$this->error( $e->getMessage() );
}
}
$this->showForm();
}
+
+ function doReturnTo() {
+ global $wgRequest, $wgOut;
+ $titleObj = Title::newFromText( $wgRequest->getVal( 'returnto' ) );
+ if ( !$titleObj instanceof Title ) {
+ $titleObj = Title::newMainPage();
+ }
+ $wgOut->redirect( $titleObj->getFullURL() );
+ }
function error( $msg ) {
global $wgOut;
@@ -102,52 +112,60 @@ class SpecialResetpass extends SpecialPage {
array(
'method' => 'post',
'action' => $self->getLocalUrl(),
- 'id' => 'mw-resetpass-form' ) ) .
- Xml::hidden( 'token', $wgUser->editToken() ) .
- Xml::hidden( 'wpName', $this->mUserName ) .
- Xml::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) .
- wfMsgExt( 'resetpass_text', array( 'parse' ) ) .
- Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) .
+ 'id' => 'mw-resetpass-form' ) ) . "\n" .
+ Xml::hidden( 'token', $wgUser->editToken() ) . "\n" .
+ Xml::hidden( 'wpName', $this->mUserName ) . "\n" .
+ Xml::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . "\n" .
+ wfMsgExt( 'resetpass_text', array( 'parse' ) ) . "\n" .
+ Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" .
$this->pretty( array(
array( 'wpName', 'username', 'text', $this->mUserName ),
array( 'wpPassword', $oldpassMsg, 'password', $this->mOldpass ),
- array( 'wpNewPassword', 'newpassword', 'password', '' ),
- array( 'wpRetype', 'retypenew', 'password', '' ),
- ) ) .
+ array( 'wpNewPassword', 'newpassword', 'password', null ),
+ array( 'wpRetype', 'retypenew', 'password', null ),
+ ) ) . "\n" .
$rememberMe .
- '<tr>' .
- '<td></td>' .
+ "<tr>\n" .
+ "<td></td>\n" .
'<td class="mw-input">' .
Xml::submitButton( wfMsg( $submitMsg ) ) .
- '</td>' .
- '</tr>' .
+ Xml::submitButton( wfMsg( 'resetpass-submit-cancel' ), array( 'name' => 'wpCancel' ) ) .
+ "</td>\n" .
+ "</tr>\n" .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'form' ) .
- Xml::closeElement( 'fieldset' )
+ Xml::closeElement( 'fieldset' ) . "\n"
);
}
function pretty( $fields ) {
$out = '';
- foreach( $fields as $list ) {
+ foreach ( $fields as $list ) {
list( $name, $label, $type, $value ) = $list;
if( $type == 'text' ) {
$field = htmlspecialchars( $value );
} else {
- $field = Xml::input( $name, 20, $value,
- array( 'id' => $name, 'type' => $type ) );
+ $attribs = array( 'id' => $name );
+ if ( $name == 'wpNewPassword' || $name == 'wpRetype' ) {
+ $attribs = array_merge( $attribs,
+ User::passwordChangeInputAttribs() );
+ }
+ if ( $name == 'wpPassword' ) {
+ $attribs[] = 'autofocus';
+ }
+ $field = Html::input( $name, $value, $type, $attribs );
}
- $out .= '<tr>';
- $out .= "<td class='mw-label'>";
+ $out .= "<tr>\n";
+ $out .= "\t<td class='mw-label'>";
if ( $type != 'text' )
$out .= Xml::label( wfMsg( $label ), $name );
else
- $out .= wfMsg( $label );
- $out .= '</td>';
- $out .= "<td class='mw-input'>";
+ $out .= wfMsgHtml( $label );
+ $out .= "</td>\n";
+ $out .= "\t<td class='mw-input'>";
$out .= $field;
- $out .= '</td>';
- $out .= '</tr>';
+ $out .= "</td>\n";
+ $out .= "</tr>";
}
return $out;
}
diff --git a/includes/specials/SpecialRestrictUser.php b/includes/specials/SpecialRestrictUser.php
deleted file mode 100644
index b946cde8..00000000
--- a/includes/specials/SpecialRestrictUser.php
+++ /dev/null
@@ -1,190 +0,0 @@
-<?php
-
-function wfSpecialRestrictUser( $par = null ) {
- global $wgOut, $wgRequest;
- $user = $userOrig = null;
- if( $par ) {
- $userOrig = $par;
- } elseif( $wgRequest->getVal( 'user' ) ) {
- $userOrig = $wgRequest->getVal( 'user' );
- } else {
- $wgOut->addHTML( RestrictUserForm::selectUserForm() );
- return;
- }
- $isIP = User::isIP( $userOrig );
- $user = $isIP ? $userOrig : User::getCanonicalName( $userOrig );
- $uid = User::idFromName( $user );
- if( !$uid && !$isIP ) {
- $err = '<strong class="error">' . wfMsgHtml( 'restrictuser-notfound' ) . '</strong>';
- $wgOut->addHTML( RestrictUserForm::selectUserForm( $userOrig, $err ) );
- return;
- }
- $wgOut->addHTML( RestrictUserForm::selectUserForm( $user ) );
-
- UserRestriction::purgeExpired();
- $old = UserRestriction::fetchForUser( $user, true );
-
- RestrictUserForm::pageRestrictionForm( $uid, $user, $old );
- RestrictUserForm::namespaceRestrictionForm( $uid, $user, $old );
-
- // Renew it after possible changes in previous two functions
- $old = UserRestriction::fetchForUser( $user, true );
- if( $old ) {
- $wgOut->addHTML( RestrictUserForm::existingRestrictions( $old ) );
- }
-}
-
-class RestrictUserForm {
- public static function selectUserForm( $val = null, $error = null ) {
- global $wgScript, $wgTitle;
- $action = htmlspecialchars( $wgScript );
- $s = Xml::fieldset( wfMsg( 'restrictuser-userselect' ) ) . "<form action=\"{$action}\">";
- if( $error )
- $s .= '<p>' . $error . '</p>';
- $s .= Xml::hidden( 'title', $wgTitle->getPrefixedDbKey() );
- $form = array( 'restrictuser-user' => Xml::input( 'user', false, $val ) );
- $s .= Xml::buildForm( $form, 'restrictuser-go' );
- $s .= "</form></fieldset>";
- return $s;
- }
-
- public static function existingRestrictions( $restrictions ) {
- //TODO: autoload?
- require_once( dirname( __FILE__ ) . '/SpecialListUserRestrictions.php' );
- $s = Xml::fieldset( wfMsg( 'restrictuser-existing' ) ) . '<ul>';
- foreach( $restrictions as $r )
- $s .= UserRestrictionsPager::formatRestriction( $r );
- $s .= "</ul></fieldset>";
- return $s;
- }
-
- public static function pageRestrictionForm( $uid, $user, $oldRestrictions ) {
- global $wgOut, $wgTitle, $wgRequest, $wgUser;
- $error = '';
- $success = false;
- if( $wgRequest->wasPosted() && $wgRequest->getVal( 'type' ) == UserRestriction::PAGE &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) {
-
- $title = Title::newFromText( $wgRequest->getVal( 'page' ) );
- if( !$title ) {
- $error = array( 'restrictuser-badtitle', $wgRequest->getVal( 'page' ) );
- } elseif( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) === false ) {
- $error = array( 'restrictuser-badexpiry', $wgRequest->getVal( 'expiry' ) );
- } else {
- foreach( $oldRestrictions as $r ) {
- if( $r->isPage() && $r->getPage()->equals( $title ) )
- $error = array( 'restrictuser-duptitle' );
- }
- }
- if( !$error ) {
- self::doPageRestriction( $uid, $user );
- $success = array('restrictuser-success', $user);
- }
- }
- $useRequestValues = $wgRequest->getVal( 'type' ) == UserRestriction::PAGE;
- $wgOut->addHTML( Xml::fieldset( wfMsg( 'restrictuser-legend-page' ) ) );
-
- self::printSuccessError( $success, $error );
-
- $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl(),
- 'method' => 'post' ) ) );
- $wgOut->addHTML( Xml::hidden( 'type', UserRestriction::PAGE ) );
- $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) );
- $wgOut->addHTML( Xml::hidden( 'user', $user ) );
- $form = array();
- $form['restrictuser-title'] = Xml::input( 'page', false,
- $useRequestValues ? $wgRequest->getVal( 'page' ) : false );
- $form['restrictuser-expiry'] = Xml::input( 'expiry', false,
- $useRequestValues ? $wgRequest->getVal( 'expiry' ) : false );
- $form['restrictuser-reason'] = Xml::input( 'reason', false,
- $useRequestValues ? $wgRequest->getVal( 'reason' ) : false );
- $wgOut->addHTML( Xml::buildForm( $form, 'restrictuser-submit' ) );
- $wgOut->addHTML( "</form></fieldset>" );
- }
-
- public static function printSuccessError( $success, $error ) {
- global $wgOut;
- if ( $error )
- $wgOut->wrapWikiMsg( '<strong class="error">$1</strong>', $error );
- if ( $success )
- $wgOut->wrapWikiMsg( '<strong class="success">$1</strong>', $success );
- }
-
- public static function doPageRestriction( $uid, $user ) {
- global $wgUser, $wgRequest;
- $r = new UserRestriction();
- $r->setType( UserRestriction::PAGE );
- $r->setPage( Title::newFromText( $wgRequest->getVal( 'page' ) ) );
- $r->setSubjectId( $uid );
- $r->setSubjectText( $user );
- $r->setBlockerId( $wgUser->getId() );
- $r->setBlockerText( $wgUser->getName() );
- $r->setReason( $wgRequest->getVal( 'reason' ) );
- $r->setExpiry( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) );
- $r->setTimestamp( wfTimestampNow( TS_MW ) );
- $r->commit();
- $logExpiry = $wgRequest->getVal( 'expiry' ) ? $wgRequest->getVal( 'expiry' ) : Block::infinity();
- $l = new LogPage( 'restrict' );
- $l->addEntry( 'restrict', Title::makeTitle( NS_USER, $user ), $r->getReason(),
- array( $r->getType(), $r->getPage()->getFullText(), $logExpiry) );
- }
-
- public static function namespaceRestrictionForm( $uid, $user, $oldRestrictions ) {
- global $wgOut, $wgTitle, $wgRequest, $wgUser, $wgContLang;
- $error = '';
- $success = false;
- if( $wgRequest->wasPosted() && $wgRequest->getVal( 'type' ) == UserRestriction::NAMESPACE &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) {
- $ns = $wgRequest->getVal( 'namespace' );
- if( $wgContLang->getNsText( $ns ) === false )
- $error = wfMsgExt( 'restrictuser-badnamespace', 'parseinline' );
- elseif( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) === false )
- $error = wfMsgExt( 'restrictuser-badexpiry', 'parseinline', $wgRequest->getVal( 'expiry' ) );
- else
- foreach( $oldRestrictions as $r )
- if( $r->isNamespace() && $r->getNamespace() == $ns )
- $error = wfMsgExt( 'restrictuser-dupnamespace', 'parse' );
- if( !$error ) {
- self::doNamespaceRestriction( $uid, $user );
- $success = array('restrictuser-success', $user);
- }
- }
- $useRequestValues = $wgRequest->getVal( 'type' ) == UserRestriction::NAMESPACE;
- $wgOut->addHTML( Xml::fieldset( wfMsg( 'restrictuser-legend-namespace' ) ) );
-
- self::printSuccessError( $success, $error );
-
- $wgOut->addHTML( Xml::openElement( 'form', array( 'action' => $wgTitle->getLocalUrl(),
- 'method' => 'post' ) ) );
- $wgOut->addHTML( Xml::hidden( 'type', UserRestriction::NAMESPACE ) );
- $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) );
- $wgOut->addHTML( Xml::hidden( 'user', $user ) );
- $form = array();
- $form['restrictuser-namespace'] = Xml::namespaceSelector( $wgRequest->getVal( 'namespace' ) );
- $form['restrictuser-expiry'] = Xml::input( 'expiry', false,
- $useRequestValues ? $wgRequest->getVal( 'expiry' ) : false );
- $form['restrictuser-reason'] = Xml::input( 'reason', false,
- $useRequestValues ? $wgRequest->getVal( 'reason' ) : false );
- $wgOut->addHTML( Xml::buildForm( $form, 'restrictuser-submit' ) );
- $wgOut->addHTML( "</form></fieldset>" );
- }
-
- public static function doNamespaceRestriction( $uid, $user ) {
- global $wgUser, $wgRequest;
- $r = new UserRestriction();
- $r->setType( UserRestriction::NAMESPACE );
- $r->setNamespace( $wgRequest->getVal( 'namespace' ) );
- $r->setSubjectId( $uid );
- $r->setSubjectText( $user );
- $r->setBlockerId( $wgUser->getId() );
- $r->setBlockerText( $wgUser->getName() );
- $r->setReason( $wgRequest->getVal( 'reason' ) );
- $r->setExpiry( UserRestriction::convertExpiry( $wgRequest->getVal( 'expiry' ) ) );
- $r->setTimestamp( wfTimestampNow( TS_MW ) );
- $r->commit();
- $logExpiry = $wgRequest->getVal( 'expiry' ) ? $wgRequest->getVal( 'expiry' ) : Block::infinity();
- $l = new LogPage( 'restrict' );
- $l->addEntry( 'restrict', Title::makeTitle( NS_USER, $user ), $r->getReason(),
- array( $r->getType(), $r->getNamespace(), $logExpiry ) );
- }
-}
diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php
index 7fdb3cc4..b2db869c 100644
--- a/includes/specials/SpecialRevisiondelete.php
+++ b/includes/specials/SpecialRevisiondelete.php
@@ -8,164 +8,287 @@
*/
class SpecialRevisionDelete extends UnlistedSpecialPage {
+ /** Skin object */
+ var $skin;
+
+ /** True if the submit button was clicked, and the form was posted */
+ var $submitClicked;
+
+ /** Target ID list */
+ var $ids;
+
+ /** Archive name, for reviewing deleted files */
+ var $archiveName;
+
+ /** Edit token for securing image views against XSS */
+ var $token;
+
+ /** Title object for target parameter */
+ var $targetObj;
+
+ /** Deletion type, may be revision, archive, oldimage, filearchive, logging. */
+ var $typeName;
+
+ /** Array of checkbox specs (message, name, deletion bits) */
+ var $checks;
+
+ /** Information about the current type */
+ var $typeInfo;
+
+ /** The RevDel_List object, storing the list of items to be deleted/undeleted */
+ var $list;
+
+ /**
+ * Assorted information about each type, needed by the special page.
+ * TODO Move some of this to the list class
+ */
+ static $allowedTypes = array(
+ 'revision' => array(
+ 'check-label' => 'revdelete-hide-text',
+ 'deletion-bits' => Revision::DELETED_TEXT,
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_RevisionList',
+ ),
+ 'archive' => array(
+ 'check-label' => 'revdelete-hide-text',
+ 'deletion-bits' => Revision::DELETED_TEXT,
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_ArchiveList',
+ ),
+ 'oldimage'=> array(
+ 'check-label' => 'revdelete-hide-image',
+ 'deletion-bits' => File::DELETED_FILE,
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_FileList',
+ ),
+ 'filearchive' => array(
+ 'check-label' => 'revdelete-hide-image',
+ 'deletion-bits' => File::DELETED_FILE,
+ 'success' => 'revdelete-success',
+ 'failure' => 'revdelete-failure',
+ 'list-class' => 'RevDel_ArchivedFileList',
+ ),
+ 'logging' => array(
+ 'check-label' => 'revdelete-hide-name',
+ 'deletion-bits' => LogPage::DELETED_ACTION,
+ 'success' => 'logdelete-success',
+ 'failure' => 'logdelete-failure',
+ 'list-class' => 'RevDel_LogList',
+ ),
+ );
+
+ /** Type map to support old log entries */
+ static $deprecatedTypeMap = array(
+ 'oldid' => 'revision',
+ 'artimestamp' => 'archive',
+ 'oldimage' => 'oldimage',
+ 'fileid' => 'filearchive',
+ 'logid' => 'logging',
+ );
public function __construct() {
- parent::__construct( 'Revisiondelete', 'deleterevision' );
- $this->includable( false );
+ parent::__construct( 'Revisiondelete', 'deletedhistory' );
}
public function execute( $par ) {
global $wgOut, $wgUser, $wgRequest;
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
+ if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
+ $wgOut->permissionRequired( 'deletedhistory' );
return;
- }
- if( !$wgUser->isAllowed( 'deleterevision' ) ) {
- $wgOut->permissionRequired( 'deleterevision' );
+ } else if( wfReadOnly() ) {
+ $wgOut->readOnlyPage();
return;
}
- $this->skin =& $wgUser->getSkin();
- # Set title and such
+ $this->mIsAllowed = $wgUser->isAllowed('deleterevision'); // for changes
+ $this->skin = $wgUser->getSkin();
$this->setHeaders();
$this->outputHeader();
- $this->wasPosted = $wgRequest->wasPosted();
- # Handle our many different possible input types
- $this->target = $wgRequest->getText( 'target' );
- $this->oldids = $wgRequest->getArray( 'oldid' );
- $this->artimestamps = $wgRequest->getArray( 'artimestamp' );
- $this->logids = $wgRequest->getArray( 'logid' );
- $this->oldimgs = $wgRequest->getArray( 'oldimage' );
- $this->fileids = $wgRequest->getArray( 'fileid' );
+ $this->submitClicked = $wgRequest->wasPosted() && $wgRequest->getBool( 'wpSubmit' );
+ # Handle our many different possible input types.
+ $ids = $wgRequest->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_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'];
+ $this->typeName = 'revision';
+ } else {
+ $this->typeName = $wgRequest->getVal( 'type' );
+ $this->targetObj = Title::newFromText( $wgRequest->getText( 'target' ) );
+ }
+
# For reviewing deleted files...
- $this->file = $wgRequest->getVal( 'file' );
- # Only one target set at a time please!
- $i = (bool)$this->file + (bool)$this->oldids + (bool)$this->logids
- + (bool)$this->artimestamps + (bool)$this->fileids + (bool)$this->oldimgs;
- # No targets?
- if( $i == 0 ) {
- $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
+ $this->archiveName = $wgRequest->getVal( 'file' );
+ $this->token = $wgRequest->getVal( 'token' );
+ if ( $this->archiveName && $this->targetObj ) {
+ $this->tryShowFile( $this->archiveName );
return;
}
- # Too many targets?
- if( $i !== 1 ) {
- $wgOut->showErrorPage( 'revdelete-toomanytargets-title', 'revdelete-toomanytargets-text' );
+
+ if ( isset( self::$deprecatedTypeMap[$this->typeName] ) ) {
+ $this->typeName = self::$deprecatedTypeMap[$this->typeName];
+ }
+
+ # No targets?
+ if( !isset( self::$allowedTypes[$this->typeName] ) || count( $this->ids ) == 0 ) {
+ $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
return;
}
- $this->page = Title::newFromUrl( $this->target );
+ $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
# for more flexibility with page moves...
- if( count($this->oldids) > 0 ) {
- $rev = Revision::newFromId( $this->oldids[0] );
- $this->page = $rev ? $rev->getTitle() : $this->page;
+ if( $this->typeName == 'revision' ) {
+ $rev = Revision::newFromId( $this->ids[0] );
+ $this->targetObj = $rev ? $rev->getTitle() : $this->targetObj;
}
+
+ $this->otherReason = $wgRequest->getVal( 'wpReason' );
# We need a target page!
- if( is_null($this->page) ) {
+ if( is_null($this->targetObj) ) {
$wgOut->addWikiMsg( 'undelete-header' );
return;
}
- # Logs must have a type given
- if( $this->logids && !strpos($this->page->getDBKey(),'/') ) {
- $wgOut->showErrorPage( 'revdelete-nologtype-title', 'revdelete-nologtype-text' );
- return;
- }
- # For reviewing deleted files...show it now if allowed
- if( $this->file ) {
- $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->page, $this->file );
- $oimage->load();
- // Check if user is allowed to see this file
- if( !$oimage->userCan(File::DELETED_FILE) ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- } else {
- $this->showFile( $this->file );
- }
- return;
- }
# Give a link to the logs/hist for this page
- if( !is_null($this->page) && $this->page->getNamespace() > -1 ) {
- $links = array();
+ $this->showConvenienceLinks();
- $logtitle = SpecialPage::getTitleFor( 'Log' );
- $links[] = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'viewpagelogs' ),
- wfArrayToCGI( array( 'page' => $this->page->getPrefixedUrl() ) ) );
- # Give a link to the page history
- $links[] = $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml( 'pagehist' ),
- wfArrayToCGI( array( 'action' => 'history' ) ) );
- # Link to deleted edits
- if( $wgUser->isAllowed('undelete') ) {
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- $links[] = $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml( 'deletedhist' ),
- wfArrayToCGI( array( 'target' => $this->page->getPrefixedDBkey() ) ) );
- }
- # Logs themselves don't have histories or archived revisions
- $wgOut->setSubtitle( '<p>'.implode($links,' / ').'</p>' );
- }
- # Lock the operation and the form context
- $this->secureOperation();
- # Either submit or create our form
- if( $this->wasPosted ) {
- $this->submit( $wgRequest );
- } else if( $this->deleteKey == 'oldid' || $this->deleteKey == 'artimestamp' ) {
- $this->showRevs();
- } else if( $this->deleteKey == 'fileid' || $this->deleteKey == 'oldimage' ) {
- $this->showImages();
- } else if( $this->deleteKey == 'logid' ) {
- $this->showLogItems();
- }
- # Show relevant lines from the deletion log. This will show even if said ID
- # does not exist...might be helpful
- $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
- LogEventsList::showLogExtract( $wgOut, 'delete', $this->page->getPrefixedText() );
- if( $wgUser->isAllowed( 'suppressionlog' ) ){
- $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" );
- LogEventsList::showLogExtract( $wgOut, 'suppress', $this->page->getPrefixedText() );
- }
- }
-
- private function secureOperation() {
- global $wgUser;
- $this->deleteKey = '';
- // At this point, we should only have one of these
- if( $this->oldids ) {
- $this->revisions = $this->oldids;
- $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT );
- $this->deleteKey = 'oldid';
- } else if( $this->artimestamps ) {
- $this->archrevs = $this->artimestamps;
- $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT );
- $this->deleteKey = 'artimestamp';
- } else if( $this->oldimgs ) {
- $this->ofiles = $this->oldimgs;
- $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE );
- $this->deleteKey = 'oldimage';
- } else if( $this->fileids ) {
- $this->afiles = $this->fileids;
- $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE );
- $this->deleteKey = 'fileid';
- } else if( $this->logids ) {
- $this->events = $this->logids;
- $hide_content_name = array( 'revdelete-hide-name', 'wpHideName', LogPage::DELETED_ACTION );
- $this->deleteKey = 'logid';
- }
- // Our checkbox messages depends one what we are doing,
- // e.g. we don't hide "text" for logs or images
+ # Initialise checkboxes
$this->checks = array(
- $hide_content_name,
+ array( $this->typeInfo['check-label'], 'wpHidePrimary', $this->typeInfo['deletion-bits'] ),
array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ),
array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER )
);
if( $wgUser->isAllowed('suppressrevision') ) {
- $this->checks[] = array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED );
+ $this->checks[] = array( 'revdelete-hide-restricted',
+ 'wpHideRestricted', Revision::DELETED_RESTRICTED );
+ }
+
+ # Either submit or create our form
+ if( $this->mIsAllowed && $this->submitClicked ) {
+ $this->submit( $wgRequest );
+ } 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',
+ $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',
+ $this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) );
}
}
/**
+ * 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(
+ SpecialPage::getTitleFor( 'Log' ),
+ wfMsgHtml( 'viewpagelogs' ),
+ array(),
+ array( 'page' => $this->targetObj->getPrefixedText() )
+ );
+ if ( $this->targetObj->getNamespace() != NS_SPECIAL ) {
+ # Give a link to the page history
+ $links[] = $this->skin->linkKnown(
+ $this->targetObj,
+ wfMsgHtml( 'pagehist' ),
+ array(),
+ array( 'action' => 'history' )
+ );
+ # Link to deleted edits
+ if( $wgUser->isAllowed('undelete') ) {
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $links[] = $this->skin->linkKnown(
+ $undelete,
+ wfMsgHtml( 'deletedhist' ),
+ array(),
+ array( 'target' => $this->targetObj->getPrefixedDBkey() )
+ );
+ }
+ }
+ # Logs themselves don't have histories or archived revisions
+ $wgOut->setSubtitle( '<p>' . $wgLang->pipeList( $links ) . '</p>' );
+ }
+ }
+
+ /**
+ * Get the condition used for fetching log snippets
+ */
+ protected function getLogQueryCond() {
+ $conds = array();
+ // Revision delete logs for these item
+ $conds['log_type'] = array('delete','suppress');
+ $conds['log_action'] = $this->getList()->getLogAction();
+ $conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
+ $conds['ls_value'] = $this->ids;
+ return $conds;
+ }
+
+ /**
* Show a deleted file version requested by the visitor.
+ * TODO Mostly copied from Special:Undelete. Refactor.
*/
- private function showFile( $key ) {
- global $wgOut, $wgRequest;
+ 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' );
+ return;
+ }
+ if( !$oimage->userCan(File::DELETED_FILE) ) {
+ if( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
+ $wgOut->permissionRequired( 'suppressrevision' );
+ } else {
+ $wgOut->permissionRequired( 'deletedtext' );
+ }
+ return;
+ }
+ if ( !$wgUser->matchEditToken( $this->token, $archiveName ) ) {
+ $wgOut->addWikiMsg( 'revdelete-show-file-confirm',
+ $this->targetObj->getText(),
+ $wgLang->date( $oimage->getTimestamp() ),
+ $wgLang->time( $oimage->getTimestamp() ) );
+ $wgOut->addHTML(
+ Xml::openElement( 'form', array(
+ 'method' => 'POST',
+ 'action' => $this->getTitle()->getLocalUrl(
+ 'target=' . urlencode( $oimage->getName() ) .
+ '&file=' . urlencode( $archiveName ) .
+ '&token=' . urlencode( $wgUser->editToken( $archiveName ) ) )
+ )
+ ) .
+ Xml::submitButton( wfMsg( 'revdelete-show-file-submit' ) ) .
+ '</form>'
+ );
+ return;
+ }
$wgOut->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
@@ -174,103 +297,61 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
$wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
$wgRequest->response()->header( 'Pragma: no-cache' );
- $store = FileStore::get( 'deleted' );
- $store->stream( $key );
+ # Stream the file to the client
+ global $IP;
+ require_once( "$IP/includes/StreamFile.php" );
+ $key = $oimage->getStorageKey();
+ $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
+ wfStreamFile( $path );
}
/**
- * This lets a user set restrictions for live and archived revisions
+ * Get the list object for this request
*/
- private function showRevs() {
- global $wgOut, $wgUser;
+ protected function getList() {
+ if ( is_null( $this->list ) ) {
+ $class = $this->typeInfo['list-class'];
+ $this->list = new $class( $this, $this->targetObj, $this->ids );
+ }
+ return $this->list;
+ }
+
+ /**
+ * 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;
- $count = ($this->deleteKey=='oldid') ?
- count($this->revisions) : count($this->archrevs);
- $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(), $count );
+ if ( $this->typeName == 'logging' ) {
+ $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->ids) ) );
+ } else {
+ $wgOut->addWikiMsg( 'revdelete-selected',
+ $this->targetObj->getPrefixedText(), count( $this->ids ) );
+ }
- $bitfields = 0;
$wgOut->addHTML( "<ul>" );
$where = $revObjs = array();
- $dbr = wfGetDB( DB_MASTER );
- $revisions = 0;
+ $numRevisions = 0;
// Live revisions...
- if( $this->deleteKey=='oldid' ) {
- // Run through and pull all our data in one query
- foreach( $this->revisions as $revid ) {
- $where[] = intval($revid);
- }
- $result = $dbr->select( array('revision','page'), '*',
- array(
- 'rev_page' => $this->page->getArticleID(),
- 'rev_id' => $where,
- 'rev_page = page_id' ),
- __METHOD__ );
- while( $row = $dbr->fetchObject( $result ) ) {
- $revObjs[$row->rev_id] = new Revision( $row );
- }
- foreach( $this->revisions as $revid ) {
- // Hiding top revisison is bad
- if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) {
- continue;
- } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) {
- // If a rev is hidden from sysops
- if( !$this->wasPosted ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return;
- }
- $UserAllowed = false;
- }
- $revisions++;
- $wgOut->addHTML( $this->historyLine( $revObjs[$revid] ) );
- $bitfields |= $revObjs[$revid]->mDeleted;
- }
- // The archives...
- } else {
- // Run through and pull all our data in one query
- foreach( $this->archrevs as $timestamp ) {
- $where[] = $dbr->timestamp( $timestamp );
- }
- $result = $dbr->select( 'archive', '*',
- array(
- 'ar_namespace' => $this->page->getNamespace(),
- 'ar_title' => $this->page->getDBKey(),
- 'ar_timestamp' => $where ),
- __METHOD__ );
- while( $row = $dbr->fetchObject( $result ) ) {
- $timestamp = wfTimestamp( TS_MW, $row->ar_timestamp );
- $revObjs[$timestamp] = new Revision( array(
- 'page' => $this->page->getArticleId(),
- 'id' => $row->ar_rev_id,
- 'text' => $row->ar_text_id,
- 'comment' => $row->ar_comment,
- 'user' => $row->ar_user,
- 'user_text' => $row->ar_user_text,
- 'timestamp' => $timestamp,
- 'minor_edit' => $row->ar_minor_edit,
- 'text_id' => $row->ar_text_id,
- 'deleted' => $row->ar_deleted,
- 'len' => $row->ar_len) );
- }
- foreach( $this->archrevs as $timestamp ) {
- if( !isset($revObjs[$timestamp]) ) {
- continue;
- } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) {
- // If a rev is hidden from sysops
- if( !$this->wasPosted ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return;
- }
- $UserAllowed = false;
+ $list = $this->getList();
+ for ( $list->reset(); $list->current(); $list->next() ) {
+ $item = $list->current();
+ if ( !$item->canView() ) {
+ if( !$this->submitClicked ) {
+ $wgOut->permissionRequired( 'suppressrevision' );
+ return;
}
- $revisions++;
- $wgOut->addHTML( $this->historyLine( $revObjs[$timestamp] ) );
- $bitfields |= $revObjs[$timestamp]->mDeleted;
+ $UserAllowed = false;
}
+ $numRevisions++;
+ $wgOut->addHTML( $item->getHTML() );
}
- if( !$revisions ) {
+
+ if( !$numRevisions ) {
$wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
return;
}
@@ -282,1235 +363,1485 @@ class SpecialRevisionDelete extends UnlistedSpecialPage {
// Normal sysops can always see what they did, but can't always change it
if( !$UserAllowed ) return;
- $items = array(
- Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
- Xml::submitButton( wfMsg( 'revdelete-submit' ) )
- );
- $hidden = array(
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ),
- Xml::hidden( 'target', $this->page->getPrefixedText() ),
- Xml::hidden( 'type', $this->deleteKey )
- );
- if( $this->deleteKey=='oldid' ) {
- foreach( $revObjs as $rev )
- $hidden[] = Xml::hidden( 'oldid[]', $rev->getId() );
+ // Show form if the user can submit
+ if( $this->mIsAllowed ) {
+ $out = Xml::openElement( 'form', array( 'method' => 'post',
+ 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ),
+ 'id' => 'mw-revdel-form-revisions' ) ) .
+ Xml::fieldset( wfMsg( 'revdelete-legend' ) ) .
+ $this->buildCheckBoxes() .
+ Xml::openElement( 'table' ) .
+ "<tr>\n" .
+ '<td class="mw-label">' .
+ Xml::label( wfMsg( 'revdelete-log' ), 'wpRevDeleteReasonList' ) .
+ '</td>' .
+ '<td class="mw-input">' .
+ Xml::listDropDown( 'wpRevDeleteReasonList',
+ wfMsgForContent( 'revdelete-reason-dropdown' ),
+ wfMsgForContent( 'revdelete-reasonotherlist' ), '', 'wpReasonDropDown', 1
+ ) .
+ '</td>' .
+ "</tr><tr>\n" .
+ '<td class="mw-label">' .
+ Xml::label( wfMsg( 'revdelete-otherreason' ), 'wpReason' ) .
+ '</td>' .
+ '<td class="mw-input">' .
+ Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason' ) ) .
+ '</td>' .
+ "</tr><tr>\n" .
+ '<td></td>' .
+ '<td class="mw-submit">' .
+ Xml::submitButton( wfMsgExt('revdelete-submit','parsemag',$numRevisions),
+ array( 'name' => 'wpSubmit' ) ) .
+ '</td>' .
+ "</tr>\n" .
+ Xml::closeElement( 'table' ) .
+ Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
+ Xml::hidden( 'target', $this->targetObj->getPrefixedText() ) .
+ Xml::hidden( 'type', $this->typeName ) .
+ Xml::hidden( 'ids', implode( ',', $this->ids ) ) .
+ Xml::closeElement( 'fieldset' ) . "\n";
} else {
- foreach( $revObjs as $rev )
- $hidden[] = Xml::hidden( 'artimestamp[]', $rev->getTimestamp() );
- }
- $special = SpecialPage::getTitleFor( 'Revisiondelete' );
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ),
- 'id' => 'mw-revdel-form-revisions' ) ) .
- Xml::openElement( 'fieldset' ) .
- xml::element( 'legend', null, wfMsg( 'revdelete-legend' ) )
- );
-
- $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) );
- foreach( $items as $item ) {
- $wgOut->addHTML( Xml::tags( 'p', null, $item ) );
- }
- foreach( $hidden as $item ) {
- $wgOut->addHTML( $item );
+ $out = '';
+ }
+ if( $this->mIsAllowed ) {
+ $out .= Xml::closeElement( 'form' ) . "\n";
+ // Show link to edit the dropdown reasons
+ if( $wgUser->isAllowed( 'editinterface' ) ) {
+ $title = Title::makeTitle( NS_MEDIAWIKI, 'revdelete-reason-dropdown' );
+ $link = $wgUser->getSkin()->link(
+ $title,
+ wfMsgHtml( 'revdelete-edit-reasonlist' ),
+ array(),
+ array( 'action' => 'edit' )
+ );
+ $out .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n";
+ }
}
- $wgOut->addHTML(
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) . "\n"
- );
+ $wgOut->addHTML( $out );
}
/**
- * This lets a user set restrictions for archived images
+ * Show some introductory text
+ * FIXME Wikimedia-specific policy text
*/
- private function showImages() {
- global $wgOut, $wgUser, $wgLang;
- $UserAllowed = true;
-
- $count = ($this->deleteKey=='oldimage') ? count($this->ofiles) : count($this->afiles);
- $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(),
- $wgLang->formatNum($count) );
-
- $bitfields = 0;
- $wgOut->addHTML( "<ul>" );
-
- $where = $filesObjs = array();
- $dbr = wfGetDB( DB_MASTER );
- // Live old revisions...
- $revisions = 0;
- if( $this->deleteKey=='oldimage' ) {
- // Run through and pull all our data in one query
- foreach( $this->ofiles as $timestamp ) {
- $where[] = $timestamp.'!'.$this->page->getDBKey();
- }
- $result = $dbr->select( 'oldimage', '*',
- array(
- 'oi_name' => $this->page->getDBKey(),
- 'oi_archive_name' => $where ),
- __METHOD__ );
- while( $row = $dbr->fetchObject( $result ) ) {
- $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
- $filesObjs[$row->oi_archive_name]->user = $row->oi_user;
- $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text;
+ protected function addUsageText() {
+ global $wgOut, $wgUser;
+ $wgOut->addWikiMsg( 'revdelete-text' );
+ if( $wgUser->isAllowed( 'suppressrevision' ) ) {
+ $wgOut->addWikiMsg( 'revdelete-suppress-text' );
+ }
+ if( $this->mIsAllowed ) {
+ $wgOut->addWikiMsg( 'revdelete-confirm' );
+ }
+ }
+
+ /**
+ * @return String: HTML
+ */
+ protected function buildCheckBoxes() {
+ global $wgRequest;
+
+ $html = '<table>';
+ // If there is just one item, use checkboxes
+ $list = $this->getList();
+ if( $list->length() == 1 ) {
+ $list->reset();
+ $bitfield = $list->current()->getBits(); // existing field
+ if( $this->submitClicked ) {
+ $bitfield = $this->extractBitfield( $this->extractBitParams($wgRequest), $bitfield );
}
- // Check through our images
- foreach( $this->ofiles as $timestamp ) {
- $archivename = $timestamp.'!'.$this->page->getDBKey();
- if( !isset($filesObjs[$archivename]) ) {
- continue;
- } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) {
- // If a rev is hidden from sysops
- if( !$this->wasPosted ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return;
- }
- $UserAllowed = false;
- }
- $revisions++;
- // Inject history info
- $wgOut->addHTML( $this->fileLine( $filesObjs[$archivename] ) );
- $bitfields |= $filesObjs[$archivename]->deleted;
+ foreach( $this->checks as $item ) {
+ list( $message, $name, $field ) = $item;
+ $innerHTML = Xml::checkLabel( wfMsg($message), $name, $name, $bitfield & $field );
+ if( $field == Revision::DELETED_RESTRICTED )
+ $innerHTML = "<b>$innerHTML</b>";
+ $line = Xml::tags( 'td', array( 'class' => 'mw-input' ), $innerHTML );
+ $html .= "<tr>$line</tr>\n";
}
- // Archived files...
+ // Otherwise, use tri-state radios
} else {
- // Run through and pull all our data in one query
- foreach( $this->afiles as $id ) {
- $where[] = intval($id);
- }
- $result = $dbr->select( 'filearchive', '*',
- array(
- 'fa_name' => $this->page->getDBKey(),
- 'fa_id' => $where ),
- __METHOD__ );
- while( $row = $dbr->fetchObject( $result ) ) {
- $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row );
- }
-
- foreach( $this->afiles as $fileid ) {
- if( !isset($filesObjs[$fileid]) ) {
- continue;
- } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) {
- // If a rev is hidden from sysops
- if( !$this->wasPosted ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return;
- }
- $UserAllowed = false;
+ $html .= '<tr>';
+ $html .= '<th class="mw-revdel-checkbox">'.wfMsgHtml('revdelete-radio-same').'</th>';
+ $html .= '<th class="mw-revdel-checkbox">'.wfMsgHtml('revdelete-radio-unset').'</th>';
+ $html .= '<th class="mw-revdel-checkbox">'.wfMsgHtml('revdelete-radio-set').'</th>';
+ $html .= "<th></th></tr>\n";
+ foreach( $this->checks as $item ) {
+ list( $message, $name, $field ) = $item;
+ // If there are several items, use third state by default...
+ if( $this->submitClicked ) {
+ $selected = $wgRequest->getInt( $name, 0 /* unchecked */ );
+ } else {
+ $selected = -1; // use existing field
+ }
+ $line = '<td class="mw-revdel-checkbox">' . Xml::radio( $name, -1, $selected == -1 ) . '</td>';
+ $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 0, $selected == 0 ) . '</td>';
+ $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 1, $selected == 1 ) . '</td>';
+ $label = wfMsgHtml($message);
+ if( $field == Revision::DELETED_RESTRICTED ) {
+ $label = "<b>$label</b>";
}
- $revisions++;
- // Inject history info
- $wgOut->addHTML( $this->archivedfileLine( $filesObjs[$fileid] ) );
- $bitfields |= $filesObjs[$fileid]->deleted;
+ $line .= "<td>$label</td>";
+ $html .= "<tr>$line</tr>\n";
}
}
- if( !$revisions ) {
- $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
- return;
- }
- $wgOut->addHTML( "</ul>" );
- // Explanation text
- $this->addUsageText();
- // Normal sysops can always see what they did, but can't always change it
- if( !$UserAllowed ) return;
-
- $items = array(
- Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
- Xml::submitButton( wfMsg( 'revdelete-submit' ) )
- );
- $hidden = array(
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ),
- Xml::hidden( 'target', $this->page->getPrefixedText() ),
- Xml::hidden( 'type', $this->deleteKey )
- );
- if( $this->deleteKey=='oldimage' ) {
- foreach( $this->ofiles as $filename )
- $hidden[] = Xml::hidden( 'oldimage[]', $filename );
- } else {
- foreach( $this->afiles as $fileid )
- $hidden[] = Xml::hidden( 'fileid[]', $fileid );
- }
- $special = SpecialPage::getTitleFor( 'Revisiondelete' );
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ),
- 'id' => 'mw-revdel-form-filerevisions' ) ) .
- Xml::fieldset( wfMsg( 'revdelete-legend' ) )
- );
+ $html .= '</table>';
+ return $html;
+ }
- $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) );
- foreach( $items as $item ) {
- $wgOut->addHTML( "<p>$item</p>" );
+ /**
+ * UI entry point for form submission.
+ * @param $request WebRequest
+ */
+ protected function submit( $request ) {
+ global $wgUser, $wgOut;
+ # Check edit token on submission
+ if( $this->submitClicked && !$wgUser->matchEditToken( $request->getVal('wpEditToken') ) ) {
+ $wgOut->addWikiMsg( 'sessionfailure' );
+ return false;
}
- foreach( $hidden as $item ) {
- $wgOut->addHTML( $item );
+ $bitParams = $this->extractBitParams( $request );
+ $listReason = $request->getText( 'wpRevDeleteReasonList', 'other' ); // from dropdown
+ $comment = $listReason;
+ if( $comment != 'other' && $this->otherReason != '' ) {
+ // Entry from drop down menu + additional comment
+ $comment .= wfMsgForContent( 'colon-separator' ) . $this->otherReason;
+ } elseif( $comment == 'other' ) {
+ $comment = $this->otherReason;
}
-
- $wgOut->addHTML(
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) . "\n"
- );
+ # Can the user set this field?
+ if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$wgUser->isAllowed('suppressrevision') ) {
+ $wgOut->permissionRequired( 'suppressrevision' );
+ return false;
+ }
+ # If the save went through, go to success message...
+ $status = $this->save( $bitParams, $comment, $this->targetObj );
+ if ( $status->isGood() ) {
+ $this->success();
+ return true;
+ # ...otherwise, bounce back to form...
+ } else {
+ $this->failure( $status );
+ }
+ return false;
}
/**
- * This lets a user set restrictions for log items
+ * Report that the submit operation succeeded
*/
- private function showLogItems() {
- global $wgOut, $wgUser, $wgMessageCache, $wgLang;
- $UserAllowed = true;
-
- $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->events) ) );
+ protected function success() {
+ global $wgOut;
+ $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
+ $wgOut->wrapWikiMsg( '<span class="success">$1</span>', $this->typeInfo['success'] );
+ $this->list->reloadFromMaster();
+ $this->showForm();
+ }
- $bitfields = 0;
- $wgOut->addHTML( "<ul>" );
+ /**
+ * Report that the submit operation failed
+ */
+ protected function failure( $status ) {
+ global $wgOut;
+ $wgOut->setPagetitle( wfMsg( 'actionfailed' ) );
+ $wgOut->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) );
+ $this->showForm();
+ }
- $where = $logRows = array();
- $dbr = wfGetDB( DB_MASTER );
- // Run through and pull all our data in one query
- $logItems = 0;
- foreach( $this->events as $logid ) {
- $where[] = intval($logid);
+ /**
+ * Put together an array that contains -1, 0, or the *_deleted const for each bit
+ * @param $request WebRequest
+ * @return array
+ */
+ protected function extractBitParams( $request ) {
+ $bitfield = array();
+ foreach( $this->checks as $item ) {
+ list( /* message */ , $name, $field ) = $item;
+ $val = $request->getInt( $name, 0 /* unchecked */ );
+ if( $val < -1 || $val > 1) {
+ $val = -1; // -1 for existing value
+ }
+ $bitfield[$field] = $val;
}
- list($log,$logtype) = explode( '/',$this->page->getDBKey(), 2 );
- $result = $dbr->select( 'logging', '*',
- array(
- 'log_type' => $logtype,
- 'log_id' => $where ),
- __METHOD__ );
- while( $row = $dbr->fetchObject( $result ) ) {
- $logRows[$row->log_id] = $row;
+ if( !isset($bitfield[Revision::DELETED_RESTRICTED]) ) {
+ $bitfield[Revision::DELETED_RESTRICTED] = 0;
}
- $wgMessageCache->loadAllMessages();
- foreach( $this->events as $logid ) {
- // Don't hide from oversight log!!!
- if( !isset( $logRows[$logid] ) || $logRows[$logid]->log_type=='suppress' ) {
- continue;
- } else if( !LogEventsList::userCan( $logRows[$logid],Revision::DELETED_RESTRICTED) ) {
- // If an event is hidden from sysops
- if( !$this->wasPosted ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return;
- }
- $UserAllowed = false;
+ return $bitfield;
+ }
+
+ /**
+ * Put together a rev_deleted bitfield
+ * @param $bitPars array extractBitParams() params
+ * @param $oldfield int current bitfield
+ * @return array
+ */
+ public static function extractBitfield( $bitPars, $oldfield ) {
+ // Build the actual new rev_deleted bitfield
+ $newBits = 0;
+ foreach( $bitPars as $const => $val ) {
+ if( $val == 1 ) {
+ $newBits |= $const; // $const is the *_deleted const
+ } else if( $val == -1 ) {
+ $newBits |= ($oldfield & $const); // use existing
}
- $logItems++;
- $wgOut->addHTML( $this->logLine( $logRows[$logid] ) );
- $bitfields |= $logRows[$logid]->log_deleted;
- }
- if( !$logItems ) {
- $wgOut->showErrorPage( 'revdelete-nologid-title', 'revdelete-nologid-text' );
- return;
}
-
- $wgOut->addHTML( "</ul>" );
- // Explanation text
- $this->addUsageText();
- // Normal sysops can always see what they did, but can't always change it
- if( !$UserAllowed ) return;
+ return $newBits;
+ }
- $items = array(
- Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
- Xml::submitButton( wfMsg( 'revdelete-submit' ) ) );
- $hidden = array(
- Xml::hidden( 'wpEditToken', $wgUser->editToken() ),
- Xml::hidden( 'target', $this->page->getPrefixedText() ),
- Xml::hidden( 'type', $this->deleteKey ) );
- foreach( $this->events as $logid ) {
- $hidden[] = Xml::hidden( 'logid[]', $logid );
- }
-
- $special = SpecialPage::getTitleFor( 'Revisiondelete' );
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ),
- 'id' => 'mw-revdel-form-logs' ) ) .
- Xml::fieldset( wfMsg( 'revdelete-legend' ) )
+ /**
+ * Do the write operations. Simple wrapper for RevDel_*List::setVisibility().
+ */
+ protected function save( $bitfield, $reason, $title ) {
+ return $this->getList()->setVisibility(
+ array( 'value' => $bitfield, 'comment' => $reason )
);
-
- $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) );
- foreach( $items as $item ) {
- $wgOut->addHTML( "<p>$item</p>" );
+ }
+}
+
+/**
+ * Temporary b/c interface, collection of static functions.
+ * @ingroup SpecialPage
+ */
+class RevisionDeleter {
+ /**
+ * Checks for a change in the bitfield for a certain option and updates the
+ * provided array accordingly.
+ *
+ * @param $desc String: description to add to the array if the option was
+ * enabled / disabled.
+ * @param $field Integer: the bitmask describing the single option.
+ * @param $diff Integer: the xor of the old and new bitfields.
+ * @param $new Integer: the new bitfield
+ * @param $arr Array: the array to update.
+ */
+ protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
+ if( $diff & $field ) {
+ $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
+ }
+ }
+
+ /**
+ * Gets an array describing the changes made to the visibilit of the revision.
+ * If the resulting array is $arr, then $arr[0] will contain an array of strings
+ * describing the items that were hidden, $arr[2] will contain an array of strings
+ * describing the items that were unhidden, and $arr[3] will contain an array with
+ * a single string, which can be one of "applied restrictions to sysops",
+ * "removed restrictions from sysops", or null.
+ *
+ * @param $n Integer: the new bitfield.
+ * @param $o Integer: the old bitfield.
+ * @return An array as described above.
+ */
+ protected static function getChanges( $n, $o ) {
+ $diff = $n ^ $o;
+ $ret = array( 0 => array(), 1 => array(), 2 => array() );
+ // Build bitfield changes in language
+ self::checkItem( wfMsgForContent( 'revdelete-content' ),
+ Revision::DELETED_TEXT, $diff, $n, $ret );
+ self::checkItem( wfMsgForContent( 'revdelete-summary' ),
+ Revision::DELETED_COMMENT, $diff, $n, $ret );
+ self::checkItem( wfMsgForContent( 'revdelete-uname' ),
+ Revision::DELETED_USER, $diff, $n, $ret );
+ // Restriction application to sysops
+ if( $diff & Revision::DELETED_RESTRICTED ) {
+ if( $n & Revision::DELETED_RESTRICTED )
+ $ret[2][] = wfMsgForContent( 'revdelete-restricted' );
+ else
+ $ret[2][] = wfMsgForContent( 'revdelete-unrestricted' );
}
- foreach( $hidden as $item ) {
- $wgOut->addHTML( $item );
+ return $ret;
+ }
+
+ /**
+ * Gets a log message to describe the given revision visibility change. This
+ * message will be of the form "[hid {content, edit summary, username}];
+ * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
+ *
+ * @param $count Integer: The number of effected revisions.
+ * @param $nbitfield Integer: The new bitfield for the revision.
+ * @param $obitfield Integer: The old bitfield for the revision.
+ * @param $isForLog Boolean
+ */
+ public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false ) {
+ global $wgLang;
+ $s = '';
+ $changes = self::getChanges( $nbitfield, $obitfield );
+ if( count( $changes[0] ) ) {
+ $s .= wfMsgForContent( 'revdelete-hid', implode( ', ', $changes[0] ) );
}
+ if( count( $changes[1] ) ) {
+ if ($s) $s .= '; ';
+ $s .= wfMsgForContent( 'revdelete-unhid', implode( ', ', $changes[1] ) );
+ }
+ if( count( $changes[2] ) ) {
+ $s .= $s ? ' (' . $changes[2][0] . ')' : $changes[2][0];
+ }
+ $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
+ return wfMsgExt( $msg, array( 'parsemag', 'content' ), $s, $wgLang->formatNum($count) );
- $wgOut->addHTML(
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' ) . "\n"
- );
}
- private function addUsageText() {
- global $wgOut, $wgUser;
- $wgOut->addWikiMsg( 'revdelete-text' );
- if( $wgUser->isAllowed( 'suppressrevision' ) ) {
- $wgOut->addWikiMsg( 'revdelete-suppress-text' );
+ // Get DB field name for URL param...
+ // Future code for other things may also track
+ // other types of revision-specific changes.
+ // @returns string One of log_id/rev_id/fa_id/ar_timestamp/oi_archive_name
+ public static function getRelationType( $typeName ) {
+ if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
+ $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
+ }
+ if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
+ $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
+ $list = new $class( null, null, null );
+ return $list->getIdField();
+ } else {
+ return null;
}
}
-
+}
+
+/**
+ * Abstract base class for a list of deletable items
+ */
+abstract class RevDel_List {
+ var $special, $title, $ids, $res, $current;
+ var $type = null; // override this
+ var $idField = null; // override this
+ var $dateField = false; // override this
+ var $authorIdField = false; // override this
+ var $authorNameField = false; // override this
+
/**
- * @param int $bitfields, aggregate bitfield of all the bitfields
- * @returns string HTML
- */
- private function buildCheckBoxes( $bitfields ) {
- $html = '';
- // FIXME: all items checked for just one rev are checked, even if not set for the others
- foreach( $this->checks as $item ) {
- list( $message, $name, $field ) = $item;
- $line = Xml::tags( 'div', null, Xml::checkLabel( wfMsg($message), $name, $name,
- $bitfields & $field ) );
- if( $field == Revision::DELETED_RESTRICTED ) $line = "<b>$line</b>";
- $html .= $line;
- }
- return $html;
+ * @param $special The parent SpecialPage
+ * @param $title The target title
+ * @param $ids Array of IDs
+ */
+ public function __construct( $special, $title, $ids ) {
+ $this->special = $special;
+ $this->title = $title;
+ $this->ids = $ids;
}
/**
- * @param Revision $rev
- * @returns string
+ * Get the internal type name of this list. Equal to the table name.
*/
- private function historyLine( $rev ) {
- global $wgLang, $wgUser;
+ public function getType() {
+ return $this->type;
+ }
- $date = $wgLang->timeanddate( $rev->getTimestamp() );
- $difflink = $del = '';
- // Live revisions
- if( $this->deleteKey=='oldid' ) {
- $tokenParams = '&unhide=1&token='.urlencode( $wgUser->editToken( $rev->getId() ) );
- $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid='.$rev->getId() . $tokenParams );
- $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'),
- 'diff=' . $rev->getId() . '&oldid=prev' . $tokenParams ) . ')';
- // Archived revisions
- } else {
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- $target = $this->page->getPrefixedText();
- $revlink = $this->skin->makeLinkObj( $undelete, $date,
- "target=$target&timestamp=" . $rev->getTimestamp() );
- $difflink = '(' . $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml('diff'),
- "target=$target&diff=prev&timestamp=" . $rev->getTimestamp() ) . ')';
- }
- // Check permissions; items may be "suppressed"
- if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
- $revlink = '<span class="history-deleted">'.$revlink.'</span>';
- $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
- if( !$rev->userCan(Revision::DELETED_TEXT) ) {
- $revlink = '<span class="history-deleted">'.$date.'</span>';
- $difflink = '(' . wfMsgHtml('diff') . ')';
- }
- }
- $userlink = $this->skin->revUserLink( $rev );
- $comment = $this->skin->revComment( $rev );
+ /**
+ * Get the DB field name associated with the ID list
+ */
+ public function getIdField() {
+ return $this->idField;
+ }
- return "<li>$difflink $revlink $userlink $comment{$del}</li>";
+ /**
+ * Get the DB field name storing timestamps
+ */
+ public function getTimestampField() {
+ return $this->dateField;
}
/**
- * @param File $file
- * @returns string
+ * Get the DB field name storing user ids
*/
- private function fileLine( $file ) {
- global $wgLang, $wgTitle;
+ public function getAuthorIdField() {
+ return $this->authorIdField;
+ }
- $target = $this->page->getPrefixedText();
- $date = $wgLang->timeanddate( $file->getTimestamp(), true );
+ /**
+ * Get the DB field name storing user names
+ */
+ public function getAuthorNameField() {
+ return $this->authorNameField;
+ }
+ /**
+ * Set the visibility for the revisions in this list. Logging and
+ * transactions are done here.
+ *
+ * @param $params Associative array of parameters. Members are:
+ * value: The integer value to set the visibility to
+ * comment: The log comment.
+ * @return Status
+ */
+ public function setVisibility( $params ) {
+ $bitPars = $params['value'];
+ $comment = $params['comment'];
- $del = '';
- # Hidden files...
- if( $file->isDeleted(File::DELETED_FILE) ) {
- $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
- if( !$file->userCan(File::DELETED_FILE) ) {
- $pageLink = $date;
+ $this->res = false;
+ $dbw = wfGetDB( DB_MASTER );
+ $this->doQuery( $dbw );
+ $dbw->begin();
+ $status = Status::newGood();
+ $missing = array_flip( $this->ids );
+ $this->clearFileOps();
+ $idsForLog = array();
+ $authorIds = $authorIPs = array();
+
+ for ( $this->reset(); $this->current(); $this->next() ) {
+ $item = $this->current();
+ unset( $missing[ $item->getId() ] );
+
+ $oldBits = $item->getBits();
+ // Build the actual new rev_deleted bitfield
+ $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
+
+ if ( $oldBits == $newBits ) {
+ $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ } elseif ( $oldBits == 0 && $newBits != 0 ) {
+ $opType = 'hide';
+ } elseif ( $oldBits != 0 && $newBits == 0 ) {
+ $opType = 'show';
} else {
- $pageLink = $this->skin->makeKnownLinkObj( $wgTitle, $date,
- "target=$target&file=$file->sha1.".$file->getExtension() );
+ $opType = 'modify';
+ }
+
+ if ( $item->isHideCurrentOp( $newBits ) ) {
+ // Cannot hide current version text
+ $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ }
+ if ( !$item->canView() ) {
+ // Cannot access this revision
+ $msg = ($opType == 'show') ?
+ 'revdelete-show-no-access' : 'revdelete-modify-no-access';
+ $status->error( $msg, $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ }
+ // Cannot just "hide from Sysops" without hiding any fields
+ if( $newBits == Revision::DELETED_RESTRICTED ) {
+ $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
+ continue;
+ }
+
+ // Update the revision
+ $ok = $item->setBits( $newBits );
+
+ if ( $ok ) {
+ $idsForLog[] = $item->getId();
+ $status->successCount++;
+ if( $item->getAuthorId() > 0 ) {
+ $authorIds[] = $item->getAuthorId();
+ } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
+ $authorIPs[] = $item->getAuthorName();
+ }
+ } else {
+ $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
+ $status->failCount++;
}
- $pageLink = '<span class="history-deleted">' . $pageLink . '</span>';
- # Regular files...
- } else {
- $url = $file->getUrlRel();
- $pageLink = "<a href=\"{$url}\">{$date}</a>";
}
- $data = wfMsg( 'widthheight',
- $wgLang->formatNum( $file->getWidth() ),
- $wgLang->formatNum( $file->getHeight() ) ) .
- ' (' . wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $file->getSize() ) ) . ')';
- $data = htmlspecialchars( $data );
+ // Handle missing revisions
+ foreach ( $missing as $id => $unused ) {
+ $status->error( 'revdelete-modify-missing', $id );
+ $status->failCount++;
+ }
- return "<li>$pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del</li>";
- }
+ if ( $status->successCount == 0 ) {
+ $status->ok = false;
+ $dbw->rollback();
+ return $status;
+ }
- /**
- * @param ArchivedFile $file
- * @returns string
- */
- private function archivedfileLine( $file ) {
- global $wgLang;
+ // Save success count
+ $successCount = $status->successCount;
- $target = $this->page->getPrefixedText();
- $date = $wgLang->timeanddate( $file->getTimestamp(), true );
+ // Move files, if there are any
+ $status->merge( $this->doPreCommitUpdates() );
+ if ( !$status->isOK() ) {
+ // Fatal error, such as no configured archive directory
+ $dbw->rollback();
+ return $status;
+ }
- $undelete = SpecialPage::getTitleFor( 'Undelete' );
- $pageLink = $this->skin->makeKnownLinkObj( $undelete, $date, "target=$target&file={$file->getKey()}" );
+ // Log it
+ $this->updateLog( array(
+ 'title' => $this->title,
+ 'count' => $successCount,
+ 'newBits' => $newBits,
+ 'oldBits' => $oldBits,
+ 'comment' => $comment,
+ 'ids' => $idsForLog,
+ 'authorIds' => $authorIds,
+ 'authorIPs' => $authorIPs
+ ) );
+ $dbw->commit();
- $del = '';
- if( $file->isDeleted(File::DELETED_FILE) ) {
- $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
- }
+ // Clear caches
+ $status->merge( $this->doPostCommitUpdates() );
+ return $status;
+ }
- $data = wfMsg( 'widthheight',
- $wgLang->formatNum( $file->getWidth() ),
- $wgLang->formatNum( $file->getHeight() ) ) .
- ' (' . wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $file->getSize() ) ) . ')';
- $data = htmlspecialchars( $data );
+ /**
+ * Reload the list data from the master DB. This can be done after setVisibility()
+ * to allow $item->getHTML() to show the new data.
+ */
+ function reloadFromMaster() {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->res = $this->doQuery( $dbw );
+ }
- return "<li> $pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del</li>";
+ /**
+ * Record a log entry on the action
+ * @param $params Associative array of parameters:
+ * newBits: The new value of the *_deleted bitfield
+ * oldBits: The old value of the *_deleted bitfield.
+ * title: The target title
+ * ids: The ID list
+ * comment: The log comment
+ * authorsIds: The array of the user IDs of the offenders
+ * authorsIPs: The array of the IP/anon user offenders
+ */
+ protected function updateLog( $params ) {
+ // Get the URL param's corresponding DB field
+ $field = RevisionDeleter::getRelationType( $this->getType() );
+ if( !$field ) {
+ throw new MWException( "Bad log URL param type!" );
+ }
+ // Put things hidden from sysops in the oversight log
+ if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
+ $logType = 'suppress';
+ } else {
+ $logType = 'delete';
+ }
+ // Add params for effected page and ids
+ $logParams = $this->getLogParams( $params );
+ // Actually add the deletion log entry
+ $log = new LogPage( $logType );
+ $logid = $log->addEntry( $this->getLogAction(), $params['title'],
+ $params['comment'], $logParams );
+ // Allow for easy searching of deletion log items for revision/log items
+ $log->addRelations( $field, $params['ids'], $logid );
+ $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
+ $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
}
/**
- * @param Array $row row
- * @returns string
+ * Get the log action for this list type
*/
- private function logLine( $row ) {
- global $wgLang;
+ public function getLogAction() {
+ return 'revision';
+ }
- $date = $wgLang->timeanddate( $row->log_timestamp );
- $paramArray = LogPage::extractParams( $row->log_params );
- $title = Title::makeTitle( $row->log_namespace, $row->log_title );
+ /**
+ * Get log parameter array.
+ * @param $params Associative array of log parameters, same as updateLog()
+ * @return array
+ */
+ public function getLogParams( $params ) {
+ return array(
+ $this->getType(),
+ implode( ',', $params['ids'] ),
+ "ofield={$params['oldBits']}",
+ "nfield={$params['newBits']}"
+ );
+ }
- $logtitle = SpecialPage::getTitleFor( 'Log' );
- $loglink = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'log' ),
- wfArrayToCGI( array( 'page' => $title->getPrefixedUrl() ) ) );
- // Action text
- if( !LogEventsList::userCan($row,LogPage::DELETED_ACTION) ) {
- $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
+ /**
+ * Initialise the current iteration pointer
+ */
+ protected function initCurrent() {
+ $row = $this->res->current();
+ if ( $row ) {
+ $this->current = $this->newItem( $row );
} else {
- $action = LogPage::actionText( $row->log_type, $row->log_action, $title,
- $this->skin, $paramArray, true, true );
- if( $row->log_deleted & LogPage::DELETED_ACTION )
- $action = '<span class="history-deleted">' . $action . '</span>';
- }
- // User links
- $userLink = $this->skin->userLink( $row->log_user, User::WhoIs($row->log_user) );
- if( LogEventsList::isDeleted($row,LogPage::DELETED_USER) ) {
- $userLink = '<span class="history-deleted">' . $userLink . '</span>';
+ $this->current = false;
}
- // Comment
- $comment = $wgLang->getDirMark() . $this->skin->commentBlock( $row->log_comment );
- if( LogEventsList::isDeleted($row,LogPage::DELETED_COMMENT) ) {
- $comment = '<span class="history-deleted">' . $comment . '</span>';
- }
- return "<li>($loglink) $date $userLink $action $comment</li>";
}
/**
- * Generate a user tool link cluster if the current user is allowed to view it
- * @param ArchivedFile $file
- * @return string HTML
+ * Start iteration. This must be called before current() or next().
+ * @return First list item
*/
- private function fileUserTools( $file ) {
- if( $file->userCan( Revision::DELETED_USER ) ) {
- $link = $this->skin->userLink( $file->user, $file->user_text ) .
- $this->skin->userToolLinks( $file->user, $file->user_text );
+ public function reset() {
+ if ( !$this->res ) {
+ $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
} else {
- $link = wfMsgHtml( 'rev-deleted-user' );
- }
- if( $file->isDeleted( Revision::DELETED_USER ) ) {
- return '<span class="history-deleted">' . $link . '</span>';
+ $this->res->rewind();
}
- return $link;
+ $this->initCurrent();
+ return $this->current;
}
/**
- * Wrap and format the given file's comment block, if the current
- * user is allowed to view it.
- *
- * @param ArchivedFile $file
- * @return string HTML
+ * Get the current list item, or false if we are at the end
*/
- private function fileComment( $file ) {
- if( $file->userCan( File::DELETED_COMMENT ) ) {
- $block = $this->skin->commentBlock( $file->description );
- } else {
- $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
- }
- if( $file->isDeleted( File::DELETED_COMMENT ) ) {
- return "<span class=\"history-deleted\">$block</span>";
- }
- return $block;
+ public function current() {
+ return $this->current;
}
/**
- * @param WebRequest $request
+ * Move the iteration pointer to the next list item, and return it.
*/
- private function submit( $request ) {
- global $wgUser, $wgOut;
- # Check edit token on submission
- if( $this->wasPosted && !$wgUser->matchEditToken( $request->getVal('wpEditToken') ) ) {
- $wgOut->addWikiMsg( 'sessionfailure' );
- return false;
- }
- $bitfield = $this->extractBitfield( $request );
- $comment = $request->getText( 'wpReason' );
- # Can the user set this field?
- if( $bitfield & Revision::DELETED_RESTRICTED && !$wgUser->isAllowed('suppressrevision') ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return false;
- }
- # If the save went through, go to success message. Otherwise
- # bounce back to form...
- if( $this->save( $bitfield, $comment, $this->page ) ) {
- $this->success();
- } else if( $request->getCheck( 'oldid' ) || $request->getCheck( 'artimestamp' ) ) {
- return $this->showRevs();
- } else if( $request->getCheck( 'logid' ) ) {
- return $this->showLogs();
- } else if( $request->getCheck( 'oldimage' ) || $request->getCheck( 'fileid' ) ) {
- return $this->showImages();
+ public function next() {
+ $this->res->next();
+ $this->initCurrent();
+ return $this->current;
+ }
+
+ /**
+ * Get the number of items in the list.
+ */
+ public function length() {
+ if( !$this->res ) {
+ return 0;
+ } else {
+ return $this->res->numRows();
}
}
- private function success() {
- global $wgOut;
-
- $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
-
- $wrap = '<span class="success">$1</span>';
+ /**
+ * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates()
+ * STUB
+ */
+ public function clearFileOps() {
+ }
- if( $this->deleteKey=='logid' ) {
- $wgOut->wrapWikiMsg( $wrap, 'logdelete-success' );
- $this->showLogItems();
- } else if( $this->deleteKey=='oldid' || $this->deleteKey=='artimestamp' ) {
- $wgOut->wrapWikiMsg( $wrap, 'revdelete-success' );
- $this->showRevs();
- } else if( $this->deleteKey=='fileid' ) {
- $wgOut->wrapWikiMsg( $wrap, 'revdelete-success' );
- $this->showImages();
- } else if( $this->deleteKey=='oldimage' ) {
- $wgOut->wrapWikiMsg( $wrap, 'revdelete-success' );
- $this->showImages();
- }
+ /**
+ * A hook for setVisibility(): do batch updates pre-commit.
+ * STUB
+ * @return Status
+ */
+ public function doPreCommitUpdates() {
+ return Status::newGood();
}
/**
- * Put together a rev_deleted bitfield from the submitted checkboxes
- * @param WebRequest $request
- * @return int
+ * A hook for setVisibility(): do any necessary updates post-commit.
+ * STUB
+ * @return Status
*/
- private function extractBitfield( $request ) {
- $bitfield = 0;
- foreach( $this->checks as $item ) {
- list( /* message */ , $name, $field ) = $item;
- if( $request->getCheck( $name ) ) {
- $bitfield |= $field;
- }
- }
- return $bitfield;
+ public function doPostCommitUpdates() {
+ return Status::newGood();
}
- private function save( $bitfield, $reason, $title ) {
- $dbw = wfGetDB( DB_MASTER );
- // Don't allow simply locking the interface for no reason
- if( $bitfield == Revision::DELETED_RESTRICTED ) {
- $bitfield = 0;
- }
- $deleter = new RevisionDeleter( $dbw );
- // By this point, only one of the below should be set
- if( isset($this->revisions) ) {
- return $deleter->setRevVisibility( $title, $this->revisions, $bitfield, $reason );
- } else if( isset($this->archrevs) ) {
- return $deleter->setArchiveVisibility( $title, $this->archrevs, $bitfield, $reason );
- } else if( isset($this->ofiles) ) {
- return $deleter->setOldImgVisibility( $title, $this->ofiles, $bitfield, $reason );
- } else if( isset($this->afiles) ) {
- return $deleter->setArchFileVisibility( $title, $this->afiles, $bitfield, $reason );
- } else if( isset($this->events) ) {
- return $deleter->setEventVisibility( $title, $this->events, $bitfield, $reason );
- }
- }
+ /**
+ * Create an item object from a DB result row
+ * @param $row stdclass
+ */
+ abstract public function newItem( $row );
+
+ /**
+ * Do the DB query to iterate through the objects.
+ * @param $db Database object to use for the query
+ */
+ abstract public function doQuery( $db );
+
+ /**
+ * Get the integer value of the flag used for suppression
+ */
+ abstract public function getSuppressBit();
}
/**
- * Implements the actions for Revision Deletion.
- * @ingroup SpecialPage
+ * Abstract base class for deletable items
*/
-class RevisionDeleter {
- function __construct( $db ) {
- $this->dbw = $db;
+abstract class RevDel_Item {
+ /** The parent SpecialPage */
+ var $special;
+
+ /** The parent RevDel_List */
+ var $list;
+
+ /** The DB result row */
+ var $row;
+
+ /**
+ * @param $list RevDel_List
+ * @param $row DB result row
+ */
+ public function __construct( $list, $row ) {
+ $this->special = $list->special;
+ $this->list = $list;
+ $this->row = $row;
}
/**
- * @param $title, the page these events apply to
- * @param array $items list of revision ID numbers
- * @param int $bitfield new rev_deleted value
- * @param string $comment Comment for log records
+ * Get the ID, as it would appear in the ids URL parameter
*/
- function setRevVisibility( $title, $items, $bitfield, $comment ) {
- global $wgOut;
+ public function getId() {
+ $field = $this->list->getIdField();
+ return $this->row->$field;
+ }
- $userAllowedAll = $success = true;
- $revIDs = array();
- $revCount = 0;
- // Run through and pull all our data in one query
- foreach( $items as $revid ) {
- $where[] = intval($revid);
- }
- $result = $this->dbw->select( 'revision', '*',
- array(
- 'rev_page' => $title->getArticleID(),
- 'rev_id' => $where ),
- __METHOD__ );
- while( $row = $this->dbw->fetchObject( $result ) ) {
- $revObjs[$row->rev_id] = new Revision( $row );
- }
- // To work!
- foreach( $items as $revid ) {
- if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) {
- $success = false;
- continue; // Must exist
- } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) {
- $userAllowedAll=false;
- continue;
- }
- // For logging, maintain a count of revisions
- if( $revObjs[$revid]->mDeleted != $bitfield ) {
- $revCount++;
- $revIDs[]=$revid;
+ /**
+ * Get the date, formatted with $wgLang
+ */
+ public function formatDate() {
+ global $wgLang;
+ return $wgLang->date( $this->getTimestamp() );
+ }
- $this->updateRevision( $revObjs[$revid], $bitfield );
- $this->updateRecentChangesEdits( $revObjs[$revid], $bitfield, false );
- }
- }
- // Clear caches...
- // Don't log or touch if nothing changed
- if( $revCount > 0 ) {
- $this->updateLog( $title, $revCount, $bitfield, $revObjs[$revid]->mDeleted,
- $comment, $title, 'oldid', $revIDs );
- $this->updatePage( $title );
- }
- // Where all revs allowed to be set?
- if( !$userAllowedAll ) {
- //FIXME: still might be confusing???
- $wgOut->permissionRequired( 'suppressrevision' );
- return false;
- }
+ /**
+ * Get the time, formatted with $wgLang
+ */
+ public function formatTime() {
+ global $wgLang;
+ return $wgLang->time( $this->getTimestamp() );
+ }
- return $success;
+ /**
+ * Get the timestamp in MW 14-char form
+ */
+ public function getTimestamp() {
+ $field = $this->list->getTimestampField();
+ return wfTimestamp( TS_MW, $this->row->$field );
+ }
+
+ /**
+ * Get the author user ID
+ */
+ public function getAuthorId() {
+ $field = $this->list->getAuthorIdField();
+ return intval( $this->row->$field );
+ }
+
+ /**
+ * Get the author user name
+ */
+ public function getAuthorName() {
+ $field = $this->list->getAuthorNameField();
+ return strval( $this->row->$field );
}
- /**
- * @param $title, the page these events apply to
- * @param array $items list of revision ID numbers
- * @param int $bitfield new rev_deleted value
- * @param string $comment Comment for log records
+ /**
+ * Returns true if the item is "current", and the operation to set the given
+ * bits can't be executed for that reason
+ * STUB
*/
- function setArchiveVisibility( $title, $items, $bitfield, $comment ) {
- global $wgOut;
+ public function isHideCurrentOp( $newBits ) {
+ return false;
+ }
- $userAllowedAll = $success = true;
- $count = 0;
- $Id_set = array();
- // Run through and pull all our data in one query
- foreach( $items as $timestamp ) {
- $where[] = $this->dbw->timestamp( $timestamp );
- }
- $result = $this->dbw->select( 'archive', '*',
- array(
- 'ar_namespace' => $title->getNamespace(),
- 'ar_title' => $title->getDBKey(),
- 'ar_timestamp' => $where ),
- __METHOD__ );
- while( $row = $this->dbw->fetchObject( $result ) ) {
- $timestamp = wfTimestamp( TS_MW, $row->ar_timestamp );
- $revObjs[$timestamp] = new Revision( array(
- 'page' => $title->getArticleId(),
- 'id' => $row->ar_rev_id,
- 'text' => $row->ar_text_id,
- 'comment' => $row->ar_comment,
- 'user' => $row->ar_user,
- 'user_text' => $row->ar_user_text,
- 'timestamp' => $timestamp,
- 'minor_edit' => $row->ar_minor_edit,
- 'text_id' => $row->ar_text_id,
- 'deleted' => $row->ar_deleted,
- 'len' => $row->ar_len) );
- }
- // To work!
- foreach( $items as $timestamp ) {
- // This will only select the first revision with this timestamp.
- // Since they are all selected/deleted at once, we can just check the
- // permissions of one. UPDATE is done via timestamp, so all revs are set.
- if( !is_object($revObjs[$timestamp]) ) {
- $success = false;
- continue; // Must exist
- } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) {
- $userAllowedAll=false;
- continue;
- }
- // Which revisions did we change anything about?
- if( $revObjs[$timestamp]->mDeleted != $bitfield ) {
- $Id_set[]=$timestamp;
- $count++;
+ /**
+ * Returns true if the current user can view the item
+ */
+ abstract public function canView();
+
+ /**
+ * Returns true if the current user can view the item text/file
+ */
+ abstract public function canViewContent();
- $this->updateArchive( $revObjs[$timestamp], $title, $bitfield );
- }
- }
- // For logging, maintain a count of revisions
- if( $count > 0 ) {
- $this->updateLog( $title, $count, $bitfield, $revObjs[$timestamp]->mDeleted,
- $comment, $title, 'artimestamp', $Id_set );
- }
- // Where all revs allowed to be set?
- if( !$userAllowedAll ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return false;
- }
+ /**
+ * Get the current deletion bitfield value
+ */
+ abstract public function getBits();
- return $success;
- }
+ /**
+ * Get the HTML of the list item. Should be include <li></li> tags.
+ * This is used to show the list in HTML form, by the special page.
+ */
+ abstract public function getHTML();
- /**
- * @param $title, the page these events apply to
- * @param array $items list of revision ID numbers
- * @param int $bitfield new rev_deleted value
- * @param string $comment Comment for log records
+ /**
+ * Set the visibility of the item. This should do any necessary DB queries.
+ *
+ * The DB update query should have a condition which forces it to only update
+ * if the value in the DB matches the value fetched earlier with the SELECT.
+ * If the update fails because it did not match, the function should return
+ * false. This prevents concurrency problems.
+ *
+ * @return boolean success
*/
- function setOldImgVisibility( $title, $items, $bitfield, $comment ) {
- global $wgOut;
+ abstract public function setBits( $newBits );
+}
- $userAllowedAll = $success = true;
- $count = 0;
- $set = array();
- // Run through and pull all our data in one query
- foreach( $items as $timestamp ) {
- $where[] = $timestamp.'!'.$title->getDBKey();
- }
- $result = $this->dbw->select( 'oldimage', '*',
+/**
+ * List for revision table items
+ */
+class RevDel_RevisionList extends RevDel_List {
+ var $currentRevId;
+ var $type = 'revision';
+ var $idField = 'rev_id';
+ var $dateField = 'rev_timestamp';
+ var $authorIdField = 'rev_user';
+ var $authorNameField = 'rev_user_text';
+
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ return $db->select( array('revision','page'), '*',
array(
- 'oi_name' => $title->getDBKey(),
- 'oi_archive_name' => $where ),
- __METHOD__ );
- while( $row = $this->dbw->fetchObject( $result ) ) {
- $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
- $filesObjs[$row->oi_archive_name]->user = $row->oi_user;
- $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text;
- }
- // To work!
- foreach( $items as $timestamp ) {
- $archivename = $timestamp.'!'.$title->getDBKey();
- if( !isset($filesObjs[$archivename]) ) {
- $success = false;
- continue; // Must exist
- } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) {
- $userAllowedAll=false;
- continue;
- }
+ 'rev_page' => $this->title->getArticleID(),
+ 'rev_id' => $ids,
+ 'rev_page = page_id'
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'rev_id DESC' )
+ );
+ }
- $transaction = true;
- // Which revisions did we change anything about?
- if( $filesObjs[$archivename]->deleted != $bitfield ) {
- $count++;
-
- $this->dbw->begin();
- $this->updateOldFiles( $filesObjs[$archivename], $bitfield );
- // If this image is currently hidden...
- if( $filesObjs[$archivename]->deleted & File::DELETED_FILE ) {
- if( $bitfield & File::DELETED_FILE ) {
- # Leave it alone if we are not changing this...
- $set[]=$archivename;
- $transaction = true;
- } else {
- # We are moving this out
- $transaction = $this->makeOldImagePublic( $filesObjs[$archivename] );
- $set[]=$transaction;
- }
- // Is it just now becoming hidden?
- } else if( $bitfield & File::DELETED_FILE ) {
- $transaction = $this->makeOldImagePrivate( $filesObjs[$archivename] );
- $set[]=$transaction;
- } else {
- $set[]=$timestamp;
- }
- // If our file operations fail, then revert back the db
- if( $transaction==false ) {
- $this->dbw->rollback();
- return false;
- }
- $this->dbw->commit();
- }
- }
+ public function newItem( $row ) {
+ return new RevDel_RevisionItem( $this, $row );
+ }
- // Log if something was changed
- if( $count > 0 ) {
- $this->updateLog( $title, $count, $bitfield, $filesObjs[$archivename]->deleted,
- $comment, $title, 'oldimage', $set );
- # Purge page/history
- $file = wfLocalFile( $title );
- $file->purgeCache();
- $file->purgeHistory();
- # Invalidate cache for all pages using this file
- $update = new HTMLCacheUpdate( $title, 'imagelinks' );
- $update->doUpdate();
- }
- // Where all revs allowed to be set?
- if( !$userAllowedAll ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return false;
+ public function getCurrent() {
+ if ( is_null( $this->currentRevId ) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $this->currentRevId = $dbw->selectField(
+ 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
}
-
- return $success;
+ return $this->currentRevId;
}
- /**
- * @param $title, the page these events apply to
- * @param array $items list of revision ID numbers
- * @param int $bitfield new rev_deleted value
- * @param string $comment Comment for log records
- */
- function setArchFileVisibility( $title, $items, $bitfield, $comment ) {
- global $wgOut;
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
- $userAllowedAll = $success = true;
- $count = 0;
- $Id_set = array();
+ public function doPreCommitUpdates() {
+ $this->title->invalidateCache();
+ return Status::newGood();
+ }
- // Run through and pull all our data in one query
- foreach( $items as $id ) {
- $where[] = intval($id);
- }
- $result = $this->dbw->select( 'filearchive', '*',
- array( 'fa_name' => $title->getDBKey(),
- 'fa_id' => $where ),
- __METHOD__ );
- while( $row = $this->dbw->fetchObject( $result ) ) {
- $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row );
- }
- // To work!
- foreach( $items as $fileid ) {
- if( !isset($filesObjs[$fileid]) ) {
- $success = false;
- continue; // Must exist
- } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) {
- $userAllowedAll=false;
- continue;
- }
- // Which revisions did we change anything about?
- if( $filesObjs[$fileid]->deleted != $bitfield ) {
- $Id_set[]=$fileid;
- $count++;
+ public function doPostCommitUpdates() {
+ $this->title->purgeSquid();
+ // Extensions that require referencing previous revisions may need this
+ wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) );
+ return Status::newGood();
+ }
+}
- $this->updateArchFiles( $filesObjs[$fileid], $bitfield );
- }
- }
- // Log if something was changed
- if( $count > 0 ) {
- $this->updateLog( $title, $count, $bitfield, $comment,
- $filesObjs[$fileid]->deleted, $title, 'fileid', $Id_set );
- }
- // Where all revs allowed to be set?
- if( !$userAllowedAll ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return false;
- }
+/**
+ * Item class for a revision table row
+ */
+class RevDel_RevisionItem extends RevDel_Item {
+ var $revision;
- return $success;
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->revision = new Revision( $row );
}
- /**
- * @param $title, the log page these events apply to
- * @param array $items list of log ID numbers
- * @param int $bitfield new log_deleted value
- * @param string $comment Comment for log records
- */
- function setEventVisibility( $title, $items, $bitfield, $comment ) {
- global $wgOut;
+ public function canView() {
+ return $this->revision->userCan( Revision::DELETED_RESTRICTED );
+ }
+
+ public function canViewContent() {
+ return $this->revision->userCan( Revision::DELETED_TEXT );
+ }
- $userAllowedAll = $success = true;
- $count = 0;
- $log_Ids = array();
+ public function getBits() {
+ return $this->revision->mDeleted;
+ }
- // Run through and pull all our data in one query
- foreach( $items as $logid ) {
- $where[] = intval($logid);
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ // Update revision table
+ $dbw->update( 'revision',
+ array( 'rev_deleted' => $bits ),
+ array(
+ 'rev_id' => $this->revision->getId(),
+ 'rev_page' => $this->revision->getPage(),
+ 'rev_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+ if ( !$dbw->affectedRows() ) {
+ // Concurrent fail!
+ return false;
}
- list($log,$logtype) = explode( '/',$title->getDBKey(), 2 );
- $result = $this->dbw->select( 'logging', '*',
+ // Update recentchanges table
+ $dbw->update( 'recentchanges',
+ array(
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => 1
+ ),
array(
- 'log_type' => $logtype,
- 'log_id' => $where ),
- __METHOD__ );
- while( $row = $this->dbw->fetchObject( $result ) ) {
- $logRows[$row->log_id] = $row;
- }
- // To work!
- foreach( $items as $logid ) {
- if( !isset($logRows[$logid]) ) {
- $success = false;
- continue; // Must exist
- } else if( !LogEventsList::userCan($logRows[$logid], LogPage::DELETED_RESTRICTED)
- || $logRows[$logid]->log_type == 'suppress' ) {
- // Don't hide from oversight log!!!
- $userAllowedAll=false;
- continue;
- }
- // Which logs did we change anything about?
- if( $logRows[$logid]->log_deleted != $bitfield ) {
- $log_Ids[]=$logid;
- $count++;
+ 'rc_this_oldid' => $this->revision->getId(), // condition
+ // non-unique timestamp index
+ 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
+ ),
+ __METHOD__
+ );
+ return true;
+ }
- $this->updateLogs( $logRows[$logid], $bitfield );
- $this->updateRecentChangesLog( $logRows[$logid], $bitfield, true );
- }
- }
- // Don't log or touch if nothing changed
- if( $count > 0 ) {
- $this->updateLog( $title, $count, $bitfield, $logRows[$logid]->log_deleted,
- $comment, $title, 'logid', $log_Ids );
- }
- // Were all revs allowed to be set?
- if( !$userAllowedAll ) {
- $wgOut->permissionRequired( 'suppressrevision' );
- return false;
- }
+ public function isDeleted() {
+ return $this->revision->isDeleted( Revision::DELETED_TEXT );
+ }
- return $success;
+ public function isHideCurrentOp( $newBits ) {
+ return ( $newBits & Revision::DELETED_TEXT )
+ && $this->list->getCurrent() == $this->getId();
}
/**
- * Moves an image to a safe private location
- * Caller is responsible for clearing caches
- * @param File $oimage
- * @returns mixed, timestamp string on success, false on failure
+ * Get the HTML link to the revision text.
+ * Overridden by RevDel_ArchiveItem.
*/
- function makeOldImagePrivate( $oimage ) {
- $transaction = new FSTransaction();
- if( !FileStore::lock() ) {
- wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
- return false;
- }
- $oldpath = $oimage->getArchivePath() . DIRECTORY_SEPARATOR . $oimage->archive_name;
- // Dupe the file into the file store
- if( file_exists( $oldpath ) ) {
- // Is our directory configured?
- if( $store = FileStore::get( 'deleted' ) ) {
- if( !$oimage->sha1 ) {
- $oimage->upgradeRow(); // sha1 may be missing
- }
- $key = $oimage->sha1 . '.' . $oimage->getExtension();
- $transaction->add( $store->insert( $key, $oldpath, FileStore::DELETE_ORIGINAL ) );
- } else {
- $group = null;
- $key = null;
- $transaction = false; // Return an error and do nothing
- }
+ protected function getRevisionLink() {
+ global $wgLang;
+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $date;
+ }
+ return $this->special->skin->link(
+ $this->list->title,
+ $date,
+ array(),
+ array(
+ 'oldid' => $this->revision->getId(),
+ 'unhide' => 1
+ )
+ );
+ }
+
+ /**
+ * Get the HTML link to the diff.
+ * Overridden by RevDel_ArchiveItem
+ */
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return wfMsgHtml('diff');
} else {
- wfDebug( __METHOD__." deleting already-missing '$oldpath'; moving on to database\n" );
- $group = null;
- $key = '';
- $transaction = new FSTransaction(); // empty
+ return
+ $this->special->skin->link(
+ $this->list->title,
+ wfMsgHtml('diff'),
+ array(),
+ array(
+ 'diff' => $this->revision->getId(),
+ 'oldid' => 'prev',
+ 'unhide' => 1
+ ),
+ array(
+ 'known',
+ 'noclasses'
+ )
+ );
}
+ }
- if( $transaction === false ) {
- // Fail to restore?
- wfDebug( __METHOD__.": import to file store failed, aborting\n" );
- throw new MWException( "Could not archive and delete file $oldpath" );
- return false;
+ public function getHTML() {
+ $difflink = $this->getDiffLink();
+ $revlink = $this->getRevisionLink();
+ $userlink = $this->special->skin->revUserLink( $this->revision );
+ $comment = $this->special->skin->revComment( $this->revision );
+ if ( $this->isDeleted() ) {
+ $revlink = "<span class=\"history-deleted\">$revlink</span>";
}
+ return "<li>($difflink) $revlink $userlink $comment</li>";
+ }
+}
- wfDebug( __METHOD__.": set db items, applying file transactions\n" );
- $transaction->commit();
- FileStore::unlock();
+/**
+ * List for archive table items, i.e. revisions deleted via action=delete
+ */
+class RevDel_ArchiveList extends RevDel_RevisionList {
+ var $type = 'archive';
+ var $idField = 'ar_timestamp';
+ var $dateField = 'ar_timestamp';
+ var $authorIdField = 'ar_user';
+ var $authorNameField = 'ar_user_text';
+
+ public function doQuery( $db ) {
+ $timestamps = array();
+ foreach ( $this->ids as $id ) {
+ $timestamps[] = $db->timestamp( $id );
+ }
+ return $db->select( 'archive', '*',
+ array(
+ 'ar_namespace' => $this->title->getNamespace(),
+ 'ar_title' => $this->title->getDBkey(),
+ 'ar_timestamp' => $timestamps
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'ar_timestamp DESC' )
+ );
+ }
- $m = explode('!',$oimage->archive_name,2);
- $timestamp = $m[0];
+ public function newItem( $row ) {
+ return new RevDel_ArchiveItem( $this, $row );
+ }
- return $timestamp;
+ public function doPreCommitUpdates() {
+ return Status::newGood();
}
- /**
- * Moves an image from a safe private location
- * Caller is responsible for clearing caches
- * @param File $oimage
- * @returns mixed, string timestamp on success, false on failure
- */
- function makeOldImagePublic( $oimage ) {
- $transaction = new FSTransaction();
- if( !FileStore::lock() ) {
- wfDebug( __METHOD__." could not acquire filestore lock\n" );
- return false;
- }
+ public function doPostCommitUpdates() {
+ return Status::newGood();
+ }
+}
- $store = FileStore::get( 'deleted' );
- if( !$store ) {
- wfDebug( __METHOD__.": skipping row with no file.\n" );
- return false;
+/**
+ * Item class for a archive table row
+ */
+class RevDel_ArchiveItem extends RevDel_RevisionItem {
+ public function __construct( $list, $row ) {
+ RevDel_Item::__construct( $list, $row );
+ $this->revision = Revision::newFromArchiveRow( $row,
+ array( 'page' => $this->list->title->getArticleId() ) );
+ }
+
+ public function getId() {
+ # Convert DB timestamp to MW timestamp
+ return $this->revision->getTimestamp();
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'archive',
+ array( 'ar_deleted' => $bits ),
+ array( 'ar_namespace' => $this->list->title->getNamespace(),
+ 'ar_title' => $this->list->title->getDBkey(),
+ // use timestamp for index
+ 'ar_timestamp' => $this->row->ar_timestamp,
+ 'ar_rev_id' => $this->row->ar_rev_id,
+ 'ar_deleted' => $this->getBits()
+ ),
+ __METHOD__ );
+ return (bool)$dbw->affectedRows();
+ }
+
+ protected function getRevisionLink() {
+ global $wgLang;
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return $date;
}
+ return $this->special->skin->link( $undelete, $date, array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'timestamp' => $this->revision->getTimestamp()
+ ) );
+ }
- $key = $oimage->sha1.'.'.$oimage->getExtension();
- $destDir = $oimage->getArchivePath();
- if( !is_dir( $destDir ) ) {
- wfMkdirParents( $destDir );
- }
- $destPath = $destDir . DIRECTORY_SEPARATOR . $oimage->archive_name;
- // Check if any other stored revisions use this file;
- // if so, we shouldn't remove the file from the hidden
- // archives so they will still work. Check hidden files first.
- $useCount = $this->dbw->selectField( 'oldimage', '1',
- array( 'oi_sha1' => $oimage->sha1,
- 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ),
- __METHOD__, array( 'FOR UPDATE' ) );
- // Check the rest of the deleted archives too.
- // (these are the ones that don't show in the image history)
- if( !$useCount ) {
- $useCount = $this->dbw->selectField( 'filearchive', '1',
- array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
- __METHOD__, array( 'FOR UPDATE' ) );
- }
-
- if( $useCount == 0 ) {
- wfDebug( __METHOD__.": nothing else using {$oimage->sha1}, will deleting after\n" );
- $flags = FileStore::DELETE_ORIGINAL;
- } else {
- $flags = 0;
+ protected function getDiffLink() {
+ if ( $this->isDeleted() && !$this->canViewContent() ) {
+ return wfMsgHtml( 'diff' );
}
- $transaction->add( $store->export( $key, $destPath, $flags ) );
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'diff' => 'prev',
+ 'timestamp' => $this->revision->getTimestamp()
+ ) );
+ }
+}
- wfDebug( __METHOD__.": set db items, applying file transactions\n" );
- $transaction->commit();
- FileStore::unlock();
+/**
+ * List for oldimage table items
+ */
+class RevDel_FileList extends RevDel_List {
+ var $type = 'oldimage';
+ var $idField = 'oi_archive_name';
+ var $dateField = 'oi_timestamp';
+ var $authorIdField = 'oi_user';
+ var $authorNameField = 'oi_user_text';
+ var $storeBatch, $deleteBatch, $cleanupBatch;
+
+ public function doQuery( $db ) {
+ $archiveName = array();
+ foreach( $this->ids as $timestamp ) {
+ $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
+ }
+ return $db->select( 'oldimage', '*',
+ array(
+ 'oi_name' => $this->title->getDBkey(),
+ 'oi_archive_name' => $archiveNames
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'oi_timestamp DESC' )
+ );
+ }
- $m = explode('!',$oimage->archive_name,2);
- $timestamp = $m[0];
+ public function newItem( $row ) {
+ return new RevDel_FileItem( $this, $row );
+ }
- return $timestamp;
+ public function clearFileOps() {
+ $this->deleteBatch = array();
+ $this->storeBatch = array();
+ $this->cleanupBatch = array();
}
- /**
- * Update the revision's rev_deleted field
- * @param Revision $rev
- * @param int $bitfield new rev_deleted bitfield value
- */
- function updateRevision( $rev, $bitfield ) {
- $this->dbw->update( 'revision',
- array( 'rev_deleted' => $bitfield ),
- array( 'rev_id' => $rev->getId(),
- 'rev_page' => $rev->getPage() ),
- __METHOD__ );
+ public function doPreCommitUpdates() {
+ $status = Status::newGood();
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ if ( $this->storeBatch ) {
+ $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
+ }
+ if ( !$status->isOK() ) {
+ return $status;
+ }
+ if ( $this->deleteBatch ) {
+ $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
+ }
+ if ( !$status->isOK() ) {
+ // Running cleanupDeletedBatch() after a failed storeBatch() with the DB already
+ // modified (but destined for rollback) causes data loss
+ return $status;
+ }
+ if ( $this->cleanupBatch ) {
+ $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
+ }
+ return $status;
}
- /**
- * Update the revision's rev_deleted field
- * @param Revision $rev
- * @param Title $title
- * @param int $bitfield new rev_deleted bitfield value
- */
- function updateArchive( $rev, $title, $bitfield ) {
- $this->dbw->update( 'archive',
- array( 'ar_deleted' => $bitfield ),
- array( 'ar_namespace' => $title->getNamespace(),
- 'ar_title' => $title->getDBKey(),
- 'ar_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ),
- 'ar_rev_id' => $rev->getId() ),
- __METHOD__ );
+ public function doPostCommitUpdates() {
+ $file = wfLocalFile( $this->title );
+ $file->purgeCache();
+ $file->purgeDescription();
+ return Status::newGood();
}
- /**
- * Update the images's oi_deleted field
- * @param File $file
- * @param int $bitfield new rev_deleted bitfield value
- */
- function updateOldFiles( $file, $bitfield ) {
- $this->dbw->update( 'oldimage',
- array( 'oi_deleted' => $bitfield ),
- array( 'oi_name' => $file->getName(),
- 'oi_timestamp' => $this->dbw->timestamp( $file->getTimestamp() ) ),
- __METHOD__ );
+ public function getSuppressBit() {
+ return File::DELETED_RESTRICTED;
}
+}
- /**
- * Update the images's fa_deleted field
- * @param ArchivedFile $file
- * @param int $bitfield new rev_deleted bitfield value
- */
- function updateArchFiles( $file, $bitfield ) {
- $this->dbw->update( 'filearchive',
- array( 'fa_deleted' => $bitfield ),
- array( 'fa_id' => $file->getId() ),
- __METHOD__ );
+/**
+ * Item class for an oldimage table row
+ */
+class RevDel_FileItem extends RevDel_Item {
+ var $file;
+
+ public function __construct( $list, $row ) {
+ parent::__construct( $list, $row );
+ $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
}
- /**
- * Update the logging log_deleted field
- * @param Row $row
- * @param int $bitfield new rev_deleted bitfield value
- */
- function updateLogs( $row, $bitfield ) {
- $this->dbw->update( 'logging',
- array( 'log_deleted' => $bitfield ),
- array( 'log_id' => $row->log_id ),
- __METHOD__ );
+ public function getId() {
+ $parts = explode( '!', $this->row->oi_archive_name );
+ return $parts[0];
}
- /**
- * Update the revision's recentchanges record if fields have been hidden
- * @param Revision $rev
- * @param int $bitfield new rev_deleted bitfield value
- */
- function updateRecentChangesEdits( $rev, $bitfield ) {
- $this->dbw->update( 'recentchanges',
- array( 'rc_deleted' => $bitfield,
- 'rc_patrolled' => 1 ),
- array( 'rc_this_oldid' => $rev->getId(),
- 'rc_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ) ),
- __METHOD__ );
+ public function canView() {
+ return $this->file->userCan( File::DELETED_RESTRICTED );
+ }
+
+ public function canViewContent() {
+ return $this->file->userCan( File::DELETED_FILE );
}
- /**
- * Update the revision's recentchanges record if fields have been hidden
- * @param Row $row
- * @param int $bitfield new rev_deleted bitfield value
- */
- function updateRecentChangesLog( $row, $bitfield ) {
- $this->dbw->update( 'recentchanges',
- array( 'rc_deleted' => $bitfield,
- 'rc_patrolled' => 1 ),
- array( 'rc_logid' => $row->log_id,
- 'rc_timestamp' => $row->log_timestamp ),
- __METHOD__ );
+ public function getBits() {
+ return $this->file->getVisibility();
+ }
+
+ public function setBits( $bits ) {
+ # Queue the file op
+ # FIXME: move to LocalFile.php
+ if ( $this->isDeleted() ) {
+ if ( $bits & File::DELETED_FILE ) {
+ # Still deleted
+ } else {
+ # Newly undeleted
+ $key = $this->file->getStorageKey();
+ $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->storeBatch[] = array(
+ $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
+ 'public',
+ $this->file->getRel()
+ );
+ $this->list->cleanupBatch[] = $key;
+ }
+ } elseif ( $bits & File::DELETED_FILE ) {
+ # Newly deleted
+ $key = $this->file->getStorageKey();
+ $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
+ $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
+ }
+
+ # Do the database operations
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'oldimage',
+ array( 'oi_deleted' => $bits ),
+ array(
+ 'oi_name' => $this->row->oi_name,
+ 'oi_timestamp' => $this->row->oi_timestamp,
+ 'oi_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+ return (bool)$dbw->affectedRows();
+ }
+
+ public function isDeleted() {
+ return $this->file->isDeleted( File::DELETED_FILE );
}
/**
- * Touch the page's cache invalidation timestamp; this forces cached
- * history views to refresh, so any newly hidden or shown fields will
- * update properly.
- * @param Title $title
+ * Get the link to the file.
+ * Overridden by RevDel_ArchivedFileItem.
*/
- function updatePage( $title ) {
- $title->invalidateCache();
- $title->purgeSquid();
- $title->touchLinks();
- // Extensions that require referencing previous revisions may need this
- wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$title ) );
+ protected function getLink() {
+ global $wgLang, $wgUser;
+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
+ if ( $this->isDeleted() ) {
+ # Hidden files...
+ if ( !$this->canViewContent() ) {
+ $link = $date;
+ } else {
+ $link = $this->special->skin->link(
+ $this->special->getTitle(),
+ $date, array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $this->file->getArchiveName(),
+ 'token' => $wgUser->editToken( $this->file->getArchiveName() )
+ )
+ );
+ }
+ return '<span class="history-deleted">' . $link . '</span>';
+ } else {
+ # Regular files...
+ $url = $this->file->getUrl();
+ return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
+ }
}
-
/**
- * Checks for a change in the bitfield for a certain option and updates the
- * provided array accordingly.
- *
- * @param String $desc Description to add to the array if the option was
- * enabled / disabled.
- * @param int $field The bitmask describing the single option.
- * @param int $diff The xor of the old and new bitfields.
- * @param array $arr The array to update.
+ * Generate a user tool link cluster if the current user is allowed to view it
+ * @return string HTML
*/
- function checkItem ( $desc, $field, $diff, $new, &$arr ) {
- if ( $diff & $field ) {
- $arr [ ( $new & $field ) ? 0 : 1 ][] = $desc;
+ protected function getUserTools() {
+ if( $this->file->userCan( Revision::DELETED_USER ) ) {
+ $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
+ $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
+ } else {
+ $link = wfMsgHtml( 'rev-deleted-user' );
}
+ if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
+ return '<span class="history-deleted">' . $link . '</span>';
+ }
+ return $link;
}
/**
- * Gets an array describing the changes made to the visibilit of the revision.
- * If the resulting array is $arr, then $arr[0] will contain an array of strings
- * describing the items that were hidden, $arr[2] will contain an array of strings
- * describing the items that were unhidden, and $arr[3] will contain an array with
- * a single string, which can be one of "applied restrictions to sysops",
- * "removed restrictions from sysops", or null.
+ * Wrap and format the file's comment block, if the current
+ * user is allowed to view it.
*
- * @param int $n The new bitfield.
- * @param int $o The old bitfield.
- * @return An array as described above.
+ * @return string HTML
*/
- function getChanges ( $n, $o ) {
- $diff = $n ^ $o;
- $ret = array ( 0 => array(), 1 => array(), 2 => array() );
+ protected function getComment() {
+ if( $this->file->userCan( File::DELETED_COMMENT ) ) {
+ $block = $this->special->skin->commentBlock( $this->file->description );
+ } else {
+ $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
+ }
+ if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
+ return "<span class=\"history-deleted\">$block</span>";
+ }
+ return $block;
+ }
- $this->checkItem ( wfMsgForContent ( 'revdelete-content' ),
- Revision::DELETED_TEXT, $diff, $n, $ret );
- $this->checkItem ( wfMsgForContent ( 'revdelete-summary' ),
- Revision::DELETED_COMMENT, $diff, $n, $ret );
- $this->checkItem ( wfMsgForContent ( 'revdelete-uname' ),
- Revision::DELETED_USER, $diff, $n, $ret );
+ public function getHTML() {
+ global $wgLang;
+ $data =
+ wfMsg(
+ 'widthheight',
+ $wgLang->formatNum( $this->file->getWidth() ),
+ $wgLang->formatNum( $this->file->getHeight() )
+ ) .
+ ' (' .
+ wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
+ ')';
+ $pageLink = $this->getLink();
+
+ return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
+ $data . ' ' . $this->getComment(). '</li>';
+ }
+}
- // Restriction application to sysops
- if ( $diff & Revision::DELETED_RESTRICTED ) {
- if ( $n & Revision::DELETED_RESTRICTED )
- $ret[2][] = wfMsgForContent ( 'revdelete-restricted' );
- else
- $ret[2][] = wfMsgForContent ( 'revdelete-unrestricted' );
- }
+/**
+ * List for filearchive table items
+ */
+class RevDel_ArchivedFileList extends RevDel_FileList {
+ var $type = 'filearchive';
+ var $idField = 'fa_id';
+ var $dateField = 'fa_timestamp';
+ var $authorIdField = 'fa_user';
+ var $authorNameField = 'fa_user_text';
+
+ public function doQuery( $db ) {
+ $ids = array_map( 'intval', $this->ids );
+ return $db->select( 'filearchive', '*',
+ array(
+ 'fa_name' => $this->title->getDBkey(),
+ 'fa_id' => $ids
+ ),
+ __METHOD__,
+ array( 'ORDER BY' => 'fa_id DESC' )
+ );
+ }
- return $ret;
+ public function newItem( $row ) {
+ return new RevDel_ArchivedFileItem( $this, $row );
}
+}
- /**
- * Gets a log message to describe the given revision visibility change. This
- * message will be of the form "[hid {content, edit summary, username}];
- * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
- *
- * @param int $count The number of effected revisions.
- * @param int $nbitfield The new bitfield for the revision.
- * @param int $obitfield The old bitfield for the revision.
- * @param string $comment The comment associated with the change.
- * @param bool $isForLog
- */
- function getLogMessage ( $count, $nbitfield, $obitfield, $comment, $isForLog = false ) {
- global $wgContLang;
+/**
+ * Item class for a filearchive table row
+ */
+class RevDel_ArchivedFileItem extends RevDel_FileItem {
+ public function __construct( $list, $row ) {
+ RevDel_Item::__construct( $list, $row );
+ $this->file = ArchivedFile::newFromRow( $row );
+ }
- $s = '';
- $changes = $this->getChanges( $nbitfield, $obitfield );
+ public function getId() {
+ return $this->row->fa_id;
+ }
+
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'filearchive',
+ array( 'fa_deleted' => $bits ),
+ array(
+ 'fa_id' => $this->row->fa_id,
+ 'fa_deleted' => $this->getBits(),
+ ),
+ __METHOD__
+ );
+ return (bool)$dbw->affectedRows();
+ }
- if ( count ( $changes[0] ) ) {
- $s .= wfMsgForContent ( 'revdelete-hid', implode ( ', ', $changes[0] ) );
+ protected function getLink() {
+ global $wgLang, $wgUser;
+ $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
+ $undelete = SpecialPage::getTitleFor( 'Undelete' );
+ $key = $this->file->getKey();
+ # Hidden files...
+ if( !$this->canViewContent() ) {
+ $link = $date;
+ } else {
+ $link = $this->special->skin->link( $undelete, $date, array(),
+ array(
+ 'target' => $this->list->title->getPrefixedText(),
+ 'file' => $key,
+ 'token' => $wgUser->editToken( $key )
+ )
+ );
+ }
+ if( $this->isDeleted() ) {
+ $link = '<span class="history-deleted">' . $link . '</span>';
}
+ return $link;
+ }
+}
- if ( count ( $changes[1] ) ) {
- if ($s) $s .= '; ';
+/**
+ * List for logging table items
+ */
+class RevDel_LogList extends RevDel_List {
+ var $type = 'logging';
+ var $idField = 'log_id';
+ var $dateField = 'log_timestamp';
+ var $authorIdField = 'log_user';
+ var $authorNameField = 'log_user_text';
+
+ public function doQuery( $db ) {
+ global $wgMessageCache;
+ $wgMessageCache->loadAllMessages();
+ $ids = array_map( 'intval', $this->ids );
+ return $db->select( 'logging', '*',
+ array( 'log_id' => $ids ),
+ __METHOD__,
+ array( 'ORDER BY' => 'log_id DESC' )
+ );
+ }
- $s .= wfMsgForContent ( 'revdelete-unhid', implode ( ', ', $changes[1] ) );
- }
+ public function newItem( $row ) {
+ return new RevDel_LogItem( $this, $row );
+ }
- if ( count ( $changes[2] )) {
- if ($s)
- $s .= ' (' . $changes[2][0] . ')';
- else
- $s = $changes[2][0];
- }
+ public function getSuppressBit() {
+ return Revision::DELETED_RESTRICTED;
+ }
- $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
- $ret = wfMsgExt ( $msg, array( 'parsemag', 'content' ),
- $s, $wgContLang->formatNum( $count ) );
+ public function getLogAction() {
+ return 'event';
+ }
- if ( $comment )
- $ret .= ": $comment";
+ public function getLogParams( $params ) {
+ return array(
+ implode( ',', $params['ids'] ),
+ "ofield={$params['oldBits']}",
+ "nfield={$params['newBits']}"
+ );
+ }
+}
- return $ret;
+/**
+ * Item class for a logging table row
+ */
+class RevDel_LogItem extends RevDel_Item {
+ public function canView() {
+ return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
+ }
+
+ public function canViewContent() {
+ return true; // none
+ }
+ public function getBits() {
+ return $this->row->log_deleted;
}
- /**
- * Record a log entry on the action
- * @param Title $title, page where item was removed from
- * @param int $count the number of revisions altered for this page
- * @param int $nbitfield the new _deleted value
- * @param int $obitfield the old _deleted value
- * @param string $comment
- * @param Title $target, the relevant page
- * @param string $param, URL param
- * @param Array $items
- */
- function updateLog( $title, $count, $nbitfield, $obitfield, $comment, $target, $param, $items = array() ) {
- // Put things hidden from sysops in the oversight log
- $logtype = ( ($nbitfield | $obitfield) & Revision::DELETED_RESTRICTED ) ? 'suppress' : 'delete';
- $log = new LogPage( $logtype );
+ public function setBits( $bits ) {
+ $dbw = wfGetDB( DB_MASTER );
+ $dbw->update( 'recentchanges',
+ array(
+ 'rc_deleted' => $bits,
+ 'rc_patrolled' => 1
+ ),
+ array(
+ 'rc_logid' => $this->row->log_id,
+ 'rc_timestamp' => $this->row->log_timestamp // index
+ ),
+ __METHOD__
+ );
+ $dbw->update( 'logging',
+ array( 'log_deleted' => $bits ),
+ array(
+ 'log_id' => $this->row->log_id,
+ 'log_deleted' => $this->getBits()
+ ),
+ __METHOD__
+ );
+ return (bool)$dbw->affectedRows();
+ }
+
+ public function getHTML() {
+ global $wgLang;
- $reason = $this->getLogMessage ( $count, $nbitfield, $obitfield, $comment, $param == 'logid' );
+ $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
+ $paramArray = LogPage::extractParams( $this->row->log_params );
+ $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
- if( $param == 'logid' ) {
- $params = array( implode( ',', $items) );
- $log->addEntry( 'event', $title, $reason, $params );
+ // Log link for this page
+ $loglink = $this->special->skin->link(
+ SpecialPage::getTitleFor( 'Log' ),
+ wfMsgHtml( 'log' ),
+ array(),
+ array( 'page' => $title->getPrefixedText() )
+ );
+ // Action text
+ if( !$this->canView() ) {
+ $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
} else {
- // Add params for effected page and ids
- $params = array( $param, implode( ',', $items) );
- $log->addEntry( 'revision', $title, $reason, $params );
+ $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
+ $this->special->skin, $paramArray, true, true );
+ if( $this->row->log_deleted & LogPage::DELETED_ACTION )
+ $action = '<span class="history-deleted">' . $action . '</span>';
}
+ // User links
+ $userLink = $this->special->skin->userLink( $this->row->log_user,
+ User::WhoIs( $this->row->log_user ) );
+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
+ $userLink = '<span class="history-deleted">' . $userLink . '</span>';
+ }
+ // Comment
+ $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
+ if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
+ $comment = '<span class="history-deleted">' . $comment . '</span>';
+ }
+ return "<li>($loglink) $date $userLink $action $comment</li>";
}
}
diff --git a/includes/specials/SpecialSearch.php b/includes/specials/SpecialSearch.php
index cb783819..da054e02 100644
--- a/includes/specials/SpecialSearch.php
+++ b/includes/specials/SpecialSearch.php
@@ -29,16 +29,15 @@
* @param $par String: (default '')
*/
function wfSpecialSearch( $par = '' ) {
- global $wgRequest, $wgUser, $wgUseOldSearchUI;
+ global $wgRequest, $wgUser;
// 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 ) );
- $class = $wgUseOldSearchUI ? 'SpecialSearchOld' : 'SpecialSearch';
- $searchPage = new $class( $wgRequest, $wgUser );
- if( $wgRequest->getVal( 'fulltext' )
- || !is_null( $wgRequest->getVal( 'offset' ))
+ $searchPage = new SpecialSearch( $wgRequest, $wgUser );
+ if( $wgRequest->getVal( 'fulltext' )
+ || !is_null( $wgRequest->getVal( 'offset' ))
|| !is_null( $wgRequest->getVal( 'searchx' )) )
{
$searchPage->showResults( $search );
@@ -74,7 +73,7 @@ class SpecialSearch {
$this->active = 'advanced';
$this->sk = $user->getSkin();
$this->didYouMeanHtml = ''; # html of did you mean... link
- $this->fulltext = $request->getVal('fulltext');
+ $this->fulltext = $request->getVal('fulltext');
}
/**
@@ -103,7 +102,7 @@ class SpecialSearch {
wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
# If the feature is enabled, go straight to the edit page
if( $wgGoToEdit ) {
- $wgOut->redirect( $t->getFullURL( 'action=edit' ) );
+ $wgOut->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) );
return;
}
}
@@ -114,11 +113,11 @@ class SpecialSearch {
* @param string $term
*/
public function showResults( $term ) {
- global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang;
+ global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang, $wgScript;
wfProfileIn( __METHOD__ );
-
+
$sk = $wgUser->getSkin();
-
+
$this->searchEngine = SearchEngine::create();
$search =& $this->searchEngine;
$search->setLimitOffset( $this->limit, $this->offset );
@@ -126,9 +125,9 @@ class SpecialSearch {
$search->showRedirects = $this->searchRedirects;
$search->prefix = $this->mPrefix;
$term = $search->transformSearchTerm($term);
-
+
$this->setupPage( $term );
-
+
if( $wgDisableTextSearch ) {
global $wgSearchForwardUrl;
if( $wgSearchForwardUrl ) {
@@ -152,10 +151,10 @@ class SpecialSearch {
wfProfileOut( __METHOD__ );
return;
}
-
+
$t = Title::newFromText( $term );
-
- // fetch search results
+
+ // fetch search results
$rewritten = $search->replacePrefixes($term);
$titleMatches = $search->searchTitle( $rewritten );
@@ -165,95 +164,116 @@ class SpecialSearch {
// did you mean... suggestions
if( $textMatches && $textMatches->hasSuggestion() ) {
$st = SpecialPage::getTitleFor( 'Search' );
+
# mirror Go/Search behaviour of original request ..
$didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
- if($this->fulltext != NULL)
- $didYouMeanParams['fulltext'] = $this->fulltext;
- $stParams = wfArrayToCGI(
+
+ if($this->fulltext != null)
+ $didYouMeanParams['fulltext'] = $this->fulltext;
+
+ $stParams = array_merge(
$didYouMeanParams,
$this->powerSearchOptions()
);
- $suggestLink = $sk->makeKnownLinkObj( $st,
- $textMatches->getSuggestionSnippet(),
- $stParams );
+
+ $suggestionSnippet = $textMatches->getSuggestionSnippet();
+
+ if( $suggestionSnippet == '' )
+ $suggestionSnippet = null;
+
+ $suggestLink = $sk->linkKnown(
+ $st,
+ $suggestionSnippet,
+ array(),
+ $stParams
+ );
$this->didYouMeanHtml = '<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>';
}
-
- // start rendering the page
- $wgOut->addHtml(
- Xml::openElement( 'table', array( 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
+ // start rendering the page
+ $wgOut->addHtml(
+ Xml::openElement(
+ 'form',
+ array(
+ 'id' => ( $this->searchAdvanced ? 'powersearch' : 'search' ),
+ 'method' => 'get',
+ 'action' => $wgScript
+ )
+ )
+ );
+ $wgOut->addHtml(
+ Xml::openElement( 'table', array( 'id'=>'mw-search-top-table', 'border'=>0, 'cellpadding'=>0, 'cellspacing'=>0 ) ) .
Xml::openElement( 'tr' ) .
Xml::openElement( 'td' ) . "\n" .
- ( $this->searchAdvanced ? $this->powerSearchBox( $term ) : $this->shortDialog( $term ) ) .
+ $this->shortDialog( $term ) .
Xml::closeElement('td') .
Xml::closeElement('tr') .
Xml::closeElement('table')
);
-
+
// Sometimes the search engine knows there are too many hits
if( $titleMatches instanceof SearchResultTooMany ) {
$wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" );
wfProfileOut( __METHOD__ );
return;
}
-
+
$filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':';
- if( '' === trim( $term ) || $filePrefix === trim( $term ) ) {
- $wgOut->addHTML( $this->searchAdvanced ? $this->powerSearchFocus() : $this->searchFocus() );
+ if( trim( $term ) === '' || $filePrefix === trim( $term ) ) {
+ $wgOut->addHTML( $this->searchFocus() );
+ $wgOut->addHTML( $this->formHeader($term, 0, 0));
+ if( $this->searchAdvanced ) {
+ $wgOut->addHTML( $this->powerSearchBox( $term ) );
+ }
+ $wgOut->addHTML( '</form>' );
// Empty query -- straight view of search form
wfProfileOut( __METHOD__ );
return;
}
- // show direct page/create link
- if( !is_null($t) ) {
- if( !$t->exists() ) {
- $wgOut->addWikiMsg( 'searchmenu-new', wfEscapeWikiText( $t->getPrefixedText() ) );
- } else {
- $wgOut->addWikiMsg( 'searchmenu-exists', wfEscapeWikiText( $t->getPrefixedText() ) );
- }
- }
-
// Get number of results
- $titleMatchesSQL = $titleMatches ? $titleMatches->numRows() : 0;
- $textMatchesSQL = $textMatches ? $textMatches->numRows() : 0;
+ $titleMatchesNum = $titleMatches ? $titleMatches->numRows() : 0;
+ $textMatchesNum = $textMatches ? $textMatches->numRows() : 0;
// Total initial query matches (possible false positives)
- $numSQL = $titleMatchesSQL + $textMatchesSQL;
+ $num = $titleMatchesNum + $textMatchesNum;
+
// Get total actual results (after second filtering, if any)
$numTitleMatches = $titleMatches && !is_null( $titleMatches->getTotalHits() ) ?
- $titleMatches->getTotalHits() : $titleMatchesSQL;
+ $titleMatches->getTotalHits() : $titleMatchesNum;
$numTextMatches = $textMatches && !is_null( $textMatches->getTotalHits() ) ?
- $textMatches->getTotalHits() : $textMatchesSQL;
- $totalRes = $numTitleMatches + $numTextMatches;
-
+ $textMatches->getTotalHits() : $textMatchesNum;
+
+ // get total number of results if backend can calculate it
+ $totalRes = 0;
+ if($titleMatches && !is_null( $titleMatches->getTotalHits() ) )
+ $totalRes += $titleMatches->getTotalHits();
+ if($textMatches && !is_null( $textMatches->getTotalHits() ))
+ $totalRes += $textMatches->getTotalHits();
+
// show number of results and current offset
- if( $numSQL > 0 ) {
- if( $numSQL > 0 ) {
- $top = wfMsgExt('showingresultstotal', array( 'parseinline' ),
- $this->offset+1, $this->offset+$numSQL, $totalRes, $numSQL );
- } elseif( $numSQL >= $this->limit ) {
- $top = wfShowingResults( $this->offset, $this->limit );
- } else {
- $top = wfShowingResultsNum( $this->offset, $this->limit, $numSQL );
- }
- $wgOut->addHTML( "<p class='mw-search-numberresults'>{$top}</p>\n" );
+ $wgOut->addHTML( $this->formHeader($term, $num, $totalRes));
+ if( $this->searchAdvanced ) {
+ $wgOut->addHTML( $this->powerSearchBox( $term ) );
}
+
+ $wgOut->addHtml( Xml::closeElement( 'form' ) );
+ $wgOut->addHtml( "<div class='searchresults'>" );
// prev/next links
- if( $numSQL || $this->offset ) {
+ if( $num || $this->offset ) {
+ // Show the create link ahead
+ $this->showCreateLink( $t );
$prevnext = wfViewPrevNext( $this->offset, $this->limit,
SpecialPage::getTitleFor( 'Search' ),
wfArrayToCGI( $this->powerSearchOptions(), array( 'search' => $term ) ),
- max( $titleMatchesSQL, $textMatchesSQL ) < $this->limit
+ max( $titleMatchesNum, $textMatchesNum ) < $this->limit
);
- $wgOut->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
+ //$wgOut->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
} else {
wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
- }
+ }
- $wgOut->addHtml( "<div class='searchresults'>" );
if( $titleMatches ) {
if( $numTitleMatches > 0 ) {
$wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' );
@@ -265,10 +285,10 @@ class SpecialSearch {
// output appropriate heading
if( $numTextMatches > 0 && $numTitleMatches > 0 ) {
// if no title matches the heading is redundant
- $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' );
+ $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' );
} elseif( $totalRes == 0 ) {
# Don't show the 'no text matches' if we received title matches
- $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' );
+ # $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' );
}
// show interwiki results if any
if( $textMatches->hasInterwikiResults() ) {
@@ -281,20 +301,41 @@ class SpecialSearch {
$textMatches->free();
}
- if( $totalRes === 0 ) {
- $wgOut->addWikiMsg( 'search-nonefound' );
+ if( $num === 0 ) {
+ $wgOut->addWikiMsg( 'search-nonefound', wfEscapeWikiText( $term ) );
+ $this->showCreateLink( $t );
}
$wgOut->addHtml( "</div>" );
- if( $totalRes === 0 ) {
- $wgOut->addHTML( $this->searchAdvanced ? $this->powerSearchFocus() : $this->searchFocus() );
+ if( $num === 0 ) {
+ $wgOut->addHTML( $this->searchFocus() );
}
- if( $numSQL || $this->offset ) {
+ if( $num || $this->offset ) {
$wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
}
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';
+ }
+ }
+ if( $messageName ) {
+ $wgOut->addWikiMsg( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) );
+ } else {
+ // preserve the paragraph for margins etc...
+ $wgOut->addHtml( '<p></p>' );
+ }
+ }
+
/**
*
*/
@@ -304,24 +345,25 @@ class SpecialSearch {
$nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
if( $this->searchAdvanced )
$this->active = 'advanced';
- else if( $this->namespaces === NS_FILE || $this->startsWithImage( $term ) )
- $this->active = 'images';
- elseif( $this->namespaces === $nsAllSet )
- $this->active = 'all';
- elseif( $this->namespaces === SearchEngine::defaultNamespaces() )
- $this->active = 'default';
- elseif( $this->namespaces === SearchEngine::projectNamespaces() )
- $this->active = 'project';
- else
- $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 ) ) {
$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->addScriptFile( 'search.js' );
}
/**
@@ -358,8 +400,8 @@ class SpecialSearch {
}
/**
- * Show whole set of results
- *
+ * Show whole set of results
+ *
* @param SearchResultSet $matches
*/
protected function showMatches( &$matches ) {
@@ -403,7 +445,20 @@ class SpecialSearch {
$sk = $wgUser->getSkin();
$t = $result->getTitle();
- $link = $this->sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms));
+ $titleSnippet = $result->getTitleSnippet($terms);
+
+ if( $titleSnippet == '' )
+ $titleSnippet = null;
+
+ $link_t = clone $t;
+
+ wfRunHooks( 'ShowSearchHitTitle',
+ array( &$link_t, &$titleSnippet, $result, $terms, $this ) );
+
+ $link = $this->sk->linkKnown(
+ $link_t,
+ $titleSnippet
+ );
//If page content is not readable, just return the title.
//This is not quite safe, but better than showing excerpts from non-readable pages
@@ -427,19 +482,42 @@ class SpecialSearch {
$sectionTitle = $result->getSectionTitle();
$sectionText = $result->getSectionSnippet($terms);
$redirect = '';
- if( !is_null($redirectTitle) )
- $redirect = "<span class='searchalttitle'>"
- .wfMsg('search-redirect',$this->sk->makeKnownLinkObj( $redirectTitle, $redirectText))
- ."</span>";
+
+ if( !is_null($redirectTitle) ) {
+ if( $redirectText == '' )
+ $redirectText = null;
+
+ $redirect = "<span class='searchalttitle'>" .
+ wfMsg(
+ 'search-redirect',
+ $this->sk->linkKnown(
+ $redirectTitle,
+ $redirectText
+ )
+ ) .
+ "</span>";
+ }
+
$section = '';
- if( !is_null($sectionTitle) )
- $section = "<span class='searchalttitle'>"
- .wfMsg('search-section', $this->sk->makeKnownLinkObj( $sectionTitle, $sectionText))
- ."</span>";
+
+
+ if( !is_null($sectionTitle) ) {
+ if( $sectionText == '' )
+ $sectionText = null;
+
+ $section = "<span class='searchalttitle'>" .
+ wfMsg(
+ 'search-section', $this->sk->linkKnown(
+ $sectionTitle,
+ $sectionText
+ )
+ ) .
+ "</span>";
+ }
// format text extract
$extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>";
-
+
// format score
if( is_null( $result->getScore() ) ) {
// Search engine doesn't report scoring info
@@ -454,20 +532,32 @@ class SpecialSearch {
$byteSize = $result->getByteSize();
$wordCount = $result->getWordCount();
$timestamp = $result->getTimestamp();
- $size = wfMsgExt( 'search-result-size', array( 'parsemag', 'escape' ),
- $this->sk->formatSize( $byteSize ), $wordCount );
+ $size = wfMsgExt(
+ 'search-result-size',
+ array( 'parsemag', 'escape' ),
+ $this->sk->formatSize( $byteSize ),
+ $wgLang->formatNum( $wordCount )
+ );
$date = $wgLang->timeanddate( $timestamp );
// link to related articles if supported
$related = '';
if( $result->hasRelated() ) {
$st = SpecialPage::getTitleFor( 'Search' );
- $stParams = wfArrayToCGI( $this->powerSearchOptions(),
- array('search' => wfMsgForContent('searchrelated').':'.$t->getPrefixedText(),
- 'fulltext' => wfMsg('search') ));
-
- $related = ' -- ' . $sk->makeKnownLinkObj( $st,
- wfMsg('search-relatedarticle'), $stParams );
+ $stParams = array_merge(
+ $this->powerSearchOptions(),
+ array(
+ 'search' => wfMsgForContent( 'searchrelated' ) . ':' . $t->getPrefixedText(),
+ 'fulltext' => wfMsg( 'search' )
+ )
+ );
+
+ $related = ' -- ' . $sk->linkKnown(
+ $st,
+ wfMsg('search-relatedarticle'),
+ array(),
+ $stParams
+ );
}
// Include a thumbnail for media files...
@@ -508,7 +598,7 @@ class SpecialSearch {
/**
* Show results from other wikis
- *
+ *
* @param SearchResultSet $matches
*/
protected function showInterwiki( &$matches, $query ) {
@@ -517,7 +607,7 @@ class SpecialSearch {
$terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
$out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".
- wfMsg('search-interwiki-caption')."</div>\n";
+ wfMsg('search-interwiki-caption')."</div>\n";
$off = $this->offset + 1;
$out .= "<ul class='mw-search-iwresults'>\n";
@@ -527,15 +617,15 @@ class SpecialSearch {
foreach($customLines as $line) {
$parts = explode(":",$line,2);
if(count($parts) == 2) // validate line
- $customCaptions[$parts[0]] = $parts[1];
+ $customCaptions[$parts[0]] = $parts[1];
}
-
+
$prev = null;
while( $result = $matches->next() ) {
$out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
$prev = $result->getInterwikiPrefix();
}
- // FIXME: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
+ // TODO: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
$out .= "</ul></div>\n";
// convert the whole thing to desired language variant
@@ -543,63 +633,88 @@ class SpecialSearch {
wfProfileOut( __METHOD__ );
return $out;
}
-
+
/**
* Show single interwiki link
*
* @param SearchResult $result
* @param string $lastInterwiki
* @param array $terms
- * @param string $query
+ * @param string $query
* @param array $customCaptions iw prefix -> caption
*/
protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
wfProfileIn( __METHOD__ );
global $wgContLang, $wgLang;
-
+
if( $result->isBrokenTitle() ) {
wfProfileOut( __METHOD__ );
return "<!-- Broken link in search result -->\n";
}
-
+
$t = $result->getTitle();
- $link = $this->sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms));
+ $titleSnippet = $result->getTitleSnippet($terms);
+
+ if( $titleSnippet == '' )
+ $titleSnippet = null;
+
+ $link = $this->sk->linkKnown(
+ $t,
+ $titleSnippet
+ );
// format redirect if any
$redirectTitle = $result->getRedirectTitle();
$redirectText = $result->getRedirectSnippet($terms);
$redirect = '';
- if( !is_null($redirectTitle) )
- $redirect = "<span class='searchalttitle'>"
- .wfMsg('search-redirect',$this->sk->makeKnownLinkObj( $redirectTitle, $redirectText))
- ."</span>";
+ if( !is_null($redirectTitle) ) {
+ if( $redirectText == '' )
+ $redirectText = null;
+
+ $redirect = "<span class='searchalttitle'>" .
+ wfMsg(
+ 'search-redirect',
+ $this->sk->linkKnown(
+ $redirectTitle,
+ $redirectText
+ )
+ ) .
+ "</span>";
+ }
$out = "";
- // display project name
+ // display project name
if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()) {
if( key_exists($t->getInterwiki(),$customCaptions) )
// captions from 'search-interwiki-custom'
$caption = $customCaptions[$t->getInterwiki()];
else{
- // default is to show the hostname of the other wiki which might suck
+ // 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());
- $caption = wfMsg('search-interwiki-default', $parsed['host']);
- }
+ $caption = wfMsg('search-interwiki-default', $parsed['host']);
+ }
// "more results" link (special page stuff could be localized, but we might not know target lang)
- $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");
- $searchLink = $this->sk->makeKnownLinkObj( $searchTitle, wfMsg('search-interwiki-more'),
- wfArrayToCGI(array('search' => $query, 'fulltext' => 'Search')));
+ $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");
+ $searchLink = $this->sk->linkKnown(
+ $searchTitle,
+ wfMsg('search-interwiki-more'),
+ array(),
+ array(
+ 'search' => $query,
+ 'fulltext' => 'Search'
+ )
+ );
$out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'>
{$searchLink}</span>{$caption}</div>\n<ul>";
}
- $out .= "<li>{$link} {$redirect}</li>\n";
+ $out .= "<li>{$link} {$redirect}</li>\n";
wfProfileOut( __METHOD__ );
return $out;
}
-
+
/**
* Generates the power search box at bottom of [[Special:Search]]
@@ -607,172 +722,241 @@ class SpecialSearch {
* @return $out string: HTML form
*/
protected function powerSearchBox( $term ) {
- global $wgScript;
-
- $namespaces = SearchEngine::searchableNamespaces();
-
- $tables = $this->namespaceTables( $namespaces );
-
- $redirect = Xml::check( 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' ) );
- $redirectLabel = Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' );
- $searchField = Xml::inputLabel( wfMsg('powersearch-field'), 'search', 'powerSearchText', 50, $term,
- array( 'type' => 'text') );
- $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' )) . "\n";
- $searchTitle = SpecialPage::getTitleFor( 'Search' );
+ global $wgScript, $wgContLang;
- $redirectText = '';
- // show redirects check only if backend supports it
- if( $this->searchEngine->acceptListRedirects() ) {
- $redirectText = "<p>". $redirect . " " . $redirectLabel ."</p>";
+ // Groups namespaces into rows according to subject
+ $rows = array();
+ foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) {
+ $subject = MWNamespace::getSubject( $namespace );
+ if( !array_key_exists( $subject, $rows ) ) {
+ $rows[$subject] = "";
+ }
+ $name = str_replace( '_', ' ', $name );
+ if( $name == '' ) {
+ $name = wfMsg( 'blanknamespace' );
+ }
+ $rows[$subject] .=
+ Xml::openElement(
+ 'td', array( 'style' => 'white-space: nowrap' )
+ ) .
+ Xml::checkLabel(
+ $name,
+ "ns{$namespace}",
+ "mw-search-ns{$namespace}",
+ in_array( $namespace, $this->namespaces )
+ ) .
+ Xml::closeElement( 'td' );
}
+ $rows = array_values( $rows );
+ $numRows = count( $rows );
- $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) .
- Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n" .
- "<p>" .
- wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) .
- "</p>\n" .
- '<input type="hidden" name="advanced" value="'.$this->searchAdvanced."\"/>\n".
- $tables .
- "<hr style=\"clear: both;\" />\n".
- $redirectText ."\n".
- "<div style=\"padding-top:2px;padding-bottom:2px;\">".
- $searchField .
- "&nbsp;" .
- Xml::hidden( 'fulltext', 'Advanced search' ) . "\n" .
- $searchButton .
- "</div>".
- "</form>";
- $t = Title::newFromText( $term );
- /* if( $t != null && count($this->namespaces) === 1 ) {
- $out .= wfMsgExt( 'searchmenu-prefix', array('parseinline'), $term );
- } */
- return Xml::openElement( 'fieldset', array('id' => 'mw-searchoptions','style' => 'margin:0em;') ) .
+ // Lays out namespaces in multiple floating two-column tables so they'll
+ // be arranged nicely while still accommodating different screen widths
+ $namespaceTables = '';
+ for( $i = 0; $i < $numRows; $i += 4 ) {
+ $namespaceTables .= Xml::openElement(
+ 'table',
+ array( 'cellpadding' => 0, 'cellspacing' => 0, 'border' => 0 )
+ );
+ for( $j = $i; $j < $i + 4 && $j < $numRows; $j++ ) {
+ $namespaceTables .= Xml::tags( 'tr', null, $rows[$j] );
+ }
+ $namespaceTables .= Xml::closeElement( 'table' );
+ }
+ // 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' );
+ }
+ // Return final output
+ return
+ Xml::openElement(
+ 'fieldset',
+ array( 'id' => 'mw-searchoptions', 'style' => 'margin:0em;' )
+ ) .
Xml::element( 'legend', null, wfMsg('powersearch-legend') ) .
- $this->formHeader($term) . $out . $this->didYouMeanHtml .
+ Xml::tags( 'h4', null, wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) ) .
+ Xml::tags(
+ 'div',
+ array( 'id' => 'mw-search-togglebox' ),
+ Xml::label( wfMsg( 'powersearch-togglelabel' ), 'mw-search-togglelabel' ) .
+ Xml::element(
+ 'input',
+ array(
+ 'type'=>'button',
+ 'id' => 'mw-search-toggleall',
+ 'onclick' => 'mwToggleSearchCheckboxes("all");',
+ 'value' => wfMsg( 'powersearch-toggleall' )
+ )
+ ) .
+ Xml::element(
+ 'input',
+ 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 .
+ Xml::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) .
+ Xml::hidden( 'advanced', $this->searchAdvanced ) .
+ Xml::hidden( 'fulltext', 'Advanced search' ) .
Xml::closeElement( 'fieldset' );
}
-
+
protected function searchFocus() {
- global $wgJsMimeType;
- return "<script type=\"$wgJsMimeType\">" .
+ $id = $this->searchAdvanced ? 'powerSearchText' : 'searchText';
+ return Html::inlineScript(
"hookEvent(\"load\", function() {" .
- "document.getElementById('searchText').focus();" .
- "});" .
- "</script>";
+ "document.getElementById('$id').focus();" .
+ "});" );
}
+
+ protected function getSearchProfiles() {
+ // Builds list of Search Types (profiles)
+ $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
+
+ $profiles = array(
+ 'default' => array(
+ 'message' => 'searchprofile-articles',
+ 'tooltip' => 'searchprofile-articles-tooltip',
+ 'namespaces' => SearchEngine::defaultNamespaces(),
+ 'namespace-messages' => SearchEngine::namespacesAsText(
+ SearchEngine::defaultNamespaces()
+ ),
+ ),
+ 'images' => array(
+ 'message' => 'searchprofile-images',
+ 'tooltip' => 'searchprofile-images-tooltip',
+ 'namespaces' => array( NS_FILE ),
+ ),
+ 'help' => array(
+ 'message' => 'searchprofile-project',
+ 'tooltip' => 'searchprofile-project-tooltip',
+ 'namespaces' => SearchEngine::helpNamespaces(),
+ 'namespace-messages' => SearchEngine::namespacesAsText(
+ SearchEngine::helpNamespaces()
+ ),
+ ),
+ 'all' => array(
+ 'message' => 'searchprofile-everything',
+ 'tooltip' => 'searchprofile-everything-tooltip',
+ 'namespaces' => $nsAllSet,
+ ),
+ 'advanced' => array(
+ 'message' => 'searchprofile-advanced',
+ 'tooltip' => 'searchprofile-advanced-tooltip',
+ 'namespaces' => $this->namespaces,
+ 'parameters' => array( 'advanced' => 1 ),
+ )
+ );
+
+ wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) );
- protected function powerSearchFocus() {
- global $wgJsMimeType;
- return "<script type=\"$wgJsMimeType\">" .
- "hookEvent(\"load\", function() {" .
- "document.getElementById('powerSearchText').focus();" .
- "});" .
- "</script>";
+ foreach( $profiles as $key => &$data ) {
+ sort($data['namespaces']);
+ }
+
+ return $profiles;
}
- protected function formHeader( $term ) {
- global $wgContLang, $wgCanonicalNamespaceNames, $wgLang;
-
- $sep = '&nbsp;&nbsp;&nbsp;';
- $out = Xml::openElement('div', array( 'style' => 'padding-bottom:0.5em;' ) );
-
+ protected function formHeader( $term, $resultsShown, $totalNum ) {
+ global $wgContLang, $wgLang;
+
+ $out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) );
+
$bareterm = $term;
- if( $this->startsWithImage( $term ) )
- $bareterm = substr( $term, strpos( $term, ':' ) + 1 ); // delete all/image prefix
-
- $nsAllSet = array_keys( SearchEngine::searchableNamespaces() );
-
- // search profiles headers
- $m = wfMsg( 'searchprofile-articles' );
- $tt = wfMsg( 'searchprofile-articles-tooltip',
- $wgLang->commaList( SearchEngine::namespacesAsText( SearchEngine::defaultNamespaces() ) ) );
- if( $this->active == 'default' ) {
- $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );
- } else {
- $out .= $this->makeSearchLink( $bareterm, SearchEngine::defaultNamespaces(), $m, $tt );
+ if( $this->startsWithImage( $term ) ) {
+ // Deletes prefixes
+ $bareterm = substr( $term, strpos( $term, ':' ) + 1 );
}
- $out .= $sep;
+
- $m = wfMsg( 'searchprofile-images' );
- $tt = wfMsg( 'searchprofile-images-tooltip' );
- if( $this->active == 'images' ) {
- $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );
- } else {
- $imageTextForm = $wgContLang->getFormattedNsText(NS_FILE).':'.$bareterm;
- $out .= $this->makeSearchLink( $imageTextForm, array( NS_FILE ) , $m, $tt );
+ $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 ) {
+ $tooltipParam = isset( $profile['namespace-messages'] ) ?
+ $wgLang->commaList( $profile['namespace-messages'] ) : null;
+ $out .= Xml::tags(
+ 'li',
+ array(
+ 'class' => $this->active == $id ? 'current' : 'normal'
+ ),
+ $this->makeSearchLink(
+ $bareterm,
+ $profile['namespaces'],
+ wfMsg( $profile['message'] ),
+ wfMsg( $profile['tooltip'], $tooltipParam ),
+ isset( $profile['parameters'] ) ? $profile['parameters'] : array()
+ )
+ );
}
- $out .= $sep;
+ $out .= Xml::closeElement( 'ul' );
+ $out .= Xml::closeElement('div') ;
- $m = wfMsg( 'searchprofile-project' );
- $tt = wfMsg( 'searchprofile-project-tooltip',
- $wgLang->commaList( SearchEngine::namespacesAsText( SearchEngine::projectNamespaces() ) ) );
- if( $this->active == 'project' ) {
- $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );
- } else {
- $out .= $this->makeSearchLink( $bareterm, SearchEngine::projectNamespaces(), $m, $tt );
- }
- $out .= $sep;
-
- $m = wfMsg( 'searchprofile-everything' );
- $tt = wfMsg( 'searchprofile-everything-tooltip' );
- if( $this->active == 'all' ) {
- $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );
- } else {
- $out .= $this->makeSearchLink( $bareterm, $nsAllSet, $m, $tt );
+ // Results-info
+ if ( $resultsShown > 0 ) {
+ if ( $totalNum > 0 ){
+ $top = wfMsgExt( 'showingresultsheader', array( 'parseinline' ),
+ $wgLang->formatNum( $this->offset + 1 ),
+ $wgLang->formatNum( $this->offset + $resultsShown ),
+ $wgLang->formatNum( $totalNum ),
+ wfEscapeWikiText( $term ),
+ $wgLang->formatNum( $resultsShown )
+ );
+ } elseif ( $resultsShown >= $this->limit ) {
+ $top = wfShowingResults( $this->offset, $this->limit );
+ } else {
+ $top = wfShowingResultsNum( $this->offset, $this->limit, $resultsShown );
+ }
+ $out .= Xml::tags( 'div', array( 'class' => 'results-info' ),
+ Xml::tags( 'ul', null, Xml::tags( 'li', null, $top ) )
+ );
}
- $out .= $sep;
- $m = wfMsg( 'searchprofile-advanced' );
- $tt = wfMsg( 'searchprofile-advanced-tooltip' );
- if( $this->active == 'advanced' ) {
- $out .= Xml::element( 'strong', array( 'title'=>$tt ), $m );
- } else {
- $out .= $this->makeSearchLink( $bareterm, $this->namespaces, $m, $tt, array( 'advanced' => '1' ) );
+ $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 .= Xml::hidden( "ns{$ns}", '1' );
+ }
}
- $out .= Xml::closeElement('div') ;
return $out;
}
-
+
protected function shortDialog( $term ) {
- global $wgScript;
$searchTitle = SpecialPage::getTitleFor( 'Search' );
$searchable = SearchEngine::searchableNamespaces();
- $out = Xml::openElement( 'form', array( 'id' => 'search', 'method' => 'get', 'action' => $wgScript ) );
- $out .= Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
- // show namespaces only for advanced search
- if( $this->active == 'advanced' ) {
- $active = array();
- foreach( $this->namespaces as $ns ) {
- $active[$ns] = $searchable[$ns];
- }
- $out .= wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) . "<br/>\n";
- $out .= $this->namespaceTables( $active, 1 )."<br/>\n";
- // Still keep namespace settings otherwise, but don't show them
- } else {
- foreach( $this->namespaces as $ns ) {
- $out .= Xml::hidden( "ns{$ns}", '1' );
- }
- }
+ $out = Html::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
// Keep redirect setting
- $out .= Xml::hidden( "redirs", (int)$this->searchRedirects );
+ $out .= Html::hidden( "redirs", (int)$this->searchRedirects ) . "\n";
// Term box
- $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . "\n";
- $out .= Xml::hidden( 'fulltext', 'Search' );
- $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) );
- $out .= ' (' . wfMsgExt('searchmenu-help',array('parseinline') ) . ')';
- $out .= Xml::closeElement( 'form' );
- // Add prefix link for single-namespace searches
- $t = Title::newFromText( $term );
- /*if( $t != null && count($this->namespaces) === 1 ) {
- $out .= wfMsgExt( 'searchmenu-prefix', array('parseinline'), $term );
- }*/
- return Xml::openElement( 'fieldset', array('id' => 'mw-searchoptions','style' => 'margin:0em;') ) .
- Xml::element( 'legend', null, wfMsg('searchmenu-legend') ) .
- $this->formHeader($term) . $out . $this->didYouMeanHtml .
- Xml::closeElement( 'fieldset' );
+ $out .= Html::input( 'search', $term, 'search', array(
+ 'id' => $this->searchAdvanced ? 'powerSearchText' : 'searchText',
+ 'size' => '50',
+ 'autofocus'
+ ) ) . "\n";
+ $out .= Html::hidden( 'fulltext', 'Search' ) . "\n";
+ $out .= Xml::submitButton( wfMsg( 'searchbutton' ) ) . "\n";
+ return $out . $this->didYouMeanHtml;
}
-
+
/** Make a search link with some target namespaces */
protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) {
$opt = $params;
@@ -781,18 +965,30 @@ class SpecialSearch {
}
$opt['redirs'] = $this->searchRedirects ? 1 : 0;
- $st = SpecialPage::getTitleFor( 'Search' );
- $stParams = wfArrayToCGI( array( 'search' => $term, 'fulltext' => wfMsg( 'search' ) ), $opt );
+ $st = SpecialPage::getTitleFor( 'Search' );
+ $stParams = array_merge(
+ array(
+ 'search' => $term,
+ 'fulltext' => wfMsg( 'search' )
+ ),
+ $opt
+ );
- return Xml::element( 'a',
- array( 'href'=> $st->getLocalURL( $stParams ), 'title' => $tooltip ),
- $label );
+ return Xml::element(
+ 'a',
+ array(
+ 'href' => $st->getLocalURL( $stParams ),
+ 'title' => $tooltip,
+ 'onmousedown' => 'mwSearchHeaderClick(this);',
+ 'onkeydown' => 'mwSearchHeaderClick(this);'),
+ $label
+ );
}
-
+
/** Check if query starts with image: prefix */
protected function startsWithImage( $term ) {
global $wgContLang;
-
+
$p = explode( ':', $term );
if( count( $p ) > 1 ) {
return $wgContLang->getNsIndex( $p[0] ) == NS_FILE;
@@ -800,689 +996,16 @@ class SpecialSearch {
return false;
}
- protected function namespaceTables( $namespaces, $rowsPerTable = 3 ) {
- global $wgContLang;
- // Group namespaces into rows according to subject.
- // Try not to make too many assumptions about namespace numbering.
- $rows = array();
- $tables = "";
- foreach( $namespaces as $ns => $name ) {
- $subj = MWNamespace::getSubject( $ns );
- if( !array_key_exists( $subj, $rows ) ) {
- $rows[$subj] = "";
- }
- $name = str_replace( '_', ' ', $name );
- if( '' == $name ) {
- $name = wfMsg( 'blanknamespace' );
- }
- $rows[$subj] .= Xml::openElement( 'td', array( 'style' => 'white-space: nowrap' ) ) .
- Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $this->namespaces ) ) .
- Xml::closeElement( 'td' ) . "\n";
- }
- $rows = array_values( $rows );
- $numRows = count( $rows );
- // Lay out namespaces in multiple floating two-column tables so they'll
- // be arranged nicely while still accommodating different screen widths
- // Float to the right on RTL wikis
- $tableStyle = $wgContLang->isRTL() ?
- 'float: right; margin: 0 0 0em 1em' : 'float: left; margin: 0 1em 0em 0';
- // Build the final HTML table...
- for( $i = 0; $i < $numRows; $i += $rowsPerTable ) {
- $tables .= Xml::openElement( 'table', array( 'style' => $tableStyle ) );
- for( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) {
- $tables .= "<tr>\n" . $rows[$j] . "</tr>";
- }
- $tables .= Xml::closeElement( 'table' ) . "\n";
- }
- return $tables;
- }
-}
-
-/**
- * implements Special:Search - Run text & title search and display the output
- * @ingroup SpecialPage
- */
-class SpecialSearchOld {
-
- /**
- * Set up basic search parameters from the request and user settings.
- * Typically you'll pass $wgRequest and $wgUser.
- *
- * @param WebRequest $request
- * @param User $user
- * @public
- */
- function __construct( &$request, &$user ) {
- list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' );
- $this->mPrefix = $request->getVal('prefix', '');
- $this->namespaces = $this->powerSearch( $request );
- if( empty( $this->namespaces ) ) {
- $this->namespaces = SearchEngine::userNamespaces( $user );
- }
-
- $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false;
- $this->fulltext = $request->getVal('fulltext');
- }
-
- /**
- * If an exact title match can be found, jump straight ahead to it.
- * @param string $term
- * @public
- */
- function goResult( $term ) {
- global $wgOut;
- global $wgGoToEdit;
-
- $this->setupPage( $term );
-
- # Try to go to page as entered.
- $t = Title::newFromText( $term );
-
- # If the string cannot be used to create a title
- if( is_null( $t ) ){
- return $this->showResults( $term );
- }
-
- # If there's an exact or very near match, jump right there.
- $t = SearchEngine::getNearMatch( $term );
- if( !is_null( $t ) ) {
- $wgOut->redirect( $t->getFullURL() );
- return;
- }
-
- # No match, generate an edit URL
- $t = Title::newFromText( $term );
- if( ! is_null( $t ) ) {
- wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) );
- # If the feature is enabled, go straight to the edit page
- if ( $wgGoToEdit ) {
- $wgOut->redirect( $t->getFullURL( 'action=edit' ) );
- return;
- }
- }
-
- $extra = $wgOut->parse( '=='.wfMsgNoTrans( 'notitlematches' )."==\n" );
- if( $t->quickUserCan( 'create' ) && $t->quickUserCan( 'edit' ) ) {
- $extra .= wfMsgExt( 'noexactmatch', 'parse', wfEscapeWikiText( $term ) );
- } else {
- $extra .= wfMsgExt( 'noexactmatch-nocreate', 'parse', wfEscapeWikiText( $term ) );
- }
-
- $this->showResults( $term, $extra );
- }
-
- /**
- * @param string $term
- * @param string $extra Extra HTML to add after "did you mean"
- */
- public function showResults( $term, $extra = '' ) {
- wfProfileIn( __METHOD__ );
- global $wgOut, $wgUser;
- $sk = $wgUser->getSkin();
-
- $search = SearchEngine::create();
- $search->setLimitOffset( $this->limit, $this->offset );
- $search->setNamespaces( $this->namespaces );
- $search->showRedirects = $this->searchRedirects;
- $search->prefix = $this->mPrefix;
- $term = $search->transformSearchTerm($term);
-
- $this->setupPage( $term );
-
- $rewritten = $search->replacePrefixes($term);
- $titleMatches = $search->searchTitle( $rewritten );
- $textMatches = $search->searchText( $rewritten );
-
- // did you mean... suggestions
- if($textMatches && $textMatches->hasSuggestion()){
- $st = SpecialPage::getTitleFor( 'Search' );
-
- # mirror Go/Search behaviour of original request
- $didYouMeanParams = array( 'search' => $textMatches->getSuggestionQuery() );
- if($this->fulltext != NULL)
- $didYouMeanParams['fulltext'] = $this->fulltext;
- $stParams = wfArrayToCGI(
- $didYouMeanParams,
- $this->powerSearchOptions()
- );
-
- $suggestLink = $sk->makeKnownLinkObj( $st,
- $textMatches->getSuggestionSnippet(),
- $stParams );
-
- $wgOut->addHTML('<div class="searchdidyoumean">'.wfMsg('search-suggest',$suggestLink).'</div>');
- }
-
- $wgOut->addHTML( $extra );
-
- $wgOut->wrapWikiMsg( "<div class='mw-searchresult'>\n$1</div>", 'searchresulttext' );
-
- if( '' === trim( $term ) ) {
- // Empty query -- straight view of search form
- $wgOut->setSubtitle( '' );
- $wgOut->addHTML( $this->powerSearchBox( $term ) );
- $wgOut->addHTML( $this->powerSearchFocus() );
- wfProfileOut( __METHOD__ );
- return;
- }
-
- global $wgDisableTextSearch;
- if ( $wgDisableTextSearch ) {
- global $wgSearchForwardUrl;
- if( $wgSearchForwardUrl ) {
- $url = str_replace( '$1', urlencode( $term ), $wgSearchForwardUrl );
- $wgOut->redirect( $url );
- 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( wfMsg( 'searchbutton' ) )
- ) .
- Xml::closeElement( 'fieldset' )
- );
- wfProfileOut( __METHOD__ );
- return;
- }
-
- $wgOut->addHTML( $this->shortDialog( $term ) );
-
- // Sometimes the search engine knows there are too many hits
- if ($titleMatches instanceof SearchResultTooMany) {
- $wgOut->addWikiText( '==' . wfMsg( 'toomanymatches' ) . "==\n" );
- $wgOut->addHTML( $this->powerSearchBox( $term ) );
- $wgOut->addHTML( $this->powerSearchFocus() );
- wfProfileOut( __METHOD__ );
- return;
- }
-
- // show number of results
- $num = ( $titleMatches ? $titleMatches->numRows() : 0 )
- + ( $textMatches ? $textMatches->numRows() : 0);
- $totalNum = 0;
- if($titleMatches && !is_null($titleMatches->getTotalHits()))
- $totalNum += $titleMatches->getTotalHits();
- if($textMatches && !is_null($textMatches->getTotalHits()))
- $totalNum += $textMatches->getTotalHits();
- if ( $num > 0 ) {
- if ( $totalNum > 0 ){
- $top = wfMsgExt('showingresultstotal', array( 'parseinline' ),
- $this->offset+1, $this->offset+$num, $totalNum, $num );
- } elseif ( $num >= $this->limit ) {
- $top = wfShowingResults( $this->offset, $this->limit );
- } else {
- $top = wfShowingResultsNum( $this->offset, $this->limit, $num );
- }
- $wgOut->addHTML( "<p class='mw-search-numberresults'>{$top}</p>\n" );
- }
-
- // prev/next links
- if( $num || $this->offset ) {
- $prevnext = wfViewPrevNext( $this->offset, $this->limit,
- SpecialPage::getTitleFor( 'Search' ),
- wfArrayToCGI(
- $this->powerSearchOptions(),
- array( 'search' => $term ) ),
- ($num < $this->limit) );
- $wgOut->addHTML( "<p class='mw-search-pager-top'>{$prevnext}</p>\n" );
- wfRunHooks( 'SpecialSearchResults', array( $term, &$titleMatches, &$textMatches ) );
- } else {
- wfRunHooks( 'SpecialSearchNoResults', array( $term ) );
- }
-
- if( $titleMatches ) {
- if( $titleMatches->numRows() ) {
- $wgOut->wrapWikiMsg( "==$1==\n", 'titlematches' );
- $wgOut->addHTML( $this->showMatches( $titleMatches ) );
- }
- $titleMatches->free();
- }
-
- if( $textMatches ) {
- // output appropriate heading
- if( $textMatches->numRows() ) {
- if($titleMatches)
- $wgOut->wrapWikiMsg( "==$1==\n", 'textmatches' );
- else // if no title matches the heading is redundant
- $wgOut->addHTML("<hr/>");
- } elseif( $num == 0 ) {
- # Don't show the 'no text matches' if we received title matches
- $wgOut->wrapWikiMsg( "==$1==\n", 'notextmatches' );
- }
- // show interwiki results if any
- if( $textMatches->hasInterwikiResults() )
- $wgOut->addHTML( $this->showInterwiki( $textMatches->getInterwikiResults(), $term ));
- // show results
- if( $textMatches->numRows() )
- $wgOut->addHTML( $this->showMatches( $textMatches ) );
-
- $textMatches->free();
- }
-
- if ( $num == 0 ) {
- $wgOut->addWikiMsg( 'nonefound' );
- }
- if( $num || $this->offset ) {
- $wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" );
- }
- $wgOut->addHTML( $this->powerSearchBox( $term ) );
- wfProfileOut( __METHOD__ );
- }
-
- #------------------------------------------------------------------
- # Private methods below this line
-
- /**
- *
- */
- function setupPage( $term ) {
- global $wgOut;
- if( !empty( $term ) ){
- $wgOut->setPageTitle( wfMsg( 'searchresults') );
- $wgOut->setHTMLTitle( wfMsg( 'pagetitle', wfMsg( 'searchresults-title', $term) ) );
- }
- $subtitlemsg = ( Title::newFromText( $term ) ? 'searchsubtitle' : 'searchsubtitleinvalid' );
- $wgOut->setSubtitle( $wgOut->parse( wfMsg( $subtitlemsg, wfEscapeWikiText($term) ) ) );
- $wgOut->setArticleRelated( false );
- $wgOut->setRobotPolicy( 'noindex,nofollow' );
- }
-
- /**
- * Extract "power search" namespace settings from the request object,
- * returning a list of index numbers to search.
- *
- * @param WebRequest $request
- * @return array
- * @private
- */
- function powerSearch( &$request ) {
- $arr = array();
- foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
- if( $request->getCheck( 'ns' . $ns ) ) {
- $arr[] = $ns;
- }
- }
- return $arr;
- }
-
- /**
- * Reconstruct the 'power search' options for links
- * @return array
- * @private
- */
- function powerSearchOptions() {
- $opt = array();
- foreach( $this->namespaces as $n ) {
- $opt['ns' . $n] = 1;
- }
- $opt['redirs'] = $this->searchRedirects ? 1 : 0;
- return $opt;
- }
-
- /**
- * Show whole set of results
- *
- * @param SearchResultSet $matches
- */
- function showMatches( &$matches ) {
- wfProfileIn( __METHOD__ );
-
- global $wgContLang;
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
-
- $out = "";
-
- $infoLine = $matches->getInfo();
- if( !is_null($infoLine) )
- $out .= "\n<!-- {$infoLine} -->\n";
-
-
- $off = $this->offset + 1;
- $out .= "<ul class='mw-search-results'>\n";
-
- while( $result = $matches->next() ) {
- $out .= $this->showHit( $result, $terms );
- }
- $out .= "</ul>\n";
-
- // convert the whole thing to desired language variant
- global $wgContLang;
- $out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
- return $out;
- }
+ /** Check if query starts with all: prefix */
+ protected function startsWithAll( $term ) {
- /**
- * Format a single hit result
- * @param SearchResult $result
- * @param array $terms terms to highlight
- */
- function showHit( $result, $terms ) {
- wfProfileIn( __METHOD__ );
- global $wgUser, $wgContLang, $wgLang;
+ $allkeyword = wfMsgForContent('searchall');
- if( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
- }
-
- $t = $result->getTitle();
- $sk = $wgUser->getSkin();
-
- $link = $sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms));
-
- //If page content is not readable, just return the title.
- //This is not quite safe, but better than showing excerpts from non-readable pages
- //Note that hiding the entry entirely would screw up paging.
- if (!$t->userCanRead()) {
- wfProfileOut( __METHOD__ );
- return "<li>{$link}</li>\n";
- }
-
- // If the page doesn't *exist*... our search index is out of date.
- // The least confusing at this point is to drop the result.
- // You may get less results, but... oh well. :P
- if( $result->isMissingRevision() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- missing page " .
- htmlspecialchars( $t->getPrefixedText() ) . "-->\n";
- }
-
- // format redirects / relevant sections
- $redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet($terms);
- $sectionTitle = $result->getSectionTitle();
- $sectionText = $result->getSectionSnippet($terms);
- $redirect = '';
- if( !is_null($redirectTitle) )
- $redirect = "<span class='searchalttitle'>"
- .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText))
- ."</span>";
- $section = '';
- if( !is_null($sectionTitle) )
- $section = "<span class='searchalttitle'>"
- .wfMsg('search-section', $sk->makeKnownLinkObj( $sectionTitle, $sectionText))
- ."</span>";
-
- // format text extract
- $extract = "<div class='searchresult'>".$result->getTextSnippet($terms)."</div>";
-
- // format score
- if( is_null( $result->getScore() ) ) {
- // Search engine doesn't report scoring info
- $score = '';
- } else {
- $percent = sprintf( '%2.1f', $result->getScore() * 100 );
- $score = wfMsg( 'search-result-score', $wgLang->formatNum( $percent ) )
- . ' - ';
- }
-
- // format description
- $byteSize = $result->getByteSize();
- $wordCount = $result->getWordCount();
- $timestamp = $result->getTimestamp();
- $size = wfMsgExt( 'search-result-size', array( 'parsemag', 'escape' ),
- $sk->formatSize( $byteSize ),
- $wordCount );
- $date = $wgLang->timeanddate( $timestamp );
-
- // link to related articles if supported
- $related = '';
- if( $result->hasRelated() ){
- $st = SpecialPage::getTitleFor( 'Search' );
- $stParams = wfArrayToCGI( $this->powerSearchOptions(),
- array('search' => wfMsgForContent('searchrelated').':'.$t->getPrefixedText(),
- 'fulltext' => wfMsg('search') ));
-
- $related = ' -- ' . $sk->makeKnownLinkObj( $st,
- wfMsg('search-relatedarticle'), $stParams );
- }
-
- // Include a thumbnail for media files...
- if( $t->getNamespace() == NS_FILE ) {
- $img = wfFindFile( $t );
- if( $img ) {
- $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) );
- if( $thumb ) {
- $desc = $img->getShortDesc();
- wfProfileOut( __METHOD__ );
- // Ugly table. :D
- // Float doesn't seem to interact well with the bullets.
- // Table messes up vertical alignment of the bullet, but I'm
- // not sure what more I can do about that. :(
- return "<li>" .
- '<table class="searchResultImage">' .
- '<tr>' .
- '<td width="120" align="center">' .
- $thumb->toHtml( array( 'desc-link' => true ) ) .
- '</td>' .
- '<td valign="top">' .
- $link .
- $extract .
- "<div class='mw-search-result-data'>{$score}{$desc} - {$date}{$related}</div>" .
- '</td>' .
- '</tr>' .
- '</table>' .
- "</li>\n";
- }
- }
- }
-
- wfProfileOut( __METHOD__ );
- return "<li>{$link} {$redirect} {$section} {$extract}\n" .
- "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" .
- "</li>\n";
-
- }
-
- /**
- * Show results from other wikis
- *
- * @param SearchResultSet $matches
- */
- function showInterwiki( &$matches, $query ) {
- wfProfileIn( __METHOD__ );
-
- global $wgContLang;
- $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
-
- $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>".wfMsg('search-interwiki-caption')."</div>\n";
- $off = $this->offset + 1;
- $out .= "<ul start='{$off}' class='mw-search-iwresults'>\n";
-
- // work out custom project captions
- $customCaptions = array();
- $customLines = explode("\n",wfMsg('search-interwiki-custom')); // format per line <iwprefix>:<caption>
- foreach($customLines as $line){
- $parts = explode(":",$line,2);
- if(count($parts) == 2) // validate line
- $customCaptions[$parts[0]] = $parts[1];
- }
-
-
- $prev = null;
- while( $result = $matches->next() ) {
- $out .= $this->showInterwikiHit( $result, $prev, $terms, $query, $customCaptions );
- $prev = $result->getInterwikiPrefix();
- }
- // FIXME: should support paging in a non-confusing way (not sure how though, maybe via ajax)..
- $out .= "</ul></div>\n";
-
- // convert the whole thing to desired language variant
- global $wgContLang;
- $out = $wgContLang->convert( $out );
- wfProfileOut( __METHOD__ );
- return $out;
- }
-
- /**
- * Show single interwiki link
- *
- * @param SearchResult $result
- * @param string $lastInterwiki
- * @param array $terms
- * @param string $query
- * @param array $customCaptions iw prefix -> caption
- */
- function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) {
- wfProfileIn( __METHOD__ );
- global $wgUser, $wgContLang, $wgLang;
-
- if( $result->isBrokenTitle() ) {
- wfProfileOut( __METHOD__ );
- return "<!-- Broken link in search result -->\n";
- }
-
- $t = $result->getTitle();
- $sk = $wgUser->getSkin();
-
- $link = $sk->makeKnownLinkObj( $t, $result->getTitleSnippet($terms));
-
- // format redirect if any
- $redirectTitle = $result->getRedirectTitle();
- $redirectText = $result->getRedirectSnippet($terms);
- $redirect = '';
- if( !is_null($redirectTitle) )
- $redirect = "<span class='searchalttitle'>"
- .wfMsg('search-redirect',$sk->makeKnownLinkObj( $redirectTitle, $redirectText))
- ."</span>";
-
- $out = "";
- // display project name
- if(is_null($lastInterwiki) || $lastInterwiki != $t->getInterwiki()){
- if( key_exists($t->getInterwiki(),$customCaptions) )
- // captions from 'search-interwiki-custom'
- $caption = $customCaptions[$t->getInterwiki()];
- 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());
- $caption = wfMsg('search-interwiki-default', $parsed['host']);
- }
- // "more results" link (special page stuff could be localized, but we might not know target lang)
- $searchTitle = Title::newFromText($t->getInterwiki().":Special:Search");
- $searchLink = $sk->makeKnownLinkObj( $searchTitle, wfMsg('search-interwiki-more'),
- wfArrayToCGI(array('search' => $query, 'fulltext' => 'Search')));
- $out .= "</ul><div class='mw-search-interwiki-project'><span class='mw-search-interwiki-more'>{$searchLink}</span>{$caption}</div>\n<ul>";
- }
-
- $out .= "<li>{$link} {$redirect}</li>\n";
- wfProfileOut( __METHOD__ );
- return $out;
- }
-
-
- /**
- * Generates the power search box at bottom of [[Special:Search]]
- * @param $term string: search term
- * @return $out string: HTML form
- */
- function powerSearchBox( $term ) {
- global $wgScript, $wgContLang;
-
- $namespaces = SearchEngine::searchableNamespaces();
-
- // group namespaces into rows according to subject; try not to make too
- // many assumptions about namespace numbering
- $rows = array();
- foreach( $namespaces as $ns => $name ) {
- $subj = MWNamespace::getSubject( $ns );
- if( !array_key_exists( $subj, $rows ) ) {
- $rows[$subj] = "";
- }
- $name = str_replace( '_', ' ', $name );
- if( '' == $name ) {
- $name = wfMsg( 'blanknamespace' );
- }
- $rows[$subj] .= Xml::openElement( 'td', array( 'style' => 'white-space: nowrap' ) ) .
- Xml::checkLabel( $name, "ns{$ns}", "mw-search-ns{$ns}", in_array( $ns, $this->namespaces ) ) .
- Xml::closeElement( 'td' ) . "\n";
- }
- $rows = array_values( $rows );
- $numRows = count( $rows );
-
- // lay out namespaces in multiple floating two-column tables so they'll
- // be arranged nicely while still accommodating different screen widths
- $rowsPerTable = 3; // seems to look nice
-
- // float to the right on RTL wikis
- $tableStyle = ( $wgContLang->isRTL() ?
- 'float: right; margin: 0 0 1em 1em' :
- 'float: left; margin: 0 1em 1em 0' );
-
- $tables = "";
- for( $i = 0; $i < $numRows; $i += $rowsPerTable ) {
- $tables .= Xml::openElement( 'table', array( 'style' => $tableStyle ) );
- for( $j = $i; $j < $i + $rowsPerTable && $j < $numRows; $j++ ) {
- $tables .= "<tr>\n" . $rows[$j] . "</tr>";
- }
- $tables .= Xml::closeElement( 'table' ) . "\n";
- }
-
- $redirect = Xml::check( 'redirs', $this->searchRedirects, array( 'value' => '1', 'id' => 'redirs' ) );
- $redirectLabel = Xml::label( wfMsg( 'powersearch-redir' ), 'redirs' );
- $searchField = Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'powerSearchText' ) );
- $searchButton = Xml::submitButton( wfMsg( 'powersearch' ), array( 'name' => 'fulltext' ) ) . "\n";
- $searchTitle = SpecialPage::getTitleFor( 'Search' );
- $searchHiddens = Xml::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n";
- $searchHiddens .= Xml::hidden( 'fulltext', 'Advanced search' ) . "\n";
-
- $out = Xml::openElement( 'form', array( 'id' => 'powersearch', 'method' => 'get', 'action' => $wgScript ) ) .
- Xml::fieldset( wfMsg( 'powersearch-legend' ),
- "<p>" .
- wfMsgExt( 'powersearch-ns', array( 'parseinline' ) ) .
- "</p>\n" .
- $tables .
- "<hr style=\"clear: both\" />\n" .
- "<p>" .
- $redirect . " " . $redirectLabel .
- "</p>\n" .
- wfMsgExt( 'powersearch-field', array( 'parseinline' ) ) .
- "&nbsp;" .
- $searchField .
- "&nbsp;" .
- $searchHiddens .
- $searchButton ) .
- "</form>";
-
- return $out;
- }
-
- function powerSearchFocus() {
- global $wgJsMimeType;
- return "<script type=\"$wgJsMimeType\">" .
- "hookEvent(\"load\", function(){" .
- "document.getElementById('powerSearchText').focus();" .
- "});" .
- "</script>";
- }
-
- function shortDialog($term) {
- global $wgScript;
-
- $out = Xml::openElement( 'form', array(
- 'id' => 'search',
- 'method' => 'get',
- 'action' => $wgScript
- ));
- $searchTitle = SpecialPage::getTitleFor( 'Search' );
- $out .= Xml::input( 'search', 50, $term, array( 'type' => 'text', 'id' => 'searchText' ) ) . ' ';
- foreach( SearchEngine::searchableNamespaces() as $ns => $name ) {
- if( in_array( $ns, $this->namespaces ) ) {
- $out .= Xml::hidden( "ns{$ns}", '1' );
- }
+ $p = explode( ':', $term );
+ if( count( $p ) > 1 ) {
+ return $p[0] == $allkeyword;
}
- $out .= Xml::hidden( 'title', $searchTitle->getPrefixedText() );
- $out .= Xml::hidden( 'fulltext', 'Search' );
- $out .= Xml::submitButton( wfMsg( 'searchbutton' ), array( 'name' => 'fulltext' ) );
- $out .= Xml::closeElement( 'form' );
-
- return $out;
+ return false;
}
}
+
diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php
index 2e7d24a5..c41b15c5 100644
--- a/includes/specials/SpecialShortpages.php
+++ b/includes/specials/SpecialShortpages.php
@@ -74,10 +74,15 @@ class ShortPagesPage extends QueryPage {
if ( !$title ) {
return '<!-- Invalid title ' . htmlspecialchars( "{$result->namespace}:{$result->title}" ). '-->';
}
- $hlink = $skin->makeKnownLinkObj( $title, wfMsgHtml( 'hist' ), 'action=history' );
+ $hlink = $skin->linkKnown(
+ $title,
+ wfMsgHtml( 'hist' ),
+ array(),
+ array( 'action' => 'history' )
+ );
$plink = $this->isCached()
- ? $skin->makeLinkObj( $title )
- : $skin->makeKnownLinkObj( $title );
+ ? $skin->link( $title )
+ : $skin->linkKnown( $title );
$size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->value ) ) );
return $title->exists()
diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php
index 4959f107..84ab689a 100644
--- a/includes/specials/SpecialSpecialpages.php
+++ b/includes/specials/SpecialSpecialpages.php
@@ -55,15 +55,15 @@ function wfSpecialSpecialpages() {
$total = count($sortedPages);
$count = 0;
- $wgOut->wrapWikiMsg( "<h4 class='mw-specialpagesgroup'>$1</h4>\n", "specialpages-group-$group" );
+ $wgOut->wrapWikiMsg( "<h4 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h4>\n", "specialpages-group-$group" );
$wgOut->addHTML( "<table style='width: 100%;' class='mw-specialpages-table'><tr>" );
$wgOut->addHTML( "<td width='30%' valign='top'><ul>\n" );
foreach( $sortedPages as $desc => $specialpage ) {
list( $title, $restricted ) = $specialpage;
- $link = $sk->makeKnownLinkObj( $title , htmlspecialchars( $desc ) );
+ $link = $sk->linkKnown( $title , htmlspecialchars( $desc ) );
if( $restricted ) {
$includesRestrictedPages = true;
- $wgOut->addHTML( "<li class='mw-specialpages-page mw-specialpagerestricted'>{$link}</li>\n" );
+ $wgOut->addHTML( "<li class='mw-specialpages-page mw-specialpagerestricted'><strong>{$link}</strong></li>\n" );
} else {
$wgOut->addHTML( "<li>{$link}</li>\n" );
}
diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php
index 109c5c30..2e785b8b 100644
--- a/includes/specials/SpecialStatistics.php
+++ b/includes/specials/SpecialStatistics.php
@@ -23,7 +23,7 @@ class SpecialStatistics extends SpecialPage {
}
public function execute( $par ) {
- global $wgOut, $wgRequest, $wgMessageCache;
+ global $wgOut, $wgRequest, $wgMessageCache, $wgMemc;
global $wgDisableCounters, $wgMiserMode;
$wgMessageCache->loadAllMessages();
@@ -38,6 +38,7 @@ class SpecialStatistics extends SpecialPage {
$this->activeUsers = SiteStats::activeUsers();
$this->admins = SiteStats::numberingroup('sysop');
$this->numJobs = SiteStats::jobs();
+ $this->hook = '';
# Staticic - views
$viewsStats = '';
@@ -47,8 +48,13 @@ class SpecialStatistics extends SpecialPage {
# Set active user count
if( !$wgMiserMode ) {
- $dbw = wfGetDB( DB_MASTER );
- SiteStatsUpdate::cacheUpdate( $dbw );
+ $key = wfMemcKey( 'sitestats', 'activeusers-updated' );
+ // Re-calculate the count if the last tally is old...
+ if( !$wgMemc->get($key) ) {
+ $dbw = wfGetDB( DB_MASTER );
+ SiteStatsUpdate::cacheUpdate( $dbw );
+ $wgMemc->set( $key, '1', 24*3600 ); // don't update for 1 day
+ }
}
# Do raw output
@@ -56,10 +62,10 @@ class SpecialStatistics extends SpecialPage {
$this->doRawOutput();
}
- $text = Xml::openElement( 'table', array( 'class' => 'mw-statistics-table' ) );
+ $text = Xml::openElement( 'table', array( 'class' => 'wikitable mw-statistics-table' ) );
# Statistic - pages
- $text .= $this->getPageStats();
+ $text .= $this->getPageStats();
# Statistic - edits
$text .= $this->getEditStats();
@@ -75,6 +81,12 @@ class SpecialStatistics extends SpecialPage {
if( !$wgDisableCounters && !$wgMiserMode ) {
$text .= $this->getMostViewedPages();
}
+
+ # Statistic - other
+ $extraStats = array();
+ if( wfRunHooks( 'SpecialStatsAddExtra', array( &$extraStats ) ) ) {
+ $text .= $this->getOtherStats( $extraStats );
+ }
$text .= Xml::closeElement( 'table' );
@@ -149,14 +161,22 @@ class SpecialStatistics extends SpecialPage {
array( 'class' => 'mw-statistics-jobqueue' ) );
}
private function getUserStats() {
- global $wgLang, $wgRCMaxAge;
+ global $wgLang, $wgUser, $wgRCMaxAge;
+ $sk = $wgUser->getSkin();
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 ),
array( 'class' => 'mw-statistics-users' ) ) .
- $this->formatRow( wfMsgExt( 'statistics-users-active', array( 'parseinline' ) ),
+ $this->formatRow( wfMsgExt( 'statistics-users-active', array( 'parseinline' ) ) . ' ' .
+ $sk->link(
+ SpecialPage::getTitleFor( 'Activeusers' ),
+ wfMsgHtml( 'listgrouprights-members' ),
+ array(),
+ array(),
+ 'known'
+ ),
$wgLang->formatNum( $this->activeUsers ),
array( 'class' => 'mw-statistics-users-active' ),
'statistics-users-active-desc',
@@ -184,13 +204,19 @@ class SpecialStatistics extends SpecialPage {
} else {
$grouppageLocalized = $msg;
}
- $grouppage = $sk->makeLink( $grouppageLocalized, htmlspecialchars( $groupnameLocalized ) );
- $grouplink = $sk->link( SpecialPage::getTitleFor( 'Listusers' ),
+ $linkTarget = Title::newFromText( $grouppageLocalized );
+ $grouppage = $sk->link(
+ $linkTarget,
+ htmlspecialchars( $groupnameLocalized )
+ );
+ $grouplink = $sk->link(
+ SpecialPage::getTitleFor( 'Listusers' ),
wfMsgHtml( 'listgrouprights-members' ),
array(),
array( 'group' => $group ),
- 'known' );
- # Add a class when a usergroup contains no members to allow hiding these rows
+ 'known'
+ );
+ # Add a class when a usergroup contains no members to allow hiding these rows
$classZero = '';
$countUsers = SiteStats::numberingroup( $groupname );
if( $countUsers == 0 ) {
@@ -238,7 +264,9 @@ class SpecialStatistics extends SpecialPage {
)
);
if( $res->numRows() > 0 ) {
+ $text .= Xml::openElement( 'tr' );
$text .= Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-mostpopular', array( 'parseinline' ) ) );
+ $text .= Xml::closeElement( 'tr' );
while( $row = $res->fetchObject() ) {
$title = Title::makeTitleSafe( $row->page_namespace, $row->page_title );
if( $title instanceof Title ) {
@@ -252,6 +280,26 @@ class SpecialStatistics extends SpecialPage {
return $text;
}
+ 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 $return;
+ }
+
/**
* Do the action=raw output for this page. Legacy, but we support
* it for backwards compatibility
diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php
index 981eb2ff..57feeae7 100644
--- a/includes/specials/SpecialTags.php
+++ b/includes/specials/SpecialTags.php
@@ -36,7 +36,7 @@ class SpecialTags extends SpecialPage {
$html .= $this->doTagRow( $tag, 0 );
}
- $wgOut->addHTML( Xml::tags( 'table', array( 'class' => 'mw-tags-table' ), $html ) );
+ $wgOut->addHTML( Xml::tags( 'table', array( 'class' => 'wikitable mw-tags-table' ), $html ) );
}
function doTagRow( $tag, $hitcount ) {
@@ -49,21 +49,23 @@ class SpecialTags extends SpecialPage {
if ( in_array( $tag, $doneTags ) ) {
return '';
}
+
+ global $wgLang;
$newRow = '';
$newRow .= Xml::tags( 'td', null, Xml::element( 'tt', null, $tag ) );
$disp = ChangeTags::tagDescription( $tag );
- $disp .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag" ), wfMsg( 'tags-edit' ) ) . ')';
+ $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;
- $desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsg( 'tags-edit' ) ) . ')';
+ $desc .= ' (' . $sk->link( Title::makeTitle( NS_MEDIAWIKI, "Tag-$tag-description" ), wfMsgHtml( 'tags-edit' ) ) . ')';
$newRow .= Xml::tags( 'td', null, $desc );
- $hitcount = wfMsg( 'tags-hitcount', $hitcount );
- $hitcount = $sk->link( SpecialPage::getTitleFor( 'RecentChanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) );
+ $hitcount = wfMsgExt( 'tags-hitcount', array( 'parsemag' ), $wgLang->formatNum( $hitcount ) );
+ $hitcount = $sk->link( SpecialPage::getTitleFor( 'Recentchanges' ), $hitcount, array(), array( 'tagfilter' => $tag ) );
$newRow .= Xml::tags( 'td', null, $hitcount );
$doneTags[] = $tag;
diff --git a/includes/specials/SpecialUncategorizedtemplates.php b/includes/specials/SpecialUncategorizedtemplates.php
index cb2a6d40..7e6fd24b 100644
--- a/includes/specials/SpecialUncategorizedtemplates.php
+++ b/includes/specials/SpecialUncategorizedtemplates.php
@@ -23,8 +23,6 @@ class UncategorizedTemplatesPage extends UncategorizedPagesPage {
/**
* Main execution point
- *
- * @param mixed $par Parameter passed to the page
*/
function wfSpecialUncategorizedtemplates() {
list( $limit, $offset ) = wfCheckLimits();
diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php
index d97efb59..4db4e633 100644
--- a/includes/specials/SpecialUndelete.php
+++ b/includes/specials/SpecialUndelete.php
@@ -58,16 +58,15 @@ class PageArchive {
$title = Title::newFromText( $prefix );
if( $title ) {
$ns = $title->getNamespace();
- $encPrefix = $dbr->escapeLike( $title->getDBkey() );
+ $prefix = $title->getDBkey();
} else {
// Prolly won't work too good
// @todo handle bare namespace names cleanly?
$ns = 0;
- $encPrefix = $dbr->escapeLike( $prefix );
}
$conds = array(
'ar_namespace' => $ns,
- "ar_title LIKE '$encPrefix%'",
+ 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
);
return self::listPages( $dbr, $conds );
}
@@ -188,20 +187,7 @@ class PageArchive {
'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
__METHOD__ );
if( $row ) {
- return new Revision( array(
- 'page' => $this->title->getArticleId(),
- 'id' => $row->ar_rev_id,
- 'text' => ($row->ar_text_id
- ? null
- : Revision::getRevisionText( $row, 'ar_' ) ),
- 'comment' => $row->ar_comment,
- 'user' => $row->ar_user,
- 'user_text' => $row->ar_user_text,
- 'timestamp' => $row->ar_timestamp,
- 'minor_edit' => $row->ar_minor_edit,
- 'text_id' => $row->ar_text_id,
- 'deleted' => $row->ar_deleted,
- 'len' => $row->ar_len) );
+ return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleId() ) );
} else {
return null;
}
@@ -299,7 +285,7 @@ class PageArchive {
if( $row ) {
return $this->getTextFromRow( $row );
} else {
- return NULL;
+ return null;
}
}
@@ -345,7 +331,7 @@ class PageArchive {
}
if( $restoreText ) {
- $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress );
+ $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
if($textRestored === false) // It must be one of UNDELETE_*
return false;
} else {
@@ -372,7 +358,7 @@ class PageArchive {
}
if( trim( $comment ) != '' )
- $reason .= ": {$comment}";
+ $reason .= wfMsgForContent( 'colon-separator' ) . $comment;
$log->addEntry( 'restore', $this->title, $reason );
return array($textRestored, $filesRestored, $reason);
@@ -390,7 +376,7 @@ class PageArchive {
*
* @return mixed number of revisions restored or false on failure
*/
- private function undeleteRevisions( $timestamps, $unsuppress = false ) {
+ private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
if ( wfReadOnly() )
return false;
$restoreAll = empty( $timestamps );
@@ -399,13 +385,14 @@ class PageArchive {
# Does this page already exist? We'll have to update it...
$article = new Article( $this->title );
- $options = 'FOR UPDATE';
+ $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() ),
__METHOD__,
- $options );
+ $options
+ );
if( $page ) {
$makepage = false;
# Page already exists. Import the history, and if necessary
@@ -462,50 +449,53 @@ class PageArchive {
$oldones ),
__METHOD__,
/* options */ array( 'ORDER BY' => 'ar_timestamp' )
- );
+ );
$ret = $dbw->resultObject( $result );
$rev_count = $dbw->numRows( $result );
+ if( !$rev_count ) {
+ wfDebug( __METHOD__.": no revisions to restore\n" );
+ return false; // ???
+ }
+
+ $ret->seek( $rev_count - 1 ); // move to last
+ $row = $ret->fetchObject(); // get newest archived rev
+ $ret->seek( 0 ); // move back
if( $makepage ) {
+ // Check the state of the newest to-be version...
+ if( !$unsuppress && ($row->ar_deleted & Revision::DELETED_TEXT) ) {
+ return false; // we can't leave the current revision like this!
+ }
+ // Safe to insert now...
$newid = $article->insertOn( $dbw );
$pageId = $newid;
+ } else {
+ // 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) ) {
+ return false; // we can't leave the current revision like this!
+ }
+ }
}
$revision = null;
$restored = 0;
while( $row = $ret->fetchObject() ) {
- if( $row->ar_text_id ) {
- // Revision was deleted in 1.5+; text is in
- // the regular text table, use the reference.
- // Specify null here so the so the text is
- // dereferenced for page length info if needed.
- $revText = null;
- } else {
- // Revision was deleted in 1.4 or earlier.
- // Text is squashed into the archive row, and
- // a new text table entry will be created for it.
- $revText = Revision::getRevisionText( $row, 'ar_' );
- }
// 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
}
-
- $revision = new Revision( array(
- 'page' => $pageId,
- 'id' => $row->ar_rev_id,
- 'text' => $revText,
- 'comment' => $row->ar_comment,
- 'user' => $row->ar_user,
- 'user_text' => $row->ar_user_text,
- 'timestamp' => $row->ar_timestamp,
- 'minor_edit' => $row->ar_minor_edit,
- 'text_id' => $row->ar_text_id,
- 'deleted' => $unsuppress ? 0 : $row->ar_deleted,
- 'len' => $row->ar_len
+ // Insert one revision at a time...maintaining deletion status
+ // unless we are specifically removing all restrictions...
+ $revision = Revision::newFromArchiveRow( $row,
+ array(
+ 'page' => $pageId,
+ 'deleted' => $unsuppress ? 0 : $row->ar_deleted
) );
+
$revision->insertOn( $dbw );
$restored++;
@@ -529,21 +519,13 @@ class PageArchive {
if( $newid || $wasnew ) {
// Update site stats, link tables, etc
$article->createUpdates( $revision );
- // We don't handle well with top revision deleted
- if( $revision->getVisibility() ) {
- $dbw->update( 'revision',
- array( 'rev_deleted' => 0 ),
- array( 'rev_id' => $revision->getId() ),
- __METHOD__
- );
- }
}
if( $newid ) {
- wfRunHooks( 'ArticleUndelete', array( &$this->title, true ) );
+ wfRunHooks( 'ArticleUndelete', array( &$this->title, true, $comment ) );
Article::onArticleCreate( $this->title );
} else {
- wfRunHooks( 'ArticleUndelete', array( &$this->title, false ) );
+ wfRunHooks( 'ArticleUndelete', array( &$this->title, false, $comment ) );
Article::onArticleEdit( $this->title );
}
@@ -569,7 +551,7 @@ class PageArchive {
*/
class UndeleteForm {
var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj;
- var $mTargetTimestamp, $mAllowed, $mComment, $mToken;
+ var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken;
function UndeleteForm( $request, $par = "" ) {
global $wgUser;
@@ -594,16 +576,21 @@ class UndeleteForm {
$this->mTarget = $par;
}
if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) {
- $this->mAllowed = true;
- } else {
+ $this->mAllowed = true; // user can restore
+ $this->mCanView = true; // user can view content
+ } elseif ( $wgUser->isAllowed( 'deletedtext' ) ) {
+ $this->mAllowed = false; // user cannot restore
+ $this->mCanView = true; // user can view content
+ } else { // user can only view the list of revisions
$this->mAllowed = false;
+ $this->mCanView = false;
$this->mTimestamp = '';
$this->mRestore = false;
}
if ( $this->mTarget !== "" ) {
$this->mTargetObj = Title::newFromURL( $this->mTarget );
} else {
- $this->mTargetObj = NULL;
+ $this->mTargetObj = null;
}
if( $this->mRestore || $this->mInvert ) {
$timestamps = array();
@@ -642,7 +629,7 @@ class UndeleteForm {
$this->showList( $result );
}
} else {
- $wgOut->addWikiText( wfMsgHtml( 'undelete-header' ) );
+ $wgOut->addWikiMsg( 'undelete-header' );
}
return;
}
@@ -652,8 +639,15 @@ class UndeleteForm {
if( $this->mFile !== null ) {
$file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
// Check if user is allowed to see this file
- if( !$file->userCan( File::DELETED_FILE ) ) {
- $wgOut->permissionRequired( 'suppressrevision' );
+ if ( !$file->exists() ) {
+ $wgOut->addWikiMsg( 'filedelete-nofile', $this->mFile );
+ return;
+ } else if( !$file->userCan( File::DELETED_FILE ) ) {
+ if( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
+ $wgOut->permissionRequired( 'suppressrevision' );
+ } else {
+ $wgOut->permissionRequired( 'deletedtext' );
+ }
return false;
} elseif ( !$wgUser->matchEditToken( $this->mToken, $this->mFile ) ) {
$this->showFileConfirmationForm( $this->mFile );
@@ -663,6 +657,11 @@ class UndeleteForm {
}
}
if( $this->mRestore && $this->mAction == "submit" ) {
+ global $wgUploadMaintenance;
+ if( $wgUploadMaintenance && $this->mTargetObj && $this->mTargetObj->getNamespace() == NS_FILE ) {
+ $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n", array( 'filedelete-maintenance' ) );
+ return;
+ }
return $this->undelete();
}
if( $this->mInvert && $this->mAction == "submit" ) {
@@ -707,8 +706,12 @@ class UndeleteForm {
$wgOut->addHTML( "<ul>\n" );
while( $row = $result->fetchObject() ) {
$title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
- $link = $sk->makeKnownLinkObj( $undelete, htmlspecialchars( $title->getPrefixedText() ),
- 'target=' . $title->getPrefixedUrl() );
+ $link = $sk->linkKnown(
+ $undelete,
+ htmlspecialchars( $title->getPrefixedText() ),
+ array(),
+ array( 'target' => $title->getPrefixedText() )
+ );
$revs = wfMsgExt( 'undeleterevisions',
array( 'parseinline' ),
$wgLang->formatNum( $row->count ) );
@@ -741,14 +744,14 @@ class UndeleteForm {
return;
} else {
$wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' );
- $wgOut->addHTML( '<br/>' );
+ $wgOut->addHTML( '<br />' );
// and we are allowed to see...
}
}
$wgOut->setPageTitle( wfMsg( 'undeletepage' ) );
- $link = $skin->makeKnownLinkObj(
+ $link = $skin->linkKnown(
SpecialPage::getTitleFor( 'Undelete', $this->mTargetObj->getPrefixedDBkey() ),
htmlspecialchars( $this->mTargetObj->getPrefixedText() )
);
@@ -763,7 +766,7 @@ class UndeleteForm {
$wgOut->addHTML( '<hr />' );
}
} else {
- $wgOut->addHTML( wfMsgHtml( 'undelete-nodiff' ) );
+ $wgOut->addWikiMsg( 'undelete-nodiff' );
}
}
@@ -774,13 +777,36 @@ class UndeleteForm {
$t = htmlspecialchars( $wgLang->time( $timestamp, true ) );
$user = $skin->revUserTools( $rev );
- $wgOut->addHTML( '<p>' . wfMsgHtml( 'undelete-revision', $link, $time, $user, $d, $t ) . '</p>' );
+ if( $this->mPreview ) {
+ $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
+ } else {
+ $openDiv = '<div id="mw-undelete-revision">';
+ }
+
+ // 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 );
+ }
+ } else {
+ $revdlink = '';
+ }
+ $wgOut->addHTML( $openDiv . $revdlink . wfMsgWikiHtml( 'undelete-revision', $link, $time, $user, $d, $t ) . '</div>' );
wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
if( $this->mPreview ) {
- $wgOut->addHTML( "<hr />\n" );
-
//Hide [edit]s
$popts = $wgOut->parserOptions();
$popts->setEditSection( false );
@@ -797,7 +823,7 @@ class UndeleteForm {
Xml::openElement( 'div' ) .
Xml::openElement( 'form', array(
'method' => 'post',
- 'action' => $self->getLocalURL( "action=submit" ) ) ) .
+ 'action' => $self->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
Xml::element( 'input', array(
'type' => 'hidden',
'name' => 'target',
@@ -830,7 +856,7 @@ class UndeleteForm {
* @return string HTML
*/
function showDiff( $previousRev, $currentRev ) {
- global $wgOut, $wgUser;
+ global $wgOut;
$diffEngine = new DifferenceEngine();
$diffEngine->showDiffStyle();
@@ -852,39 +878,63 @@ class UndeleteForm {
$diffEngine->generateDiffBody(
$previousRev->getText(), $currentRev->getText() ) .
"</table>" .
- "</div>\n" );
-
+ "</div>\n"
+ );
}
private function diffHeader( $rev, $prefix ) {
- global $wgUser, $wgLang, $wgLang;
+ global $wgUser, $wgLang;
$sk = $wgUser->getSkin();
$isDeleted = !( $rev->getId() && $rev->getTitle() );
if( $isDeleted ) {
- /// @fixme $rev->getTitle() is null for deleted revs...?
+ /// @todo Fixme: $rev->getTitle() is null for deleted revs...?
$targetPage = SpecialPage::getTitleFor( 'Undelete' );
- $targetQuery = 'target=' .
- $this->mTargetObj->getPrefixedUrl() .
- '&timestamp=' .
- wfTimestamp( TS_MW, $rev->getTimestamp() );
+ $targetQuery = array(
+ 'target' => $this->mTargetObj->getPrefixedText(),
+ 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
+ );
} else {
- /// @fixme getId() may return non-zero for deleted revs...
+ /// @todo Fixme getId() may return non-zero for deleted revs...
$targetPage = $rev->getTitle();
- $targetQuery = 'oldid=' . $rev->getId();
+ $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 = '';
}
return
'<div id="mw-diff-'.$prefix.'title1"><strong>' .
- $sk->makeLinkObj( $targetPage,
- wfMsgHtml( 'revisionasof',
- $wgLang->timeanddate( $rev->getTimestamp(), true ) ),
- $targetQuery ) .
- ( $isDeleted ? ' ' . wfMsgHtml( 'deletedrev' ) : '' ) .
+ $sk->link(
+ $targetPage,
+ wfMsgHtml(
+ 'revisionasof',
+ htmlspecialchars( $wgLang->timeanddate( $rev->getTimestamp(), true ) ),
+ htmlspecialchars( $wgLang->date( $rev->getTimestamp(), true ) ),
+ htmlspecialchars( $wgLang->time( $rev->getTimestamp(), true ) )
+ ),
+ array(),
+ $targetQuery
+ ) .
'</strong></div>' .
'<div id="mw-diff-'.$prefix.'title2">' .
- $sk->revUserTools( $rev ) . '<br/>' .
+ $sk->revUserTools( $rev ) . '<br />' .
'</div>' .
'<div id="mw-diff-'.$prefix.'title3">' .
- $sk->revComment( $rev ) . '<br/>' .
+ $sk->revComment( $rev ) . $del . '<br />' .
'</div>';
}
@@ -927,8 +977,11 @@ class UndeleteForm {
$wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
$wgRequest->response()->header( 'Pragma: no-cache' );
- $store = FileStore::get( 'deleted' );
- $store->stream( $key );
+ global $IP;
+ require_once( "$IP/includes/StreamFile.php" );
+ $repo = RepoGroup::singleton()->getLocalRepo();
+ $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
+ wfStreamFile( $path );
}
private function showHistory( ) {
@@ -941,7 +994,7 @@ class UndeleteForm {
$wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) );
}
- $wgOut->addWikiText( wfMsgHtml( 'undeletepagetitle', $this->mTargetObj->getPrefixedText()) );
+ $wgOut->wrapWikiMsg( "<div class='mw-undelete-pagetitle'>\n$1</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) );
$archive = new PageArchive( $this->mTargetObj );
/*
@@ -951,12 +1004,14 @@ class UndeleteForm {
return;
}
*/
+ $wgOut->addHTML( '<div class="mw-undelete-history">' );
if ( $this->mAllowed ) {
$wgOut->addWikiMsg( "undeletehistory" );
$wgOut->addWikiMsg( "undeleterevdel" );
} else {
$wgOut->addWikiMsg( "undeletehistorynoadmin" );
}
+ $wgOut->addHTML( '</div>' );
# List all stored revisions
$revisions = $archive->listRevisions();
@@ -987,7 +1042,7 @@ class UndeleteForm {
if ( $this->mAllowed ) {
$titleObj = SpecialPage::getTitleFor( "Undelete" );
- $action = $titleObj->getLocalURL( "action=submit" );
+ $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
# Start the form here
$top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) );
$wgOut->addHTML( $top );
@@ -1021,7 +1076,7 @@ class UndeleteForm {
Xml::fieldset( wfMsg( 'undelete-fieldset-title' ) ) .
Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
"<tr>
- <td colspan='2'>" .
+ <td colspan='2' class='mw-undelete-extrahelp'>" .
wfMsgWikiHtml( 'undeleteextrahelp' ) .
"</td>
</tr>
@@ -1091,20 +1146,13 @@ class UndeleteForm {
private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) {
global $wgUser, $wgLang;
- $rev = new Revision( array(
- 'page' => $this->mTargetObj->getArticleId(),
- 'comment' => $row->ar_comment,
- 'user' => $row->ar_user,
- 'user_text' => $row->ar_user_text,
- 'timestamp' => $row->ar_timestamp,
- 'minor_edit' => $row->ar_minor_edit,
- 'deleted' => $row->ar_deleted,
- 'len' => $row->ar_len ) );
-
+ $rev = Revision::newFromArchiveRow( $row,
+ array( 'page' => $this->mTargetObj->getArticleId() ) );
$stxt = '';
$ts = wfTimestamp( TS_MW, $row->ar_timestamp );
+ // Build checkboxen...
if( $this->mAllowed ) {
- if( $this->mInvert){
+ if( $this->mInvert ) {
if( in_array( $ts, $this->mTargetTimestamp ) ) {
$checkBox = Xml::check( "ts$ts");
} else {
@@ -1113,41 +1161,61 @@ class UndeleteForm {
} else {
$checkBox = Xml::check( "ts$ts" );
}
+ } else {
+ $checkBox = '';
+ }
+ // Build page & diff links...
+ if( $this->mCanView ) {
$titleObj = SpecialPage::getTitleFor( "Undelete" );
- $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk );
# Last link
if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
+ $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
$last = wfMsgHtml('diff');
} else if( $remaining > 0 || ($earliestLiveTime && $ts > $earliestLiveTime) ) {
- $last = $sk->makeKnownLinkObj( $titleObj, wfMsgHtml('diff'),
- "target=" . $this->mTargetObj->getPrefixedUrl() . "&timestamp=$ts&diff=prev" );
+ $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk );
+ $last = $sk->linkKnown(
+ $titleObj,
+ wfMsgHtml('diff'),
+ array(),
+ array(
+ 'target' => $this->mTargetObj->getPrefixedText(),
+ 'timestamp' => $ts,
+ 'diff' => 'prev'
+ )
+ );
} else {
+ $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk );
$last = wfMsgHtml('diff');
}
} else {
- $checkBox = '';
- $pageLink = $wgLang->timeanddate( $ts, true );
+ $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
$last = wfMsgHtml('diff');
}
+ // User links
$userLink = $sk->revUserTools( $rev );
-
- if(!is_null($size = $row->ar_len)) {
+ // Revision text size
+ if( !is_null($size = $row->ar_len) ) {
$stxt = $sk->formatRevisionSize( $size );
}
+ // Edit summary
$comment = $sk->revComment( $rev );
- $revdlink = '';
- if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ // Revision delete links
+ $canHide = $wgUser->isAllowed( 'deleterevision' );
+ if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
- // If revision was hidden from sysops
- $revdlink = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml('rev-delundel').')' );
+ $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
} else {
- $query = array( 'target' => $this->mTargetObj->getPrefixedDBkey(),
- 'artimestamp[]' => $ts
+ $query = array(
+ 'type' => 'archive',
+ 'target' => $this->mTargetObj->getPrefixedDBkey(),
+ 'ids' => $ts
);
- $revdlink = $sk->revDeleteLink( $query, $rev->isDeleted( Revision::DELETED_RESTRICTED ) );
+ $revdlink = $sk->revDeleteLink( $query,
+ $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
}
+ } else {
+ $revdlink = '';
}
-
return "<li>$checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment</li>";
}
@@ -1177,17 +1245,22 @@ class UndeleteForm {
')';
$data = htmlspecialchars( $data );
$comment = $this->getFileComment( $file, $sk );
- $revdlink = '';
- if( $wgUser->isAllowed( 'deleterevision' ) ) {
+ // Add show/hide deletion links if available
+ $canHide = $wgUser->isAllowed( 'deleterevision' );
+ if( $canHide || ($file->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
if( !$file->userCan(File::DELETED_RESTRICTED ) ) {
- // If revision was hidden from sysops
- $revdlink = Xml::tags( 'span', array( 'class'=>'mw-revdelundel-link' ), '('.wfMsgHtml('rev-delundel').')' );
+ $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
} else {
- $query = array( 'target' => $this->mTargetObj->getPrefixedDBkey(),
- 'fileid' => $row->fa_id
+ $query = array(
+ 'type' => 'filearchive',
+ 'target' => $this->mTargetObj->getPrefixedDBkey(),
+ 'ids' => $row->fa_id
);
- $revdlink = $sk->revDeleteLink( $query, $file->isDeleted( File::DELETED_RESTRICTED ) );
+ $revdlink = $sk->revDeleteLink( $query,
+ $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
}
+ } else {
+ $revdlink = '';
}
return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
}
@@ -1199,11 +1272,20 @@ class UndeleteForm {
function getPageLink( $rev, $titleObj, $ts, $sk ) {
global $wgLang;
+ $time = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
+
if( !$rev->userCan(Revision::DELETED_TEXT) ) {
- return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
+ return '<span class="history-deleted">' . $time . '</span>';
} else {
- $link = $sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ),
- "target=".$this->mTargetObj->getPrefixedUrl()."&timestamp=$ts" );
+ $link = $sk->linkKnown(
+ $titleObj,
+ $time,
+ array(),
+ array(
+ 'target' => $this->mTargetObj->getPrefixedText(),
+ 'timestamp' => $ts
+ )
+ );
if( $rev->isDeleted(Revision::DELETED_TEXT) )
$link = '<span class="history-deleted">' . $link . '</span>';
return $link;
@@ -1220,10 +1302,16 @@ class UndeleteForm {
if( !$file->userCan(File::DELETED_FILE) ) {
return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
} else {
- $link = $sk->makeKnownLinkObj( $titleObj, $wgLang->timeanddate( $ts, true ),
- "target=".$this->mTargetObj->getPrefixedUrl().
- "&file=$key" .
- "&token=" . urlencode( $wgUser->editToken( $key ) ) );
+ $link = $sk->linkKnown(
+ $titleObj,
+ $wgLang->timeanddate( $ts, true ),
+ array(),
+ array(
+ 'target' => $this->mTargetObj->getPrefixedText(),
+ 'file' => $key,
+ 'token' => $wgUser->editToken( $key )
+ )
+ );
if( $file->isDeleted(File::DELETED_FILE) )
$link = '<span class="history-deleted">' . $link . '</span>';
return $link;
@@ -1282,7 +1370,7 @@ class UndeleteForm {
$wgUser, $this->mComment) );
$skin = $wgUser->getSkin();
- $link = $skin->makeKnownLinkObj( $this->mTargetObj );
+ $link = $skin->linkKnown( $this->mTargetObj );
$wgOut->addHTML( wfMsgWikiHtml( 'undeletedpage', $link ) );
} else {
$wgOut->showFatalError( wfMsg( "cannotundelete" ) );
diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php
index a3e8a0c4..fe38a48a 100644
--- a/includes/specials/SpecialUnlockdb.php
+++ b/includes/specials/SpecialUnlockdb.php
@@ -45,7 +45,7 @@ class DBUnlockForm {
$wgOut->setPagetitle( wfMsg( "unlockdb" ) );
$wgOut->addWikiMsg( "unlockdbtext" );
- if ( "" != $err ) {
+ if ( $err != "" ) {
$wgOut->setSubtitle( wfMsg( "formerror" ) );
$wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" );
}
@@ -55,7 +55,7 @@ class DBUnlockForm {
$action = $titleObj->escapeLocalURL( "action=submit" );
$token = htmlspecialchars( $wgUser->editToken() );
- $wgOut->addHTML( <<<END
+ $wgOut->addHTML( <<<HTML
<form id="unlockdb" method="post" action="{$action}">
<table border="0">
@@ -74,7 +74,7 @@ class DBUnlockForm {
</table>
<input type="hidden" name="wpEditToken" value="{$token}" />
</form>
-END
+HTML
);
}
diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php
index 406f7944..fe7d7a17 100644
--- a/includes/specials/SpecialUnusedcategories.php
+++ b/includes/specials/SpecialUnusedcategories.php
@@ -34,7 +34,7 @@ class UnusedCategoriesPage extends QueryPage {
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_CATEGORY, $result->title );
- return $skin->makeLinkObj( $title, $title->getText() );
+ return $skin->link( $title, $title->getText() );
}
}
diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php
index fa66555d..9d9868f6 100644
--- a/includes/specials/SpecialUnusedimages.php
+++ b/includes/specials/SpecialUnusedimages.php
@@ -25,9 +25,19 @@ class UnusedimagesPage extends ImageQueryPage {
global $wgCountCategorizedImagesAsUsed, $wgDBtype;
$dbr = wfGetDB( DB_SLAVE );
- $epoch = $wgDBtype == 'mysql' ?
- 'UNIX_TIMESTAMP(img_timestamp)' :
- 'EXTRACT(epoch FROM img_timestamp)';
+ switch ($wgDBtype) {
+ case 'mysql':
+ $epoch = 'UNIX_TIMESTAMP(img_timestamp)';
+ break;
+ case 'oracle':
+ $epoch = '((trunc(img_timestamp) - to_date(\'19700101\',\'YYYYMMDD\')) * 86400)';
+ break;
+ case 'sqlite':
+ $epoch = 'img_timestamp';
+ break;
+ default:
+ $epoch = 'EXTRACT(epoch FROM img_timestamp)';
+ }
if ( $wgCountCategorizedImagesAsUsed ) {
list( $page, $image, $imagelinks, $categorylinks ) = $dbr->tableNamesN( 'page', 'image', 'imagelinks', 'categorylinks' );
diff --git a/includes/specials/SpecialUnusedtemplates.php b/includes/specials/SpecialUnusedtemplates.php
index 89acd09c..6ddbab32 100644
--- a/includes/specials/SpecialUnusedtemplates.php
+++ b/includes/specials/SpecialUnusedtemplates.php
@@ -33,11 +33,18 @@ class UnusedtemplatesPage extends QueryPage {
function formatResult( $skin, $result ) {
$title = Title::makeTitle( NS_TEMPLATE, $result->title );
- $pageLink = $skin->makeKnownLinkObj( $title, '', 'redirect=no' );
- $wlhLink = $skin->makeKnownLinkObj(
+ $pageLink = $skin->linkKnown(
+ $title,
+ null,
+ array(),
+ array( 'redirect' => 'no' )
+ );
+ $wlhLink = $skin->linkKnown(
SpecialPage::getTitleFor( 'Whatlinkshere' ),
wfMsgHtml( 'unusedtemplateswlh' ),
- 'target=' . $title->getPrefixedUrl() );
+ array(),
+ array( 'target' => $title->getPrefixedText() )
+ );
return wfSpecialList( $pageLink, $wlhLink );
}
diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php
index 64ab3729..483afdaa 100644
--- a/includes/specials/SpecialUnwatchedpages.php
+++ b/includes/specials/SpecialUnwatchedpages.php
@@ -44,8 +44,16 @@ class UnwatchedpagesPage extends QueryPage {
$nt = Title::makeTitle( $result->namespace, $result->title );
$text = $wgContLang->convert( $nt->getPrefixedText() );
- $plink = $skin->makeKnownLinkObj( $nt, htmlspecialchars( $text ) );
- $wlink = $skin->makeKnownLinkObj( $nt, wfMsgHtml( 'watch' ), 'action=watch' );
+ $plink = $skin->linkKnown(
+ $nt,
+ htmlspecialchars( $text )
+ );
+ $wlink = $skin->linkKnown(
+ $nt,
+ wfMsgHtml( 'watch' ),
+ array(),
+ array( 'action' => 'watch' )
+ );
return wfSpecialList( $plink, $wlink );
}
diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php
index 4c5bb160..9569945d 100644
--- a/includes/specials/SpecialUpload.php
+++ b/includes/specials/SpecialUpload.php
@@ -2,250 +2,139 @@
/**
* @file
* @ingroup SpecialPage
+ * @ingroup Upload
+ *
+ * Form for handling uploads and special page.
+ *
*/
-
-/**
- * Entry point
- */
-function wfSpecialUpload() {
- global $wgRequest;
- $form = new UploadForm( $wgRequest );
- $form->execute();
-}
-
-/**
- * implements Special:Upload
- * @ingroup SpecialPage
- */
-class UploadForm {
- const SUCCESS = 0;
- const BEFORE_PROCESSING = 1;
- const LARGE_FILE_SERVER = 2;
- const EMPTY_FILE = 3;
- const MIN_LENGTH_PARTNAME = 4;
- const ILLEGAL_FILENAME = 5;
- const PROTECTED_PAGE = 6;
- const OVERWRITE_EXISTING_FILE = 7;
- const FILETYPE_MISSING = 8;
- const FILETYPE_BADTYPE = 9;
- const VERIFICATION_ERROR = 10;
- const UPLOAD_VERIFICATION_ERROR = 11;
- const UPLOAD_WARNING = 12;
- const INTERNAL_ERROR = 13;
-
- /**#@+
- * @access private
- */
- var $mComment, $mLicense, $mIgnoreWarning, $mCurlError;
- var $mDestName, $mTempPath, $mFileSize, $mFileProps;
- var $mCopyrightStatus, $mCopyrightSource, $mReUpload, $mAction, $mUploadClicked;
- var $mSrcName, $mSessionKey, $mStashed, $mDesiredDestName, $mRemoveTempFile, $mSourceType;
- var $mDestWarningAck, $mCurlDestHandle;
- var $mLocalFile;
-
- # Placeholders for text injection by hooks (must be HTML)
- # extensions should take care to _append_ to the present value
- var $uploadFormTextTop;
- var $uploadFormTextAfterSummary;
-
- const SESSION_VERSION = 1;
- /**#@-*/
-
+class SpecialUpload extends SpecialPage {
/**
* Constructor : initialise object
* Get data POSTed through the form and assign them to the object
- * @param $request Data posted.
+ * @param WebRequest $request Data posted.
*/
- function UploadForm( &$request ) {
- global $wgAllowCopyUploads;
- $this->mDesiredDestName = $request->getText( 'wpDestFile' );
- $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' );
- $this->mComment = $request->getText( 'wpUploadDescription' );
- $this->mForReUpload = $request->getBool( 'wpForReUpload' );
- $this->mReUpload = $request->getCheck( 'wpReUpload' );
-
- if( !$request->wasPosted() ) {
- # GET requests just give the main form; no data except destination
- # filename and description
- return;
- }
+ public function __construct( $request = null ) {
+ global $wgRequest;
- # Placeholders for text injection by hooks (empty per default)
- $this->uploadFormTextTop = "";
- $this->uploadFormTextAfterSummary = "";
- $this->mUploadClicked = $request->getCheck( 'wpUpload' );
+ parent::__construct( 'Upload', 'upload' );
- $this->mLicense = $request->getText( 'wpLicense' );
- $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
- $this->mCopyrightSource = $request->getText( 'wpUploadSource' );
- $this->mWatchthis = $request->getBool( 'wpWatchthis' );
- $this->mSourceType = $request->getText( 'wpSourceType' );
- $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
-
- $this->mAction = $request->getVal( 'action' );
-
- $this->mSessionKey = $request->getInt( 'wpSessionKey' );
- if( !empty( $this->mSessionKey ) &&
- isset( $_SESSION['wsUploadData'][$this->mSessionKey]['version'] ) &&
- $_SESSION['wsUploadData'][$this->mSessionKey]['version'] == self::SESSION_VERSION ) {
- /**
- * Confirming a temporarily stashed upload.
- * We don't want path names to be forged, so we keep
- * them in the session on the server and just give
- * an opaque key to the user agent.
- */
- $data = $_SESSION['wsUploadData'][$this->mSessionKey];
- $this->mTempPath = $data['mTempPath'];
- $this->mFileSize = $data['mFileSize'];
- $this->mSrcName = $data['mSrcName'];
- $this->mFileProps = $data['mFileProps'];
- $this->mCurlError = 0/*UPLOAD_ERR_OK*/;
- $this->mStashed = true;
- $this->mRemoveTempFile = false;
- } else {
- /**
- *Check for a newly uploaded file.
- */
- if( $wgAllowCopyUploads && $this->mSourceType == 'web' ) {
- $this->initializeFromUrl( $request );
- } else {
- $this->initializeFromUpload( $request );
- }
- }
+ $this->loadRequest( is_null( $request ) ? $wgRequest : $request );
}
- /**
- * Initialize the uploaded file from PHP data
- * @access private
- */
- function initializeFromUpload( $request ) {
- $this->mTempPath = $request->getFileTempName( 'wpUploadFile' );
- $this->mFileSize = $request->getFileSize( 'wpUploadFile' );
- $this->mSrcName = $request->getFileName( 'wpUploadFile' );
- $this->mCurlError = $request->getUploadError( 'wpUploadFile' );
- $this->mSessionKey = false;
- $this->mStashed = false;
- $this->mRemoveTempFile = false; // PHP will handle this
- }
+ /** Misc variables **/
+ protected $mRequest; // The WebRequest or FauxRequest this form is supposed to handle
+ protected $mSourceType;
+ protected $mUpload;
+ protected $mLocalFile;
+ protected $mUploadClicked;
+
+ /** User input variables from the "description" section **/
+ public $mDesiredDestName; // The requested target file name
+ protected $mComment;
+ protected $mLicense;
+
+ /** User input variables from the root section **/
+ protected $mIgnoreWarning;
+ protected $mWatchThis;
+ protected $mCopyrightStatus;
+ protected $mCopyrightSource;
+
+ /** Hidden variables **/
+ protected $mDestWarningAck;
+ protected $mForReUpload; // The user followed an "overwrite this file" link
+ protected $mCancelUpload; // The user clicked "Cancel and return to upload form" button
+ protected $mTokenOk;
+ protected $mUploadSuccessful = false; // Subclasses can use this to determine whether a file was uploaded
+
+ /** Text injection points for hooks not using HTMLForm **/
+ public $uploadFormTextTop;
+ public $uploadFormTextAfterSummary;
+
/**
- * Copy a web file to a temporary file
- * @access private
+ * Initialize instance variables from request and create an Upload handler
+ *
+ * @param WebRequest $request The request to extract variables from
*/
- function initializeFromUrl( $request ) {
- global $wgTmpDirectory;
- $url = $request->getText( 'wpUploadFileURL' );
- $local_file = tempnam( $wgTmpDirectory, 'WEBUPLOAD' );
-
- $this->mTempPath = $local_file;
- $this->mFileSize = 0; # Will be set by curlCopy
- $this->mCurlError = $this->curlCopy( $url, $local_file );
- $pathParts = explode( '/', $url );
- $this->mSrcName = array_pop( $pathParts );
- $this->mSessionKey = false;
- $this->mStashed = false;
-
- // PHP won't auto-cleanup the file
- $this->mRemoveTempFile = file_exists( $local_file );
- }
+ protected function loadRequest( $request ) {
+ global $wgUser;
- /**
- * Safe copy from URL
- * Returns true if there was an error, false otherwise
- */
- private function curlCopy( $url, $dest ) {
- global $wgUser, $wgOut, $wgHTTPProxy;
+ $this->mRequest = $request;
+ $this->mSourceType = $request->getVal( 'wpSourceType', 'file' );
+ $this->mUpload = UploadBase::createFromRequest( $request );
+ $this->mUploadClicked = $request->wasPosted()
+ && ( $request->getCheck( 'wpUpload' )
+ || $request->getCheck( 'wpUploadIgnoreWarning' ) );
- if( !$wgUser->isAllowed( 'upload_by_url' ) ) {
- $wgOut->permissionRequired( 'upload_by_url' );
- return true;
- }
+ // Guess the desired name from the filename if not provided
+ $this->mDesiredDestName = $request->getText( 'wpDestFile' );
+ if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null )
+ $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' );
+ $this->mComment = $request->getText( 'wpUploadDescription' );
+ $this->mLicense = $request->getText( 'wpLicense' );
- # Maybe remove some pasting blanks :-)
- $url = trim( $url );
- if( stripos($url, 'http://') !== 0 && stripos($url, 'ftp://') !== 0 ) {
- # Only HTTP or FTP URLs
- $wgOut->showErrorPage( 'upload-proto-error', 'upload-proto-error-text' );
- return true;
- }
- # Open temporary file
- $this->mCurlDestHandle = @fopen( $this->mTempPath, "wb" );
- if( $this->mCurlDestHandle === false ) {
- # Could not open temporary file to write in
- $wgOut->showErrorPage( 'upload-file-error', 'upload-file-error-text');
- return true;
- }
+ $this->mDestWarningAck = $request->getText( 'wpDestFileWarningAck' );
+ $this->mIgnoreWarning = $request->getCheck( 'wpIgnoreWarning' )
+ || $request->getCheck( 'wpUploadIgnoreWarning' );
+ $this->mWatchthis = $request->getBool( 'wpWatchthis' ) && $wgUser->isLoggedIn();
+ $this->mCopyrightStatus = $request->getText( 'wpUploadCopyStatus' );
+ $this->mCopyrightSource = $request->getText( 'wpUploadSource' );
- $ch = curl_init();
- curl_setopt( $ch, CURLOPT_HTTP_VERSION, 1.0); # Probably not needed, but apparently can work around some bug
- curl_setopt( $ch, CURLOPT_TIMEOUT, 10); # 10 seconds timeout
- curl_setopt( $ch, CURLOPT_LOW_SPEED_LIMIT, 512); # 0.5KB per second minimum transfer speed
- curl_setopt( $ch, CURLOPT_URL, $url);
- if( $wgHTTPProxy ) {
- curl_setopt( $ch, CURLOPT_PROXY, $wgHTTPProxy );
- }
- curl_setopt( $ch, CURLOPT_WRITEFUNCTION, array( $this, 'uploadCurlCallback' ) );
- curl_exec( $ch );
- $error = curl_errno( $ch ) ? true : false;
- $errornum = curl_errno( $ch );
- // if ( $error ) print curl_error ( $ch ) ; # Debugging output
- curl_close( $ch );
-
- fclose( $this->mCurlDestHandle );
- unset( $this->mCurlDestHandle );
- if( $error ) {
- unlink( $dest );
- if( wfEmptyMsg( "upload-curl-error$errornum", wfMsg("upload-curl-error$errornum") ) )
- $wgOut->showErrorPage( 'upload-misc-error', 'upload-misc-error-text' );
- else
- $wgOut->showErrorPage( "upload-curl-error$errornum", "upload-curl-error$errornum-text" );
- }
- return $error;
+ $this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
+ $this->mCancelUpload = $request->getCheck( 'wpCancelUpload' )
+ || $request->getCheck( 'wpReUpload' ); // b/w compat
+
+ // If it was posted check for the token (no remote POST'ing with user credentials)
+ $token = $request->getVal( 'wpEditToken' );
+ if( $this->mSourceType == 'file' && $token == null ) {
+ // Skip token check for file uploads as that can't be faked via JS...
+ // Some client-side tools don't expect to need to send wpEditToken
+ // with their submissions, as that's new in 1.16.
+ $this->mTokenOk = true;
+ } else {
+ $this->mTokenOk = $wgUser->matchEditToken( $token );
+ }
+
+ $this->uploadFormTextTop = '';
+ $this->uploadFormTextAfterSummary = '';
}
/**
- * Callback function for CURL-based web transfer
- * Write data to file unless we've passed the length limit;
- * if so, abort immediately.
- * @access private
+ * This page can be shown if uploading is enabled.
+ * Handle permission checking elsewhere in order to be able to show
+ * custom error messages.
+ *
+ * @param User $user
+ * @return bool
*/
- function uploadCurlCallback( $ch, $data ) {
- global $wgMaxUploadSize;
- $length = strlen( $data );
- $this->mFileSize += $length;
- if( $this->mFileSize > $wgMaxUploadSize ) {
- return 0;
- }
- fwrite( $this->mCurlDestHandle, $data );
- return $length;
+ public function userCanExecute( $user ) {
+ return UploadBase::isEnabled() && parent::userCanExecute( $user );
}
/**
- * Start doing stuff
- * @access public
+ * Special page entry point
*/
- function execute() {
- global $wgUser, $wgOut;
- global $wgEnableUploads;
+ public function execute( $par ) {
+ global $wgUser, $wgOut, $wgRequest;
- # Check php's file_uploads setting
- if( !wfIniGetBool( 'file_uploads' ) ) {
- $wgOut->showErrorPage( 'uploaddisabled', 'php-uploaddisabledtext', array( $this->mDesiredDestName ) );
- return;
- }
+ $this->setHeaders();
+ $this->outputHeader();
# Check uploading enabled
- if( !$wgEnableUploads ) {
- $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext', array( $this->mDesiredDestName ) );
+ if( !UploadBase::isEnabled() ) {
+ $wgOut->showErrorPage( 'uploaddisabled', 'uploaddisabledtext' );
return;
}
# Check permissions
+ global $wgGroupPermissions;
if( !$wgUser->isAllowed( 'upload' ) ) {
- if( !$wgUser->isLoggedIn() ) {
+ if( !$wgUser->isLoggedIn() && ( $wgGroupPermissions['user']['upload']
+ || $wgGroupPermissions['autoconfirmed']['upload'] ) ) {
+ // Custom message if logged-in users without any special rights can upload
$wgOut->showErrorPage( 'uploadnologin', 'uploadnologintext' );
} else {
$wgOut->permissionRequired( 'upload' );
@@ -259,459 +148,490 @@ class UploadForm {
return;
}
+ # Check whether we actually want to allow changing stuff
if( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
- if( $this->mReUpload ) {
- if( !$this->unsaveUploadedFile() ) {
+ # Unsave the temporary file in case this was a cancelled upload
+ if ( $this->mCancelUpload ) {
+ if ( !$this->unsaveUploadedFile() )
+ # Something went wrong, so unsaveUploadedFile showed a warning
return;
- }
- # Because it is probably checked and shouldn't be
- $this->mIgnoreWarning = false;
-
- $this->mainUploadForm();
- } else if( 'submit' == $this->mAction || $this->mUploadClicked ) {
+ }
+
+ # Process upload or show a form
+ if ( $this->mTokenOk && !$this->mCancelUpload
+ && ( $this->mUpload && $this->mUploadClicked ) ) {
$this->processUpload();
} else {
- $this->mainUploadForm();
+ # Backwards compatibility hook
+ if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) )
+ {
+ wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" );
+ return;
+ }
+
+ $this->showUploadForm( $this->getUploadForm() );
}
- $this->cleanupTempFile();
+ # Cleanup
+ if ( $this->mUpload )
+ $this->mUpload->cleanupTempFile();
}
/**
- * Do the upload
- * Checks are made in SpecialUpload::execute()
+ * Show the main upload form
*
- * @access private
+ * @param mixed $form An HTMLForm instance or HTML string to show
*/
- function processUpload(){
- global $wgUser, $wgOut, $wgFileExtensions, $wgLang;
- $details = null;
- $value = null;
- $value = $this->internalProcessUpload( $details );
-
- switch($value) {
- case self::SUCCESS:
- $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
- break;
-
- case self::BEFORE_PROCESSING:
- break;
-
- case self::LARGE_FILE_SERVER:
- $this->mainUploadForm( wfMsgHtml( 'largefileserver' ) );
- break;
-
- case self::EMPTY_FILE:
- $this->mainUploadForm( wfMsgHtml( 'emptyfile' ) );
- break;
-
- case self::MIN_LENGTH_PARTNAME:
- $this->mainUploadForm( wfMsgHtml( 'minlength1' ) );
- break;
-
- case self::ILLEGAL_FILENAME:
- $filtered = $details['filtered'];
- $this->uploadError( wfMsgWikiHtml( 'illegalfilename', htmlspecialchars( $filtered ) ) );
- break;
-
- case self::PROTECTED_PAGE:
- $wgOut->showPermissionsErrorPage( $details['permissionserrors'] );
- break;
+ protected function showUploadForm( $form ) {
+ # Add links if file was previously deleted
+ if ( !$this->mDesiredDestName ) {
+ $this->showViewDeletedLinks();
+ }
+
+ if ( $form instanceof HTMLForm ) {
+ $form->show();
+ } else {
+ global $wgOut;
+ $wgOut->addHTML( $form );
+ }
+
+ }
- case self::OVERWRITE_EXISTING_FILE:
- $errorText = $details['overwrite'];
- $this->uploadError( $wgOut->parse( $errorText ) );
- break;
+ /**
+ * Get an UploadForm instance with title and text properly set.
+ *
+ * @param string $message HTML string to add to the form
+ * @param string $sessionKey Session key in case this is a stashed upload
+ * @return UploadForm
+ */
+ protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) {
+ global $wgOut;
+
+ # Initialize form
+ $form = new UploadForm( array(
+ 'watch' => $this->getWatchCheck(),
+ 'forreupload' => $this->mForReUpload,
+ 'sessionkey' => $sessionKey,
+ 'hideignorewarning' => $hideIgnoreWarning,
+ 'destwarningack' => (bool)$this->mDestWarningAck,
+
+ 'texttop' => $this->uploadFormTextTop,
+ 'textaftersummary' => $this->uploadFormTextAfterSummary,
+ 'destfile' => $this->mDesiredDestName,
+ ) );
+ $form->setTitle( $this->getTitle() );
+
+ # Check the token, but only if necessary
+ if( !$this->mTokenOk && !$this->mCancelUpload
+ && ( $this->mUpload && $this->mUploadClicked ) ) {
+ $form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) );
+ }
+
+ # Add text to form
+ $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 ) ) {
+ $form->addPostText( '<div id="mw-upload-footer-message">'
+ . $wgOut->parse( $uploadFooter ) . "</div>\n" );
+ }
+
+ return $form;
- case self::FILETYPE_MISSING:
- $this->uploadError( wfMsgExt( 'filetype-missing', array ( 'parseinline' ) ) );
- break;
+ }
- case self::FILETYPE_BADTYPE:
- $finalExt = $details['finalExt'];
- $this->uploadError(
- wfMsgExt( 'filetype-banned-type',
- array( 'parseinline' ),
- htmlspecialchars( $finalExt ),
- $wgLang->commaList( $wgFileExtensions ),
- $wgLang->formatNum( count($wgFileExtensions) )
+ /**
+ * Shows the "view X deleted revivions link""
+ */
+ protected function showViewDeletedLinks() {
+ global $wgOut, $wgUser;
+
+ $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
+ // Show a subtitle link to deleted revisions (to sysops et al only)
+ if( $title instanceof Title ) {
+ $count = $title->isDeleted();
+ if ( $count > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) {
+ $link = wfMsgExt(
+ $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
+ array( 'parse', 'replaceafter' ),
+ $wgUser->getSkin()->linkKnown(
+ SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
+ wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
)
);
- break;
-
- case self::VERIFICATION_ERROR:
- $veri = $details['veri'];
- $this->uploadError( $veri->toString() );
- break;
-
- case self::UPLOAD_VERIFICATION_ERROR:
- $error = $details['error'];
- $this->uploadError( $error );
- break;
-
- case self::UPLOAD_WARNING:
- $warning = $details['warning'];
- $this->uploadWarning( $warning );
- break;
-
- case self::INTERNAL_ERROR:
- $internal = $details['internal'];
- $this->showError( $internal );
- break;
+ $wgOut->addHTML( "<div id=\"contentSub2\">{$link}</div>" );
+ }
+ }
- default:
- throw new MWException( __METHOD__ . ": Unknown value `{$value}`" );
- }
+ // 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() );
+ }
}
/**
- * Really do the upload
- * Checks are made in SpecialUpload::execute()
+ * Stashes the upload and shows the main upload form.
*
- * @param array $resultDetails contains result-specific dict of additional values
+ * Note: only errors that can be handled by changing the name or
+ * description should be redirected here. It should be assumed that the
+ * file itself is sane and has passed UploadBase::verifyFile. This
+ * essentially means that UploadBase::VERIFICATION_ERROR and
+ * UploadBase::EMPTY_FILE should not be passed here.
*
- * @access private
+ * @param string $message HTML message to be passed to mainUploadForm
+ */
+ protected function showRecoverableUploadError( $message ) {
+ $sessionKey = $this->mUpload->stashSession();
+ $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
+ '<div class="error">' . $message . "</div>\n";
+
+ $form = $this->getUploadForm( $message, $sessionKey );
+ $form->setSubmitText( wfMsg( 'upload-tryagain' ) );
+ $this->showUploadForm( $form );
+ }
+ /**
+ * Stashes the upload, shows the main form, but adds an "continue anyway button".
+ * Also checks whether there are actually warnings to display.
+ *
+ * @param array $warnings
+ * @return boolean true if warnings were displayed, false if there are no
+ * warnings and the should continue processing like there was no warning
*/
- function internalProcessUpload( &$resultDetails ) {
+ protected function showUploadWarning( $warnings ) {
global $wgUser;
- if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
+ # If there are no warnings, or warnings we can ignore, return early.
+ # mDestWarningAck is set when some javascript has shown the warning
+ # to the user. mForReUpload is set when the user clicks the "upload a
+ # new version" link.
+ if ( !$warnings || ( count( $warnings ) == 1 &&
+ isset( $warnings['exists'] ) &&
+ ( $this->mDestWarningAck || $this->mForReUpload ) ) )
{
- wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
- return self::BEFORE_PROCESSING;
+ return false;
}
- /**
- * If there was no filename or a zero size given, give up quick.
- */
- if( trim( $this->mSrcName ) == '' || empty( $this->mFileSize ) ) {
- return self::EMPTY_FILE;
- }
+ $sessionKey = $this->mUpload->stashSession();
- /* Check for curl error */
- if( $this->mCurlError ) {
- return self::BEFORE_PROCESSING;
- }
+ $sk = $wgUser->getSkin();
- /**
- * Chop off any directories in the given filename. Then
- * filter out illegal characters, and try to make a legible name
- * out of it. We'll strip some silently that Title would die on.
- */
- if( $this->mDesiredDestName ) {
- $basename = $this->mDesiredDestName;
- } else {
- $basename = $this->mSrcName;
- }
- $filtered = wfStripIllegalFilenameChars( $basename );
-
- /* Normalize to title form before we do any further processing */
- $nt = Title::makeTitleSafe( NS_FILE, $filtered );
- if( is_null( $nt ) ) {
- $resultDetails = array( 'filtered' => $filtered );
- return self::ILLEGAL_FILENAME;
+ $warningHtml = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n"
+ . '<ul class="warning">';
+ foreach( $warnings as $warning => $args ) {
+ $msg = '';
+ if( $warning == 'exists' ) {
+ $msg = "\t<li>" . self::getExistsWarning( $args ) . "</li>\n";
+ } elseif( $warning == 'duplicate' ) {
+ $msg = self::getDupeWarning( $args );
+ } elseif( $warning == 'duplicate-archive' ) {
+ $msg = "\t<li>" . wfMsgExt( 'file-deleted-duplicate', 'parseinline',
+ array( Title::makeTitle( NS_FILE, $args )->getPrefixedText() ) )
+ . "</li>\n";
+ } else {
+ if ( $args === true )
+ $args = array();
+ elseif ( !is_array( $args ) )
+ $args = array( $args );
+ $msg = "\t<li>" . wfMsgExt( $warning, 'parseinline', $args ) . "</li>\n";
+ }
+ $warningHtml .= $msg;
}
- $filtered = $nt->getDBkey();
+ $warningHtml .= "</ul>\n";
+ $warningHtml .= wfMsgExt( 'uploadwarning-text', 'parse' );
+
+ $form = $this->getUploadForm( $warningHtml, $sessionKey, /* $hideIgnoreWarning */ true );
+ $form->setSubmitText( wfMsg( 'upload-tryagain' ) );
+ $form->addButton( 'wpUploadIgnoreWarning', wfMsg( 'ignorewarning' ) );
+ $form->addButton( 'wpCancelUpload', wfMsg( 'reuploaddesc' ) );
+
+ $this->showUploadForm( $form );
- /**
- * We'll want to blacklist against *any* 'extension', and use
- * only the final one for the whitelist.
- */
- list( $partname, $ext ) = $this->splitExtensions( $filtered );
-
- if( count( $ext ) ) {
- $finalExt = $ext[count( $ext ) - 1];
- } else {
- $finalExt = '';
- }
+ # Indicate that we showed a form
+ return true;
+ }
- # If there was more than one "extension", reassemble the base
- # filename to prevent bogus complaints about length
- if( count( $ext ) > 1 ) {
- for( $i = 0; $i < count( $ext ) - 1; $i++ )
- $partname .= '.' . $ext[$i];
- }
+ /**
+ * Show the upload form with error message, but do not stash the file.
+ *
+ * @param string $message
+ */
+ protected function showUploadError( $message ) {
+ $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" .
+ '<div class="error">' . $message . "</div>\n";
+ $this->showUploadForm( $this->getUploadForm( $message ) );
+ }
- if( strlen( $partname ) < 1 ) {
- return self::MIN_LENGTH_PARTNAME;
- }
+ /**
+ * Do the upload.
+ * Checks are made in SpecialUpload::execute()
+ */
+ protected function processUpload() {
+ global $wgUser, $wgOut;
- $this->mLocalFile = wfLocalFile( $nt );
- $this->mDestName = $this->mLocalFile->getName();
-
- /**
- * If the image is protected, non-sysop users won't be able
- * to modify it by uploading a new revision.
- */
- $permErrors = $nt->getUserPermissionsErrors( 'edit', $wgUser );
- $permErrorsUpload = $nt->getUserPermissionsErrors( 'upload', $wgUser );
- $permErrorsCreate = ( $nt->exists() ? array() : $nt->getUserPermissionsErrors( 'create', $wgUser ) );
-
- if( $permErrors || $permErrorsUpload || $permErrorsCreate ) {
- // merge all the problems into one list, avoiding duplicates
- $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsUpload, $permErrors ) );
- $permErrors = array_merge( $permErrors, wfArrayDiff2( $permErrorsCreate, $permErrors ) );
- $resultDetails = array( 'permissionserrors' => $permErrors );
- return self::PROTECTED_PAGE;
+ // Verify permissions
+ $permErrors = $this->mUpload->verifyPermissions( $wgUser );
+ if( $permErrors !== true ) {
+ $wgOut->showPermissionsErrorPage( $permErrors );
+ return;
}
- /**
- * In some cases we may forbid overwriting of existing files.
- */
- $overwrite = $this->checkOverwrite( $this->mDestName );
- if( $overwrite !== true ) {
- $resultDetails = array( 'overwrite' => $overwrite );
- return self::OVERWRITE_EXISTING_FILE;
+ // Fetch the file if required
+ $status = $this->mUpload->fetchFile();
+ if( !$status->isOK() ) {
+ $this->showUploadForm( $this->getUploadForm( $wgOut->parse( $status->getWikiText() ) ) );
+ return;
}
- /* Don't allow users to override the blacklist (check file extension) */
- global $wgCheckFileExtensions, $wgStrictFileExtensions;
- global $wgFileExtensions, $wgFileBlacklist;
- if ($finalExt == '') {
- return self::FILETYPE_MISSING;
- } elseif ( $this->checkFileExtensionList( $ext, $wgFileBlacklist ) ||
- ($wgCheckFileExtensions && $wgStrictFileExtensions &&
- !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) ) {
- $resultDetails = array( 'finalExt' => $finalExt );
- return self::FILETYPE_BADTYPE;
+ // Deprecated backwards compatibility hook
+ if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) )
+ {
+ wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" );
+ return array( 'status' => UploadBase::BEFORE_PROCESSING );
}
- /**
- * Look at the contents of the file; if we can recognize the
- * type but it's corrupt or data of the wrong type, we should
- * probably not accept it.
- */
- if( !$this->mStashed ) {
- $this->mFileProps = File::getPropsFromPath( $this->mTempPath, $finalExt );
- $this->checkMacBinary();
- $veri = $this->verify( $this->mTempPath, $finalExt );
-
- if( $veri !== true ) { //it's a wiki error...
- $resultDetails = array( 'veri' => $veri );
- return self::VERIFICATION_ERROR;
- }
- /**
- * Provide an opportunity for extensions to add further checks
- */
- $error = '';
- if( !wfRunHooks( 'UploadVerification',
- array( $this->mDestName, $this->mTempPath, &$error ) ) ) {
- $resultDetails = array( 'error' => $error );
- return self::UPLOAD_VERIFICATION_ERROR;
- }
+ // Upload verification
+ $details = $this->mUpload->verifyUpload();
+ if ( $details['status'] != UploadBase::OK ) {
+ $this->processVerificationError( $details );
+ return;
}
+ $this->mLocalFile = $this->mUpload->getLocalFile();
- /**
- * Check for non-fatal conditions
- */
- if ( ! $this->mIgnoreWarning ) {
- $warning = '';
-
- $comparableName = str_replace( ' ', '_', $basename );
- global $wgCapitalLinks, $wgContLang;
- if ( $wgCapitalLinks ) {
- $comparableName = $wgContLang->ucfirst( $comparableName );
- }
-
- if( $comparableName !== $filtered ) {
- $warning .= '<li>'.wfMsgHtml( 'badfilename', htmlspecialchars( $this->mDestName ) ).'</li>';
- }
-
- global $wgCheckFileExtensions;
- if ( $wgCheckFileExtensions ) {
- if ( !$this->checkFileExtension( $finalExt, $wgFileExtensions ) ) {
- global $wgLang;
- $warning .= '<li>' .
- wfMsgExt( 'filetype-unwanted-type',
- array( 'parseinline' ),
- htmlspecialchars( $finalExt ),
- $wgLang->commaList( $wgFileExtensions ),
- $wgLang->formatNum( count($wgFileExtensions) )
- ) . '</li>';
- }
- }
-
- global $wgUploadSizeWarning;
- if ( $wgUploadSizeWarning && ( $this->mFileSize > $wgUploadSizeWarning ) ) {
- $skin = $wgUser->getSkin();
- $wsize = $skin->formatSize( $wgUploadSizeWarning );
- $asize = $skin->formatSize( $this->mFileSize );
- $warning .= '<li>' . wfMsgHtml( 'large-file', $wsize, $asize ) . '</li>';
- }
- if ( $this->mFileSize == 0 ) {
- $warning .= '<li>'.wfMsgHtml( 'emptyfile' ).'</li>';
- }
-
- if ( !$this->mDestWarningAck ) {
- $warning .= self::getExistsWarning( $this->mLocalFile );
- }
-
- $warning .= $this->getDupeWarning( $this->mTempPath, $finalExt, $nt );
-
- if( $warning != '' ) {
- /**
- * Stash the file in a temporary location; the user can choose
- * to let it through and we'll complete the upload then.
- */
- $resultDetails = array( 'warning' => $warning );
- return self::UPLOAD_WARNING;
+ // Check warnings if necessary
+ if( !$this->mIgnoreWarning ) {
+ $warnings = $this->mUpload->checkWarnings();
+ if( $this->showUploadWarning( $warnings ) ) {
+ return;
}
}
- /**
- * Try actually saving the thing...
- * It will show an error form on failure.
- */
+ // Get the page text if this is not a reupload
if( !$this->mForReUpload ) {
$pageText = self::getInitialPageText( $this->mComment, $this->mLicense,
$this->mCopyrightStatus, $this->mCopyrightSource );
- }
-
- $status = $this->mLocalFile->upload( $this->mTempPath, $this->mComment, $pageText,
- File::DELETE_SOURCE, $this->mFileProps );
- if ( !$status->isGood() ) {
- $resultDetails = array( 'internal' => $status->getWikiText() );
- return self::INTERNAL_ERROR;
} else {
- if ( $this->mWatchthis ) {
- global $wgUser;
- $wgUser->addWatch( $this->mLocalFile->getTitle() );
- }
- // Success, redirect to description page
- $img = null; // @todo: added to avoid passing a ref to null - should this be defined somewhere?
- wfRunHooks( 'UploadComplete', array( &$this ) );
- return self::SUCCESS;
+ $pageText = false;
}
+ $status = $this->mUpload->performUpload( $this->mComment, $pageText, $this->mWatchthis, $wgUser );
+ if ( !$status->isGood() ) {
+ $this->showUploadError( $wgOut->parse( $status->getWikiText() ) );
+ return;
+ }
+
+ // Success, redirect to description page
+ $this->mUploadSuccessful = true;
+ wfRunHooks( 'SpecialUploadComplete', array( &$this ) );
+ $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() );
+
}
/**
- * Do existence checks on a file and produce a warning
- * This check is static and can be done pre-upload via AJAX
- * Returns an HTML fragment consisting of one or more LI elements if there is a warning
- * Returns an empty string if there is no warning
+ * Get the initial image page text based on a comment and optional file status information
*/
- static function getExistsWarning( $file ) {
- global $wgUser, $wgContLang;
- // Check for uppercase extension. We allow these filenames but check if an image
- // with lowercase extension exists already
- $warning = '';
- $align = $wgContLang->isRtl() ? 'left' : 'right';
-
- if( strpos( $file->getName(), '.' ) == false ) {
- $partname = $file->getName();
- $rawExtension = '';
+ public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) {
+ global $wgUseCopyrightUpload;
+ if ( $wgUseCopyrightUpload ) {
+ $licensetxt = '';
+ if ( $license != '' ) {
+ $licensetxt = '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+ }
+ $pageText = '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n" .
+ '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
+ "$licensetxt" .
+ '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
} else {
- $n = strrpos( $file->getName(), '.' );
- $rawExtension = substr( $file->getName(), $n + 1 );
- $partname = substr( $file->getName(), 0, $n );
+ if ( $license != '' ) {
+ $filedesc = $comment == '' ? '' : '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n";
+ $pageText = $filedesc .
+ '== ' . wfMsgForContent ( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n";
+ } else {
+ $pageText = $comment;
+ }
}
+ return $pageText;
+ }
- $sk = $wgUser->getSkin();
+ /**
+ * See if we should check the 'watch this page' checkbox on the form
+ * based on the user's preferences and whether we're being asked
+ * to create a new file or update an existing one.
+ *
+ * In the case where 'watch edits' is off but 'watch creations' is on,
+ * we'll leave the box unchecked.
+ *
+ * Note that the page target can be changed *on the form*, so our check
+ * state can get out of sync.
+ */
+ protected function getWatchCheck() {
+ global $wgUser;
+ if( $wgUser->getOption( 'watchdefault' ) ) {
+ // Watch all edits!
+ return true;
+ }
- if ( $rawExtension != $file->getExtension() ) {
- // We're not using the normalized form of the extension.
- // Normal form is lowercase, using most common of alternate
- // extensions (eg 'jpg' rather than 'JPEG').
- //
- // Check for another file using the normalized form...
- $nt_lc = Title::makeTitle( NS_FILE, $partname . '.' . $file->getExtension() );
- $file_lc = wfLocalFile( $nt_lc );
+ $local = wfLocalFile( $this->mDesiredDestName );
+ if( $local && $local->exists() ) {
+ // We're uploading a new version of an existing file.
+ // No creation, so don't watch it if we're not already.
+ return $local->getTitle()->userIsWatching();
} else {
- $file_lc = false;
+ // New page should get watched if that's our option.
+ return $wgUser->getOption( 'watchcreations' );
}
+ }
- if( $file->exists() ) {
- $dlink = $sk->makeKnownLinkObj( $file->getTitle() );
- if ( $file->allowInlineDisplay() ) {
- $dlink2 = $sk->makeImageLinkObj( $file->getTitle(), wfMsgExt( 'fileexists-thumb', 'parseinline' ),
- $file->getName(), $align, array(), false, true );
- } elseif ( !$file->allowInlineDisplay() && $file->isSafeFile() ) {
- $icon = $file->iconThumb();
- $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
- $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>';
- } else {
- $dlink2 = '';
- }
- $warning .= '<li>' . wfMsgExt( 'fileexists', array('parseinline','replaceafter'), $dlink ) . '</li>' . $dlink2;
-
- } elseif( $file->getTitle()->getArticleID() ) {
- $lnk = $sk->makeKnownLinkObj( $file->getTitle(), '', 'redirect=no' );
- $warning .= '<li>' . wfMsgExt( 'filepageexists', array( 'parseinline', 'replaceafter' ), $lnk ) . '</li>';
- } elseif ( $file_lc && $file_lc->exists() ) {
- # Check if image with lowercase extension exists.
- # It's not forbidden but in 99% it makes no sense to upload the same filename with uppercase extension
- $dlink = $sk->makeKnownLinkObj( $nt_lc );
- if ( $file_lc->allowInlineDisplay() ) {
- $dlink2 = $sk->makeImageLinkObj( $nt_lc, wfMsgExt( 'fileexists-thumb', 'parseinline' ),
- $nt_lc->getText(), $align, array(), false, true );
- } elseif ( !$file_lc->allowInlineDisplay() && $file_lc->isSafeFile() ) {
- $icon = $file_lc->iconThumb();
- $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
- $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' . $dlink . '</div>';
- } else {
- $dlink2 = '';
- }
+ /**
+ * Provides output to the user for a result of UploadBase::verifyUpload
+ *
+ * @param array $details Result of UploadBase::verifyUpload
+ */
+ protected function processVerificationError( $details ) {
+ global $wgFileExtensions, $wgLang;
- $warning .= '<li>' .
- wfMsgExt( 'fileexists-extension', 'parsemag',
- $file->getTitle()->getPrefixedText(), $dlink ) .
- '</li>' . $dlink2;
+ switch( $details['status'] ) {
- } elseif ( ( substr( $partname , 3, 3 ) == 'px-' || substr( $partname , 2, 3 ) == 'px-' )
- && ereg( "[0-9]{2}" , substr( $partname , 0, 2) ) )
- {
- # Check for filenames like 50px- or 180px-, these are mostly thumbnails
- $nt_thb = Title::newFromText( substr( $partname , strpos( $partname , '-' ) +1 ) . '.' . $rawExtension );
- $file_thb = wfLocalFile( $nt_thb );
- if ($file_thb->exists() ) {
- # Check if an image without leading '180px-' (or similiar) exists
- $dlink = $sk->makeKnownLinkObj( $nt_thb);
- if ( $file_thb->allowInlineDisplay() ) {
- $dlink2 = $sk->makeImageLinkObj( $nt_thb,
- wfMsgExt( 'fileexists-thumb', 'parseinline' ),
- $nt_thb->getText(), $align, array(), false, true );
- } elseif ( !$file_thb->allowInlineDisplay() && $file_thb->isSafeFile() ) {
- $icon = $file_thb->iconThumb();
- $dlink2 = '<div style="float:' . $align . '" id="mw-media-icon">' .
- $icon->toHtml( array( 'desc-link' => true ) ) . '<br />' .
- $dlink . '</div>';
+ /** Statuses that only require name changing **/
+ case UploadBase::MIN_LENGTH_PARTNAME:
+ $this->showRecoverableUploadError( wfMsgHtml( 'minlength1' ) );
+ break;
+ case UploadBase::ILLEGAL_FILENAME:
+ $this->showRecoverableUploadError( wfMsgExt( 'illegalfilename',
+ 'parseinline', $details['filtered'] ) );
+ break;
+ case UploadBase::OVERWRITE_EXISTING_FILE:
+ $this->showRecoverableUploadError( wfMsgExt( $details['overwrite'],
+ 'parseinline' ) );
+ break;
+ case UploadBase::FILETYPE_MISSING:
+ $this->showRecoverableUploadError( wfMsgExt( 'filetype-missing',
+ 'parseinline' ) );
+ break;
+
+ /** Statuses that require reuploading **/
+ case UploadBase::EMPTY_FILE:
+ $this->showUploadForm( $this->getUploadForm( wfMsgHtml( 'emptyfile' ) ) );
+ 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 ) )
+ )
+ );
+ break;
+ case UploadBase::VERIFICATION_ERROR:
+ unset( $details['status'] );
+ $code = array_shift( $details['details'] );
+ $this->showUploadError( wfMsgExt( $code, 'parseinline', $details['details'] ) );
+ break;
+ case UploadBase::HOOK_ABORTED:
+ if ( is_array( $details['error'] ) ) { # allow hooks to return error details in an array
+ $args = $details['error'];
+ $error = array_shift( $args );
} else {
- $dlink2 = '';
+ $error = $details['error'];
+ $args = null;
}
- $warning .= '<li>' . wfMsgExt( 'fileexists-thumbnail-yes', 'parsemag', $dlink ) .
- '</li>' . $dlink2;
- } else {
- # Image w/o '180px-' does not exists, but we do not like these filenames
- $warning .= '<li>' . wfMsgExt( 'file-thumbnail-no', 'parseinline' ,
- substr( $partname , 0, strpos( $partname , '-' ) +1 ) ) . '</li>';
- }
+ $this->showUploadError( wfMsgExt( $error, 'parseinline', $args ) );
+ break;
+ default:
+ throw new MWException( __METHOD__ . ": Unknown value `{$details['status']}`" );
}
+ }
- $filenamePrefixBlacklist = self::getFilenamePrefixBlacklist();
- # Do the match
- foreach( $filenamePrefixBlacklist as $prefix ) {
- if ( substr( $partname, 0, strlen( $prefix ) ) == $prefix ) {
- $warning .= '<li>' . wfMsgExt( 'filename-bad-prefix', 'parseinline', $prefix ) . '</li>';
- break;
- }
+ /**
+ * Remove a temporarily kept file stashed by saveTempUploadedFile().
+ * @access private
+ * @return success
+ */
+ protected function unsaveUploadedFile() {
+ global $wgOut;
+ if ( !( $this->mUpload instanceof UploadFromStash ) )
+ return true;
+ $success = $this->mUpload->unsaveUploadedFile();
+ if ( ! $success ) {
+ $wgOut->showFileDeleteError( $this->mUpload->getTempPath() );
+ return false;
+ } else {
+ return true;
}
+ }
+
+ /*** Functions for formatting warnings ***/
+
+ /**
+ * Formats a result of UploadBase::getExistsWarning as HTML
+ * This check is static and can be done pre-upload via AJAX
+ *
+ * @param array $exists The result of UploadBase::getExistsWarning
+ * @return string Empty string if there is no warning or an HTML fragment
+ */
+ public static function getExistsWarning( $exists ) {
+ global $wgUser, $wgContLang;
+
+ if ( !$exists )
+ return '';
- if ( $file->wasDeleted() && !$file->exists() ) {
+ $file = $exists['file'];
+ $filename = $file->getTitle()->getPrefixedText();
+ $warning = '';
+
+ $sk = $wgUser->getSkin();
+
+ if( $exists['warning'] == 'exists' ) {
+ // Exact match
+ $warning = wfMsgExt( 'fileexists', 'parseinline', $filename );
+ } elseif( $exists['warning'] == 'page-exists' ) {
+ // Page exists but file does not
+ $warning = wfMsgExt( 'filepageexists', 'parseinline', $filename );
+ } elseif ( $exists['warning'] == 'exists-normalized' ) {
+ $warning = wfMsgExt( 'fileexists-extension', 'parseinline', $filename,
+ $exists['normalizedFile']->getTitle()->getPrefixedText() );
+ } elseif ( $exists['warning'] == 'thumb' ) {
+ // Swapped argument order compared with other messages for backwards compatibility
+ $warning = wfMsgExt( 'fileexists-thumbnail-yes', 'parseinline',
+ $exists['thumbFile']->getTitle()->getPrefixedText(), $filename );
+ } elseif ( $exists['warning'] == 'thumb-name' ) {
+ // Image w/o '180px-' does not exists, but we do not like these filenames
+ $name = $file->getName();
+ $badPart = substr( $name, 0, strpos( $name, '-' ) + 1 );
+ $warning = wfMsgExt( 'file-thumbnail-no', 'parseinline', $badPart );
+ } elseif ( $exists['warning'] == 'bad-prefix' ) {
+ $warning = wfMsgExt( 'filename-bad-prefix', 'parseinline', $exists['prefix'] );
+ } elseif ( $exists['warning'] == 'was-deleted' ) {
# If the file existed before and was deleted, warn the user of this
- # Don't bother doing so if the file exists now, however
$ltitle = SpecialPage::getTitleFor( 'Log' );
- $llink = $sk->makeKnownLinkObj( $ltitle, wfMsgHtml( 'deletionlog' ),
- 'type=delete&page=' . $file->getTitle()->getPrefixedUrl() );
- $warning .= '<li>' . wfMsgWikiHtml( 'filewasdeleted', $llink ) . '</li>';
+ $llink = $sk->linkKnown(
+ $ltitle,
+ wfMsgHtml( 'deletionlog' ),
+ array(),
+ array(
+ 'type' => 'delete',
+ 'page' => $filename
+ )
+ );
+ $warning = wfMsgWikiHtml( 'filewasdeleted', $llink );
}
+
return $warning;
}
@@ -721,7 +641,7 @@ class UploadForm {
* @param string local filename, e.g. 'file exists', 'non-descriptive filename'
* @return array list of warning messages
*/
- static function ajaxGetExistsWarning( $filename ) {
+ public static function ajaxGetExistsWarning( $filename ) {
$file = wfFindFile( $filename );
if( !$file ) {
// Force local file so we have an object to do further checks against
@@ -730,297 +650,181 @@ class UploadForm {
}
$s = '&nbsp;';
if ( $file ) {
- $warning = self::getExistsWarning( $file );
+ $exists = UploadBase::getExistsWarning( $file );
+ $warning = self::getExistsWarning( $exists );
if ( $warning !== '' ) {
- $s = "<ul>$warning</ul>";
+ $s = "<div>$warning</div>";
}
}
return $s;
}
/**
- * Render a preview of a given license for the AJAX preview on upload
- *
- * @param string $license
- * @return string
- */
- public static function ajaxGetLicensePreview( $license ) {
- global $wgParser, $wgUser;
- $text = '{{' . $license . '}}';
- $title = Title::makeTitle( NS_FILE, 'Sample.jpg' );
- $options = ParserOptions::newFromUser( $wgUser );
-
- // Expand subst: first, then live templates...
- $text = $wgParser->preSaveTransform( $text, $title, $wgUser, $options );
- $output = $wgParser->parse( $text, $title, $options );
-
- return $output->getText();
- }
-
- /**
- * Check for duplicate files and throw up a warning before the upload
- * completes.
+ * Construct a warning and a gallery from an array of duplicate files.
*/
- function getDupeWarning( $tempfile, $extension, $destinationTitle ) {
- $hash = File::sha1Base36( $tempfile );
- $dupes = RepoGroup::singleton()->findBySha1( $hash );
- $archivedImage = new ArchivedFile( null, 0, $hash.".$extension" );
+ public static function getDupeWarning( $dupes ) {
if( $dupes ) {
global $wgOut;
$msg = "<gallery>";
foreach( $dupes as $file ) {
$title = $file->getTitle();
- # Don't throw the warning when the titles are the same, it's a reupload
- # and highly redundant.
- if ( !$title->equals( $destinationTitle ) || !$this->mForReUpload ) {
- $msg .= $title->getPrefixedText() .
- "|" . $title->getText() . "\n";
- }
+ $msg .= $title->getPrefixedText() .
+ "|" . $title->getText() . "\n";
}
$msg .= "</gallery>";
return "<li>" .
wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) .
$wgOut->parse( $msg ) .
"</li>\n";
- } elseif ( $archivedImage->getID() > 0 ) {
- global $wgOut;
- $name = Title::makeTitle( NS_FILE, $archivedImage->getName() )->getPrefixedText();
- return Xml::tags( 'li', null, wfMsgExt( 'file-deleted-duplicate', array( 'parseinline' ), array( $name ) ) );
} else {
return '';
}
}
- /**
- * Get a list of blacklisted filename prefixes from [[MediaWiki:filename-prefix-blacklist]]
- *
- * @return array list of prefixes
- */
- public static function getFilenamePrefixBlacklist() {
- $blacklist = array();
- $message = wfMsgForContent( 'filename-prefix-blacklist' );
- if( $message && !( wfEmptyMsg( 'filename-prefix-blacklist', $message ) || $message == '-' ) ) {
- $lines = explode( "\n", $message );
- foreach( $lines as $line ) {
- // Remove comment lines
- $comment = substr( trim( $line ), 0, 1 );
- if ( $comment == '#' || $comment == '' ) {
- continue;
- }
- // Remove additional comments after a prefix
- $comment = strpos( $line, '#' );
- if ( $comment > 0 ) {
- $line = substr( $line, 0, $comment-1 );
- }
- $blacklist[] = trim( $line );
- }
- }
- return $blacklist;
- }
+}
- /**
- * Stash a file in a temporary directory for later processing
- * after the user has confirmed it.
- *
- * If the user doesn't explicitly cancel or accept, these files
- * can accumulate in the temp directory.
- *
- * @param string $saveName - the destination filename
- * @param string $tempName - the source temporary file to save
- * @return string - full path the stashed file, or false on failure
- * @access private
- */
- function saveTempUploadedFile( $saveName, $tempName ) {
- global $wgOut;
- $repo = RepoGroup::singleton()->getLocalRepo();
- $status = $repo->storeTemp( $saveName, $tempName );
- if ( !$status->isGood() ) {
- $this->showError( $status->getWikiText() );
- return false;
- } else {
- return $status->value;
- }
- }
+/**
+ * Sub class of HTMLForm that provides the form section of SpecialUpload
+ */
+class UploadForm extends HTMLForm {
+ protected $mWatch;
+ protected $mForReUpload;
+ protected $mSessionKey;
+ protected $mHideIgnoreWarning;
+ protected $mDestWarningAck;
+ protected $mDestFile;
+
+ protected $mTextTop;
+ protected $mTextAfterSummary;
+
+ protected $mSourceIds;
- /**
- * Stash a file in a temporary directory for later processing,
- * and save the necessary descriptive info into the session.
- * Returns a key value which will be passed through a form
- * to pick up the path info on a later invocation.
- *
- * @return int
- * @access private
- */
- function stashSession() {
- $stash = $this->saveTempUploadedFile( $this->mDestName, $this->mTempPath );
+ public function __construct( $options = array() ) {
+ global $wgLang;
- if( !$stash ) {
- # Couldn't save the file.
- return false;
- }
+ $this->mWatch = !empty( $options['watch'] );
+ $this->mForReUpload = !empty( $options['forreupload'] );
+ $this->mSessionKey = isset( $options['sessionkey'] )
+ ? $options['sessionkey'] : '';
+ $this->mHideIgnoreWarning = !empty( $options['hideignorewarning'] );
+ $this->mDestWarningAck = !empty( $options['destwarningack'] );
+
+ $this->mTextTop = $options['texttop'];
+ $this->mTextAfterSummary = $options['textaftersummary'];
+ $this->mDestFile = isset( $options['destfile'] ) ? $options['destfile'] : '';
- $key = mt_rand( 0, 0x7fffffff );
- $_SESSION['wsUploadData'][$key] = array(
- 'mTempPath' => $stash,
- 'mFileSize' => $this->mFileSize,
- 'mSrcName' => $this->mSrcName,
- 'mFileProps' => $this->mFileProps,
- 'version' => self::SESSION_VERSION,
- );
- return $key;
- }
+ $sourceDescriptor = $this->getSourceSection();
+ $descriptor = $sourceDescriptor
+ + $this->getDescriptionSection()
+ + $this->getOptionsSection();
- /**
- * Remove a temporarily kept file stashed by saveTempUploadedFile().
- * @access private
- * @return success
- */
- function unsaveUploadedFile() {
- global $wgOut;
- if( !$this->mTempPath ) return true; // nothing to delete
- $repo = RepoGroup::singleton()->getLocalRepo();
- $success = $repo->freeTemp( $this->mTempPath );
- if ( ! $success ) {
- $wgOut->showFileDeleteError( $this->mTempPath );
- return false;
- } else {
- return true;
- }
- }
+ wfRunHooks( 'UploadFormInitDescriptor', array( &$descriptor ) );
+ parent::__construct( $descriptor, 'upload' );
- /* -------------------------------------------------------------- */
+ # Set some form properties
+ $this->setSubmitText( wfMsg( 'uploadbtn' ) );
+ $this->setSubmitName( 'wpUpload' );
+ $this->setSubmitTooltip( 'upload' );
+ $this->setId( 'mw-upload-form' );
+
+ # Build a list of IDs for javascript insertion
+ $this->mSourceIds = array();
+ foreach ( $sourceDescriptor as $key => $field ) {
+ if ( !empty( $field['id'] ) )
+ $this->mSourceIds[] = $field['id'];
+ }
- /**
- * @param string $error as HTML
- * @access private
- */
- function uploadError( $error ) {
- global $wgOut;
- $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
- $wgOut->addHTML( '<span class="error">' . $error . '</span>' );
}
/**
- * There's something wrong with this file, not enough to reject it
- * totally but we require manual intervention to save it for real.
- * Stash it away, then present a form asking to confirm or cancel.
- *
- * @param string $warning as HTML
- * @access private
+ * Get the descriptor of the fieldset that contains the file source
+ * selection. The section is 'source'
+ *
+ * @return array Descriptor array
*/
- function uploadWarning( $warning ) {
- global $wgOut;
- global $wgUseCopyrightUpload;
-
- $this->mSessionKey = $this->stashSession();
- if( !$this->mSessionKey ) {
- # Couldn't save file; an error has been displayed so let's go.
- return;
+ protected function getSourceSection() {
+ global $wgLang, $wgUser, $wgRequest;
+
+ if ( $this->mSessionKey ) {
+ return array(
+ 'wpSessionKey' => array(
+ 'type' => 'hidden',
+ 'default' => $this->mSessionKey,
+ ),
+ 'wpSourceType' => array(
+ 'type' => 'hidden',
+ 'default' => 'Stash',
+ ),
+ );
}
- $wgOut->addHTML( '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" );
- $wgOut->addHTML( '<ul class="warning">' . $warning . "</ul>\n" );
+ $canUploadByUrl = UploadFromUrl::isEnabled() && $wgUser->isAllowed( 'upload_by_url' );
+ $radio = $canUploadByUrl;
+ $selectedSourceType = strtolower( $wgRequest->getText( 'wpSourceType', 'File' ) );
- $titleObj = SpecialPage::getTitleFor( 'Upload' );
-
- if ( $wgUseCopyrightUpload ) {
- $copyright = Xml::hidden( 'wpUploadCopyStatus', $this->mCopyrightStatus ) . "\n" .
- Xml::hidden( 'wpUploadSource', $this->mCopyrightSource ) . "\n";
- } else {
- $copyright = '';
+ $descriptor = array();
+ if ( $this->mTextTop ) {
+ $descriptor['UploadFormTextTop'] = array(
+ 'type' => 'info',
+ 'section' => 'source',
+ 'default' => $this->mTextTop,
+ 'raw' => true,
+ );
+ }
+
+ $descriptor['UploadFile'] = array(
+ 'class' => 'UploadSourceField',
+ 'section' => 'source',
+ 'type' => 'file',
+ 'id' => 'wpUploadFile',
+ 'label-message' => 'sourcefilename',
+ 'upload-type' => 'File',
+ 'radio' => &$radio,
+ 'help' => wfMsgExt( 'upload-maxfilesize',
+ array( 'parseinline', 'escapenoentities' ),
+ $wgLang->formatSize(
+ wfShorthandToInteger( ini_get( 'upload_max_filesize' ) )
+ )
+ ) . ' ' . wfMsgHtml( 'upload_source_file' ),
+ 'checked' => $selectedSourceType == 'file',
+ );
+ if ( $canUploadByUrl ) {
+ global $wgMaxUploadSize;
+ $descriptor['UploadFileURL'] = array(
+ 'class' => 'UploadSourceField',
+ 'section' => 'source',
+ 'id' => 'wpUploadFileURL',
+ 'label-message' => 'sourceurl',
+ 'upload-type' => 'url',
+ 'radio' => &$radio,
+ 'help' => wfMsgExt( 'upload-maxfilesize',
+ array( 'parseinline', 'escapenoentities' ),
+ $wgLang->formatSize( $wgMaxUploadSize )
+ ) . ' ' . wfMsgHtml( 'upload_source_url' ),
+ 'checked' => $selectedSourceType == 'url',
+ );
}
+ wfRunHooks( 'UploadFormSourceDescriptors', array( &$descriptor, &$radio, $selectedSourceType ) );
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ),
- 'enctype' => 'multipart/form-data', 'id' => 'uploadwarning' ) ) . "\n" .
- Xml::hidden( 'wpIgnoreWarning', '1' ) . "\n" .
- Xml::hidden( 'wpSessionKey', $this->mSessionKey ) . "\n" .
- Xml::hidden( 'wpUploadDescription', $this->mComment ) . "\n" .
- Xml::hidden( 'wpLicense', $this->mLicense ) . "\n" .
- Xml::hidden( 'wpDestFile', $this->mDesiredDestName ) . "\n" .
- Xml::hidden( 'wpWatchthis', $this->mWatchthis ) . "\n" .
- "{$copyright}<br />" .
- Xml::submitButton( wfMsg( 'ignorewarning' ), array ( 'name' => 'wpUpload', 'id' => 'wpUpload', 'checked' => 'checked' ) ) . ' ' .
- Xml::submitButton( wfMsg( 'reuploaddesc' ), array ( 'name' => 'wpReUpload', 'id' => 'wpReUpload' ) ) .
- Xml::closeElement( 'form' ) . "\n"
+ $descriptor['Extensions'] = array(
+ 'type' => 'info',
+ 'section' => 'source',
+ 'default' => $this->getExtensionsMessage(),
+ 'raw' => true,
);
+ return $descriptor;
}
+
/**
- * Displays the main upload form, optionally with a highlighted
- * error message up at the top.
- *
- * @param string $msg as HTML
- * @access private
+ * Get the messages indicating which extensions are preferred and prohibitted.
+ *
+ * @return string HTML string containing the message
*/
- function mainUploadForm( $msg='' ) {
- global $wgOut, $wgUser, $wgLang, $wgMaxUploadSize;
- global $wgUseCopyrightUpload, $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview;
- global $wgRequest, $wgAllowCopyUploads;
- global $wgStylePath, $wgStyleVersion;
-
- $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
- $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview;
-
- $adc = wfBoolToStr( $useAjaxDestCheck );
- $alp = wfBoolToStr( $useAjaxLicensePreview );
- $autofill = wfBoolToStr( $this->mDesiredDestName == '' );
-
- $wgOut->addScript( "<script type=\"text/javascript\">
-wgAjaxUploadDestCheck = {$adc};
-wgAjaxLicensePreview = {$alp};
-wgUploadAutoFill = {$autofill};
-</script>" );
- $wgOut->addScriptFile( 'upload.js' );
- $wgOut->addScriptFile( 'edit.js' ); // For <charinsert> support
-
- if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) )
- {
- wfDebug( "Hook 'UploadForm:initial' broke output of the upload form\n" );
- return false;
- }
-
- if( $this->mDesiredDestName ) {
- $title = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName );
- // Show a subtitle link to deleted revisions (to sysops et al only)
- if( $title instanceof Title && ( $count = $title->isDeleted() ) > 0 && $wgUser->isAllowed( 'deletedhistory' ) ) {
- $link = wfMsgExt(
- $wgUser->isAllowed( 'delete' ) ? 'thisisdeleted' : 'viewdeleted',
- array( 'parse', 'replaceafter' ),
- $wgUser->getSkin()->makeKnownLinkObj(
- SpecialPage::getTitleFor( 'Undelete', $title->getPrefixedText() ),
- wfMsgExt( 'restorelink', array( 'parsemag', 'escape' ), $count )
- )
- );
- $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() );
- }
- }
-
- $cols = intval($wgUser->getOption( 'cols' ));
-
- if( $wgUser->getOption( 'editwidth' ) ) {
- $width = " style=\"width:100%\"";
- } else {
- $width = '';
- }
-
- if ( '' != $msg ) {
- $sub = wfMsgHtml( 'uploaderror' );
- $wgOut->addHTML( "<h2>{$sub}</h2>\n" .
- "<span class='error'>{$msg}</span>\n" );
- }
- $wgOut->addHTML( '<div id="uploadtext">' );
- $wgOut->addWikiMsg( 'uploadtext', $this->mDesiredDestName );
- $wgOut->addHTML( "</div>\n" );
-
+ protected function getExtensionsMessage() {
# Print a list of allowed file extensions, if so configured. We ignore
# MIME type here, it's incomprehensible to most people and too long.
- global $wgCheckFileExtensions, $wgStrictFileExtensions,
+ global $wgLang, $wgCheckFileExtensions, $wgStrictFileExtensions,
$wgFileExtensions, $wgFileBlacklist;
$allowedExtensions = '';
@@ -1045,805 +849,211 @@ wgUploadAutoFill = {$autofill};
# Everything is permitted.
$extensionsList = '';
}
+ return $extensionsList;
+ }
- # Get the maximum file size from php.ini as $wgMaxUploadSize works for uploads from URL via CURL only
- # See http://www.php.net/manual/en/ini.core.php#ini.upload-max-filesize for possible values of upload_max_filesize
- $val = trim( ini_get( 'upload_max_filesize' ) );
- $last = strtoupper( ( substr( $val, -1 ) ) );
- switch( $last ) {
- case 'G':
- $val2 = substr( $val, 0, -1 ) * 1024 * 1024 * 1024;
- break;
- case 'M':
- $val2 = substr( $val, 0, -1 ) * 1024 * 1024;
- break;
- case 'K':
- $val2 = substr( $val, 0, -1 ) * 1024;
- break;
- default:
- $val2 = $val;
- }
- $val2 = $wgAllowCopyUploads ? min( $wgMaxUploadSize, $val2 ) : $val2;
- $maxUploadSize = '<div id="mw-upload-maxfilesize">' .
- wfMsgExt( 'upload-maxfilesize', array( 'parseinline', 'escapenoentities' ),
- $wgLang->formatSize( $val2 ) ) .
- "</div>\n";
-
- $sourcefilename = wfMsgExt( 'sourcefilename', array( 'parseinline', 'escapenoentities' ) );
- $destfilename = wfMsgExt( 'destfilename', array( 'parseinline', 'escapenoentities' ) );
-
- $msg = $this->mForReUpload ? 'filereuploadsummary' : 'fileuploadsummary';
- $summary = wfMsgExt( $msg, 'parseinline' );
-
- $licenses = new Licenses();
- $license = wfMsgExt( 'license', array( 'parseinline' ) );
- $nolicense = wfMsgHtml( 'nolicense' );
- $licenseshtml = $licenses->getHtml();
-
- $ulb = wfMsgHtml( 'uploadbtn' );
-
-
- $titleObj = SpecialPage::getTitleFor( 'Upload' );
-
- $encDestName = htmlspecialchars( $this->mDesiredDestName );
-
- $watchChecked = $this->watchCheck() ? 'checked="checked"' : '';
- # Re-uploads should not need "file exist already" warnings
- $warningChecked = ($this->mIgnoreWarning || $this->mForReUpload) ? 'checked="checked"' : '';
-
- // Prepare form for upload or upload/copy
- if( $wgAllowCopyUploads && $wgUser->isAllowed( 'upload_by_url' ) ) {
- $filename_form =
- "<input type='radio' id='wpSourceTypeFile' name='wpSourceType' value='file' " .
- "onchange='toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\")' checked='checked' />" .
- "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
- "onfocus='" .
- "toggle_element_activation(\"wpUploadFileURL\",\"wpUploadFile\");" .
- "toggle_element_check(\"wpSourceTypeFile\",\"wpSourceTypeURL\")' " .
- "onchange='fillDestFilename(\"wpUploadFile\")' size='60' />" .
- wfMsgHTML( 'upload_source_file' ) . "<br/>" .
- "<input type='radio' id='wpSourceTypeURL' name='wpSourceType' value='web' " .
- "onchange='toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\")' />" .
- "<input tabindex='1' type='text' name='wpUploadFileURL' id='wpUploadFileURL' " .
- "onfocus='" .
- "toggle_element_activation(\"wpUploadFile\",\"wpUploadFileURL\");" .
- "toggle_element_check(\"wpSourceTypeURL\",\"wpSourceTypeFile\")' " .
- "onchange='fillDestFilename(\"wpUploadFileURL\")' size='60' disabled='disabled' />" .
- wfMsgHtml( 'upload_source_url' ) ;
- } else {
- $filename_form =
- "<input tabindex='1' type='file' name='wpUploadFile' id='wpUploadFile' " .
- ($this->mDesiredDestName?"":"onchange='fillDestFilename(\"wpUploadFile\")' ") .
- "size='60' />" .
- "<input type='hidden' name='wpSourceType' value='file' />" ;
- }
- if ( $useAjaxDestCheck ) {
- $warningRow = "<tr><td colspan='2' id='wpDestFile-warning'>&nbsp;</td></tr>";
- $destOnkeyup = 'onkeyup="wgUploadWarningObj.keypress();"';
- } else {
- $warningRow = '';
- $destOnkeyup = '';
- }
- $encComment = htmlspecialchars( $this->mComment );
-
+ /**
+ * Get the descriptor of the fieldset that contains the file description
+ * input. The section is 'description'
+ *
+ * @return array Descriptor array
+ */
+ protected function getDescriptionSection() {
+ global $wgUser, $wgOut;
- $wgOut->addHTML(
- Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL(),
- 'enctype' => 'multipart/form-data', 'id' => 'mw-upload-form' ) ) .
- Xml::openElement( 'fieldset' ) .
- Xml::element( 'legend', null, wfMsg( 'upload' ) ) .
- Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-upload-table' ) ) .
- "<tr>
- {$this->uploadFormTextTop}
- <td class='mw-label'>
- <label for='wpUploadFile'>{$sourcefilename}</label>
- </td>
- <td class='mw-input'>
- {$filename_form}
- </td>
- </tr>
- <tr>
- <td></td>
- <td>
- {$maxUploadSize}
- {$extensionsList}
- </td>
- </tr>
- <tr>
- <td class='mw-label'>
- <label for='wpDestFile'>{$destfilename}</label>
- </td>
- <td class='mw-input'>"
+ $cols = intval( $wgUser->getOption( 'cols' ) );
+ if( $wgUser->getOption( 'editwidth' ) ) {
+ $wgOut->addInlineStyle( '#mw-htmlform-description { width: 100%; }' );
+ }
+
+ $descriptor = array(
+ 'DestFile' => array(
+ 'type' => 'text',
+ 'section' => 'description',
+ 'id' => 'wpDestFile',
+ 'label-message' => 'destfilename',
+ 'size' => 60,
+ 'default' => $this->mDestFile,
+ # FIXME: hack to work around poor handling of the 'default' option in HTMLForm
+ 'nodata' => strval( $this->mDestFile ) !== '',
+ ),
+ 'UploadDescription' => array(
+ 'type' => 'textarea',
+ 'section' => 'description',
+ 'id' => 'wpUploadDescription',
+ 'label-message' => $this->mForReUpload
+ ? 'filereuploadsummary'
+ : 'fileuploadsummary',
+ 'cols' => $cols,
+ 'rows' => 8,
+ )
);
- if( $this->mForReUpload ) {
- $wgOut->addHTML(
- Xml::hidden( 'wpDestFile', $this->mDesiredDestName, array('id'=>'wpDestFile','tabindex'=>2) ) .
- "<tt>" .
- $encDestName .
- "</tt>"
- );
- }
- else {
- $wgOut->addHTML(
- "<input tabindex='2' type='text' name='wpDestFile' id='wpDestFile' size='60'
- value=\"{$encDestName}\" onchange='toggleFilenameFiller()' $destOnkeyup />"
+ if ( $this->mTextAfterSummary ) {
+ $descriptor['UploadFormTextAfterSummary'] = array(
+ 'type' => 'info',
+ 'section' => 'description',
+ 'default' => $this->mTextAfterSummary,
+ 'raw' => true,
);
}
-
- $wgOut->addHTML(
- "</td>
- </tr>
- <tr>
- <td class='mw-label'>
- <label for='wpUploadDescription'>{$summary}</label>
- </td>
- <td class='mw-input'>
- <textarea tabindex='3' name='wpUploadDescription' id='wpUploadDescription' rows='6'
- cols='{$cols}'{$width}>$encComment</textarea>
- {$this->uploadFormTextAfterSummary}
- </td>
- </tr>
- <tr>"
+ $descriptor += array(
+ 'EditTools' => array(
+ 'type' => 'edittools',
+ 'section' => 'description',
+ ),
+ 'License' => array(
+ 'type' => 'select',
+ 'class' => 'Licenses',
+ 'section' => 'description',
+ 'id' => 'wpLicense',
+ 'label-message' => 'license',
+ ),
);
- # Re-uploads should not need license info
- if ( !$this->mForReUpload && $licenseshtml != '' ) {
- global $wgStylePath;
- $wgOut->addHTML( "
- <td class='mw-label'>
- <label for='wpLicense'>$license</label>
- </td>
- <td class='mw-input'>
- <select name='wpLicense' id='wpLicense' tabindex='4'
- onchange='licenseSelectorCheck()'>
- <option value=''>$nolicense</option>
- $licenseshtml
- </select>
- </td>
- </tr>
- <tr>"
- );
- if( $useAjaxLicensePreview ) {
- $wgOut->addHTML( "
- <td></td>
- <td id=\"mw-license-preview\"></td>
- </tr>
- <tr>"
- );
- }
- }
+ if ( $this->mForReUpload )
+ $descriptor['DestFile']['readonly'] = true;
- if ( !$this->mForReUpload && $wgUseCopyrightUpload ) {
- $filestatus = wfMsgExt( 'filestatus', 'escapenoentities' );
- $copystatus = htmlspecialchars( $this->mCopyrightStatus );
- $filesource = wfMsgExt( 'filesource', 'escapenoentities' );
- $uploadsource = htmlspecialchars( $this->mCopyrightSource );
-
- $wgOut->addHTML( "
- <td class='mw-label' style='white-space: nowrap;'>
- <label for='wpUploadCopyStatus'>$filestatus</label></td>
- <td class='mw-input'>
- <input tabindex='5' type='text' name='wpUploadCopyStatus' id='wpUploadCopyStatus'
- value=\"$copystatus\" size='60' />
- </td>
- </tr>
- <tr>
- <td class='mw-label'>
- <label for='wpUploadCopyStatus'>$filesource</label>
- </td>
- <td class='mw-input'>
- <input tabindex='6' type='text' name='wpUploadSource' id='wpUploadCopyStatus'
- value=\"$uploadsource\" size='60' />
- </td>
- </tr>
- <tr>"
+ global $wgUseCopyrightUpload;
+ if ( $wgUseCopyrightUpload ) {
+ $descriptor['UploadCopyStatus'] = array(
+ 'type' => 'text',
+ 'section' => 'description',
+ 'id' => 'wpUploadCopyStatus',
+ 'label-message' => 'filestatus',
+ );
+ $descriptor['UploadSource'] = array(
+ 'type' => 'text',
+ 'section' => 'description',
+ 'id' => 'wpUploadSource',
+ 'label-message' => 'filesource',
);
}
- $wgOut->addHTML( "
- <td></td>
- <td>
- <input tabindex='7' type='checkbox' name='wpWatchthis' id='wpWatchthis' $watchChecked value='true' />
- <label for='wpWatchthis'>" . wfMsgHtml( 'watchthisupload' ) . "</label>
- <input tabindex='8' type='checkbox' name='wpIgnoreWarning' id='wpIgnoreWarning' value='true' $warningChecked />
- <label for='wpIgnoreWarning'>" . wfMsgHtml( 'ignorewarnings' ) . "</label>
- </td>
- </tr>
- $warningRow
- <tr>
- <td></td>
- <td class='mw-input'>
- <input tabindex='9' type='submit' name='wpUpload' value=\"{$ulb}\"" .
- $wgUser->getSkin()->tooltipAndAccesskey( 'upload' ) . " />
- </td>
- </tr>
- <tr>
- <td></td>
- <td class='mw-input'>"
- );
- $wgOut->addHTML( '<div class="mw-editTools">' );
- $wgOut->addWikiMsgArray( 'edittools', array(), array( 'content' ) );
- $wgOut->addHTML( '</div>' );
- $wgOut->addHTML( "
- </td>
- </tr>" .
- Xml::closeElement( 'table' ) .
- Xml::hidden( 'wpDestFileWarningAck', '', array( 'id' => 'wpDestFileWarningAck' ) ) .
- Xml::hidden( 'wpForReUpload', $this->mForReUpload, array( 'id' => 'wpForReUpload' ) ) .
- Xml::closeElement( 'fieldset' ) .
- Xml::closeElement( 'form' )
- );
- $uploadfooter = wfMsgNoTrans( 'uploadfooter' );
- if( $uploadfooter != '-' && !wfEmptyMsg( 'uploadfooter', $uploadfooter ) ){
- $wgOut->addWikiText( '<div id="mw-upload-footer-message">' . $uploadfooter . '</div>' );
- }
- }
-
- /* -------------------------------------------------------------- */
-
- /**
- * See if we should check the 'watch this page' checkbox on the form
- * based on the user's preferences and whether we're being asked
- * to create a new file or update an existing one.
- *
- * In the case where 'watch edits' is off but 'watch creations' is on,
- * we'll leave the box unchecked.
- *
- * Note that the page target can be changed *on the form*, so our check
- * state can get out of sync.
- */
- function watchCheck() {
- global $wgUser;
- if( $wgUser->getOption( 'watchdefault' ) ) {
- // Watch all edits!
- return true;
- }
-
- $local = wfLocalFile( $this->mDesiredDestName );
- if( $local && $local->exists() ) {
- // We're uploading a new version of an existing file.
- // No creation, so don't watch it if we're not already.
- return $local->getTitle()->userIsWatching();
- } else {
- // New page should get watched if that's our option.
- return $wgUser->getOption( 'watchcreations' );
- }
- }
-
- /**
- * Split a file into a base name and all dot-delimited 'extensions'
- * on the end. Some web server configurations will fall back to
- * earlier pseudo-'extensions' to determine type and execute
- * scripts, so the blacklist needs to check them all.
- *
- * @return array
- */
- public function splitExtensions( $filename ) {
- $bits = explode( '.', $filename );
- $basename = array_shift( $bits );
- return array( $basename, $bits );
- }
-
- /**
- * Perform case-insensitive match against a list of file extensions.
- * Returns true if the extension is in the list.
- *
- * @param string $ext
- * @param array $list
- * @return bool
- */
- function checkFileExtension( $ext, $list ) {
- return in_array( strtolower( $ext ), $list );
- }
-
- /**
- * Perform case-insensitive match against a list of file extensions.
- * Returns true if any of the extensions are in the list.
- *
- * @param array $ext
- * @param array $list
- * @return bool
- */
- public function checkFileExtensionList( $ext, $list ) {
- foreach( $ext as $e ) {
- if( in_array( strtolower( $e ), $list ) ) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Verifies that it's ok to include the uploaded file
- *
- * @param string $tmpfile the full path of the temporary file to verify
- * @param string $extension The filename extension that the file is to be served with
- * @return mixed true of the file is verified, a WikiError object otherwise.
- */
- function verify( $tmpfile, $extension ) {
- #magically determine mime type
- $magic = MimeMagic::singleton();
- $mime = $magic->guessMimeType($tmpfile,false);
-
-
- #check mime type, if desired
- global $wgVerifyMimeType;
- if ($wgVerifyMimeType) {
- wfDebug ( "\n\nmime: <$mime> extension: <$extension>\n\n");
- #check mime type against file extension
- if( !self::verifyExtension( $mime, $extension ) ) {
- return new WikiErrorMsg( 'uploadcorrupt' );
- }
-
- #check mime type blacklist
- global $wgMimeTypeBlacklist;
- if( isset($wgMimeTypeBlacklist) && !is_null($wgMimeTypeBlacklist) ) {
- if ( $this->checkFileExtension( $mime, $wgMimeTypeBlacklist ) ) {
- return new WikiErrorMsg( 'filetype-badmime', htmlspecialchars( $mime ) );
- }
-
- # Check IE type
- $fp = fopen( $tmpfile, 'rb' );
- $chunk = fread( $fp, 256 );
- fclose( $fp );
- $extMime = $magic->guessTypesForExtension( $extension );
- $ieTypes = $magic->getIEMimeTypes( $tmpfile, $chunk, $extMime );
- foreach ( $ieTypes as $ieType ) {
- if ( $this->checkFileExtension( $ieType, $wgMimeTypeBlacklist ) ) {
- return new WikiErrorMsg( 'filetype-bad-ie-mime', $ieType );
- }
- }
- }
- }
-
- #check for htmlish code and javascript
- if( $this->detectScript ( $tmpfile, $mime, $extension ) ) {
- return new WikiErrorMsg( 'uploadscripted' );
- }
- if( $extension == 'svg' || $mime == 'image/svg+xml' ) {
- if( $this->detectScriptInSvg( $tmpfile ) ) {
- return new WikiErrorMsg( 'uploadscripted' );
- }
- }
-
- /**
- * Scan the uploaded file for viruses
- */
- $virus= $this->detectVirus($tmpfile);
- if ( $virus ) {
- return new WikiErrorMsg( 'uploadvirus', htmlspecialchars($virus) );
- }
-
- wfDebug( __METHOD__.": all clear; passing.\n" );
- return true;
+ return $descriptor;
}
/**
- * Checks if the mime type of the uploaded file matches the file extension.
- *
- * @param string $mime the mime type of the uploaded file
- * @param string $extension The filename extension that the file is to be served with
- * @return bool
+ * Get the descriptor of the fieldset that contains the upload options,
+ * such as "watch this file". The section is 'options'
+ *
+ * @return array Descriptor array
*/
- static function verifyExtension( $mime, $extension ) {
- $magic = MimeMagic::singleton();
-
- if ( ! $mime || $mime == 'unknown' || $mime == 'unknown/unknown' )
- if ( ! $magic->isRecognizableExtension( $extension ) ) {
- wfDebug( __METHOD__.": passing file with unknown detected mime type; " .
- "unrecognized extension '$extension', can't verify\n" );
- return true;
- } else {
- wfDebug( __METHOD__.": rejecting file with unknown detected mime type; ".
- "recognized extension '$extension', so probably invalid file\n" );
- return false;
- }
-
- $match= $magic->isMatchingExtension($extension,$mime);
-
- if ($match===NULL) {
- wfDebug( __METHOD__.": no file extension known for mime type $mime, passing file\n" );
- return true;
- } elseif ($match===true) {
- wfDebug( __METHOD__.": mime type $mime matches extension $extension, passing file\n" );
-
- #TODO: if it's a bitmap, make sure PHP or ImageMagic resp. can handle it!
- return true;
-
- } else {
- wfDebug( __METHOD__.": mime type $mime mismatches file extension $extension, rejecting file\n" );
- return false;
- }
- }
-
-
- /**
- * Heuristic for detecting files that *could* contain JavaScript instructions or
- * things that may look like HTML to a browser and are thus
- * potentially harmful. The present implementation will produce false positives in some situations.
- *
- * @param string $file Pathname to the temporary upload file
- * @param string $mime The mime type of the file
- * @param string $extension The extension of the file
- * @return bool true if the file contains something looking like embedded scripts
- */
- function detectScript($file, $mime, $extension) {
- global $wgAllowTitlesInSVG;
-
- #ugly hack: for text files, always look at the entire file.
- #For binarie field, just check the first K.
-
- if (strpos($mime,'text/')===0) $chunk = file_get_contents( $file );
- else {
- $fp = fopen( $file, 'rb' );
- $chunk = fread( $fp, 1024 );
- fclose( $fp );
- }
+ protected function getOptionsSection() {
+ global $wgUser, $wgOut;
- $chunk= strtolower( $chunk );
-
- if (!$chunk) return false;
-
- #decode from UTF-16 if needed (could be used for obfuscation).
- if (substr($chunk,0,2)=="\xfe\xff") $enc= "UTF-16BE";
- elseif (substr($chunk,0,2)=="\xff\xfe") $enc= "UTF-16LE";
- else $enc= NULL;
-
- if ($enc) $chunk= iconv($enc,"ASCII//IGNORE",$chunk);
-
- $chunk= trim($chunk);
-
- #FIXME: convert from UTF-16 if necessarry!
-
- wfDebug("SpecialUpload::detectScript: checking for embedded scripts and HTML stuff\n");
-
- #check for HTML doctype
- if (eregi("<!DOCTYPE *X?HTML",$chunk)) return true;
-
- /**
- * Internet Explorer for Windows performs some really stupid file type
- * autodetection which can cause it to interpret valid image files as HTML
- * and potentially execute JavaScript, creating a cross-site scripting
- * attack vectors.
- *
- * Apple's Safari browser also performs some unsafe file type autodetection
- * which can cause legitimate files to be interpreted as HTML if the
- * web server is not correctly configured to send the right content-type
- * (or if you're really uploading plain text and octet streams!)
- *
- * Returns true if IE is likely to mistake the given file for HTML.
- * Also returns true if Safari would mistake the given file for HTML
- * when served with a generic content-type.
- */
-
- $tags = array(
- '<a href',
- '<body',
- '<head',
- '<html', #also in safari
- '<img',
- '<pre',
- '<script', #also in safari
- '<table'
+ if( $wgUser->isLoggedIn() ) {
+ $descriptor = array(
+ 'Watchthis' => array(
+ 'type' => 'check',
+ 'id' => 'wpWatchthis',
+ 'label-message' => 'watchthisupload',
+ 'section' => 'options',
+ 'default' => $wgUser->getOption( 'watchcreations' ),
+ )
);
- if( ! $wgAllowTitlesInSVG && $extension !== 'svg' && $mime !== 'image/svg' ) {
- $tags[] = '<title';
}
-
- foreach( $tags as $tag ) {
- if( false !== strpos( $chunk, $tag ) ) {
- return true;
- }
+ if( !$this->mHideIgnoreWarning ) {
+ $descriptor['IgnoreWarning'] = array(
+ 'type' => 'check',
+ 'id' => 'wpIgnoreWarning',
+ 'label-message' => 'ignorewarnings',
+ 'section' => 'options',
+ );
}
- /*
- * look for javascript
- */
-
- #resolve entity-refs to look at attributes. may be harsh on big files... cache result?
- $chunk = Sanitizer::decodeCharReferences( $chunk );
-
- #look for script-types
- if (preg_match('!type\s*=\s*[\'"]?\s*(?:\w*/)?(?:ecma|java)!sim',$chunk)) return true;
-
- #look for html-style script-urls
- if (preg_match('!(?:href|src|data)\s*=\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
-
- #look for css-style script-urls
- if (preg_match('!url\s*\(\s*[\'"]?\s*(?:ecma|java)script:!sim',$chunk)) return true;
-
- wfDebug("SpecialUpload::detectScript: no scripts found\n");
- return false;
- }
-
- function detectScriptInSvg( $filename ) {
- $check = new XmlTypeCheck( $filename, array( $this, 'checkSvgScriptCallback' ) );
- return $check->filterMatch;
- }
-
- /**
- * @todo Replace this with a whitelist filter!
- */
- function checkSvgScriptCallback( $element, $attribs ) {
- $stripped = $this->stripXmlNamespace( $element );
-
- if( $stripped == 'script' ) {
- wfDebug( __METHOD__ . ": Found script element '$element' in uploaded file.\n" );
- return true;
- }
+ $descriptor['wpDestFileWarningAck'] = array(
+ 'type' => 'hidden',
+ 'id' => 'wpDestFileWarningAck',
+ 'default' => $this->mDestWarningAck ? '1' : '',
+ );
- foreach( $attribs as $attrib => $value ) {
- $stripped = $this->stripXmlNamespace( $attrib );
- if( substr( $stripped, 0, 2 ) == 'on' ) {
- wfDebug( __METHOD__ . ": Found script attribute '$attrib'='value' in uploaded file.\n" );
- return true;
- }
- if( $stripped == 'href' && strpos( strtolower( $value ), 'javascript:' ) !== false ) {
- wfDebug( __METHOD__ . ": Found script href attribute '$attrib'='$value' in uploaded file.\n" );
- return true;
- }
- }
- }
-
- private function stripXmlNamespace( $name ) {
- // 'http://www.w3.org/2000/svg:script' -> 'script'
- $parts = explode( ':', strtolower( $name ) );
- return array_pop( $parts );
- }
-
- /**
- * Generic wrapper function for a virus scanner program.
- * This relies on the $wgAntivirus and $wgAntivirusSetup variables.
- * $wgAntivirusRequired may be used to deny upload if the scan fails.
- *
- * @param string $file Pathname to the temporary upload file
- * @return mixed false if not virus is found, NULL if the scan fails or is disabled,
- * or a string containing feedback from the virus scanner if a virus was found.
- * If textual feedback is missing but a virus was found, this function returns true.
- */
- function detectVirus($file) {
- global $wgAntivirus, $wgAntivirusSetup, $wgAntivirusRequired, $wgOut;
-
- if ( !$wgAntivirus ) {
- wfDebug( __METHOD__.": virus scanner disabled\n");
- return NULL;
- }
-
- if ( !$wgAntivirusSetup[$wgAntivirus] ) {
- wfDebug( __METHOD__.": unknown virus scanner: $wgAntivirus\n" );
- $wgOut->wrapWikiMsg( '<div class="error">$1</div>', array( 'virus-badscanner', $wgAntivirus ) );
- return wfMsg('virus-unknownscanner') . " $wgAntivirus";
- }
-
- # look up scanner configuration
- $command = $wgAntivirusSetup[$wgAntivirus]["command"];
- $exitCodeMap = $wgAntivirusSetup[$wgAntivirus]["codemap"];
- $msgPattern = isset( $wgAntivirusSetup[$wgAntivirus]["messagepattern"] ) ?
- $wgAntivirusSetup[$wgAntivirus]["messagepattern"] : null;
-
- if ( strpos( $command,"%f" ) === false ) {
- # simple pattern: append file to scan
- $command .= " " . wfEscapeShellArg( $file );
- } else {
- # complex pattern: replace "%f" with file to scan
- $command = str_replace( "%f", wfEscapeShellArg( $file ), $command );
- }
-
- wfDebug( __METHOD__.": running virus scan: $command \n" );
-
- # execute virus scanner
- $exitCode = false;
-
- #NOTE: there's a 50 line workaround to make stderr redirection work on windows, too.
- # that does not seem to be worth the pain.
- # Ask me (Duesentrieb) about it if it's ever needed.
- $output = array();
- if ( wfIsWindows() ) {
- exec( "$command", $output, $exitCode );
- } else {
- exec( "$command 2>&1", $output, $exitCode );
- }
-
- # map exit code to AV_xxx constants.
- $mappedCode = $exitCode;
- if ( $exitCodeMap ) {
- if ( isset( $exitCodeMap[$exitCode] ) ) {
- $mappedCode = $exitCodeMap[$exitCode];
- } elseif ( isset( $exitCodeMap["*"] ) ) {
- $mappedCode = $exitCodeMap["*"];
- }
- }
-
- if ( $mappedCode === AV_SCAN_FAILED ) {
- # scan failed (code was mapped to false by $exitCodeMap)
- wfDebug( __METHOD__.": failed to scan $file (code $exitCode).\n" );
-
- if ( $wgAntivirusRequired ) {
- return wfMsg('virus-scanfailed', array( $exitCode ) );
- } else {
- return NULL;
- }
- } else if ( $mappedCode === AV_SCAN_ABORTED ) {
- # scan failed because filetype is unknown (probably imune)
- wfDebug( __METHOD__.": unsupported file type $file (code $exitCode).\n" );
- return NULL;
- } else if ( $mappedCode === AV_NO_VIRUS ) {
- # no virus found
- wfDebug( __METHOD__.": file passed virus scan.\n" );
- return false;
- } else {
- $output = join( "\n", $output );
- $output = trim( $output );
-
- if ( !$output ) {
- $output = true; #if there's no output, return true
- } elseif ( $msgPattern ) {
- $groups = array();
- if ( preg_match( $msgPattern, $output, $groups ) ) {
- if ( $groups[1] ) {
- $output = $groups[1];
- }
- }
- }
-
- wfDebug( __METHOD__.": FOUND VIRUS! scanner feedback: $output \n" );
- return $output;
+ if ( $this->mForReUpload ) {
+ $descriptor['wpForReUpload'] = array(
+ 'type' => 'hidden',
+ 'id' => 'wpForReUpload',
+ 'default' => '1',
+ );
}
- }
-
- /**
- * Check if the temporary file is MacBinary-encoded, as some uploads
- * from Internet Explorer on Mac OS Classic and Mac OS X will be.
- * If so, the data fork will be extracted to a second temporary file,
- * which will then be checked for validity and either kept or discarded.
- *
- * @access private
- */
- function checkMacBinary() {
- $macbin = new MacBinary( $this->mTempPath );
- if( $macbin->isValid() ) {
- $dataFile = tempnam( wfTempDir(), "WikiMacBinary" );
- $dataHandle = fopen( $dataFile, 'wb' );
-
- wfDebug( "SpecialUpload::checkMacBinary: Extracting MacBinary data fork to $dataFile\n" );
- $macbin->extractData( $dataHandle );
- $this->mTempPath = $dataFile;
- $this->mFileSize = $macbin->dataForkLength();
+ return $descriptor;
- // We'll have to manually remove the new file if it's not kept.
- $this->mRemoveTempFile = true;
- }
- $macbin->close();
}
/**
- * If we've modified the upload file we need to manually remove it
- * on exit to clean up.
- * @access private
+ * Add the upload JS and show the form.
*/
- function cleanupTempFile() {
- if ( $this->mRemoveTempFile && $this->mTempPath && file_exists( $this->mTempPath ) ) {
- wfDebug( "SpecialUpload::cleanupTempFile: Removing temporary file {$this->mTempPath}\n" );
- unlink( $this->mTempPath );
- }
+ public function show() {
+ $this->addUploadJS();
+ parent::show();
}
/**
- * Check if there's an overwrite conflict and, if so, if restrictions
- * forbid this user from performing the upload.
- *
- * @return mixed true on success, WikiError on failure
- * @access private
- */
- function checkOverwrite( $name ) {
- $img = wfFindFile( $name );
-
- $error = '';
- if( $img ) {
- global $wgUser, $wgOut;
- if( $img->isLocal() ) {
- if( !self::userCanReUpload( $wgUser, $img->name ) ) {
- $error = 'fileexists-forbidden';
- }
- } else {
- if( !$wgUser->isAllowed( 'reupload' ) ||
- !$wgUser->isAllowed( 'reupload-shared' ) ) {
- $error = "fileexists-shared-forbidden";
- }
- }
- }
-
- if( $error ) {
- $errorText = wfMsg( $error, wfEscapeWikiText( $img->getName() ) );
- return $errorText;
- }
-
- // Rockin', go ahead and upload
- return true;
- }
-
- /**
- * Check if a user is the last uploader
- *
- * @param User $user
- * @param string $img, image name
- * @return bool
+ * Add upload JS to $wgOut
+ *
+ * @param bool $autofill Whether or not to autofill the destination
+ * filename text box
*/
- public static function userCanReUpload( User $user, $img ) {
- if( $user->isAllowed( 'reupload' ) )
- return true; // non-conditional
- if( !$user->isAllowed( 'reupload-own' ) )
- return false;
+ protected function addUploadJS( ) {
+ global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI;
+ global $wgOut;
- $dbr = wfGetDB( DB_SLAVE );
- $row = $dbr->selectRow('image',
- /* SELECT */ 'img_user',
- /* WHERE */ array( 'img_name' => $img )
+ $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck;
+ $useAjaxLicensePreview = $wgUseAjax && $wgAjaxLicensePreview && $wgEnableAPI;
+
+ $scriptVars = array(
+ 'wgAjaxUploadDestCheck' => $useAjaxDestCheck,
+ 'wgAjaxLicensePreview' => $useAjaxLicensePreview,
+ 'wgUploadAutoFill' => !$this->mForReUpload &&
+ // If we received mDestFile from the request, don't autofill
+ // the wpDestFile textbox
+ $this->mDestFile === '',
+ 'wgUploadSourceIds' => $this->mSourceIds,
);
- if ( !$row )
- return false;
- return $user->getId() == $row->img_user;
+ $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) );
+
+ // For <charinsert> support
+ $wgOut->addScriptFile( 'edit.js' );
+ $wgOut->addScriptFile( 'upload.js' );
}
/**
- * Display an error with a wikitext description
+ * Empty function; submission is handled elsewhere.
+ *
+ * @return bool false
*/
- function showError( $description ) {
- global $wgOut;
- $wgOut->setPageTitle( wfMsg( "internalerror" ) );
- $wgOut->setRobotPolicy( "noindex,nofollow" );
- $wgOut->setArticleRelated( false );
- $wgOut->enableClientCache( false );
- $wgOut->addWikiText( $description );
+ function trySubmit() {
+ return false;
}
- /**
- * Get the initial image page text based on a comment and optional file status information
- */
- static function getInitialPageText( $comment, $license, $copyStatus, $source ) {
- global $wgUseCopyrightUpload;
- if ( $wgUseCopyrightUpload ) {
- if ( $license != '' ) {
- $licensetxt = '== ' . wfMsgForContent( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
- }
- $pageText = '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n" .
- '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" .
- "$licensetxt" .
- '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ;
- } else {
- if ( $license != '' ) {
- $filedesc = $comment == '' ? '' : '== ' . wfMsg ( 'filedesc' ) . " ==\n" . $comment . "\n";
- $pageText = $filedesc .
- '== ' . wfMsgForContent ( 'license' ) . " ==\n" . '{{' . $license . '}}' . "\n";
- } else {
- $pageText = $comment;
- }
- }
- return $pageText;
- }
+}
- /**
- * If there are rows in the deletion log for this file, show them,
- * along with a nice little note for the user
- *
- * @param OutputPage $out
- * @param string filename
- */
- private function showDeletionLog( $out, $filename ) {
- global $wgUser;
- $loglist = new LogEventsList( $wgUser->getSkin(), $out );
- $pager = new LogPager( $loglist, 'delete', false, $filename );
- if( $pager->getNumRows() > 0 ) {
- $out->addHTML( '<div class="mw-warning-with-logexcerpt">' );
- $out->addWikiMsg( 'upload-wasdeleted' );
- $out->addHTML(
- $loglist->beginLogEventsList() .
- $pager->getBody() .
- $loglist->endLogEventsList()
+/**
+ * A form field that contains a radio box in the label
+ */
+class UploadSourceField extends HTMLTextField {
+ function getLabelHtml() {
+ $id = "wpSourceType{$this->mParams['upload-type']}";
+ $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel );
+
+ if ( !empty( $this->mParams['radio'] ) ) {
+ $attribs = array(
+ 'name' => 'wpSourceType',
+ 'type' => 'radio',
+ 'id' => $id,
+ 'value' => $this->mParams['upload-type'],
);
- $out->addHTML( '</div>' );
+ if ( !empty( $this->mParams['checked'] ) )
+ $attribs['checked'] = 'checked';
+ $label .= Html::element( 'input', $attribs );
}
+
+ return Html::rawElement( 'td', array( 'class' => 'mw-label' ), $label );
+ }
+ function getSize() {
+ return isset( $this->mParams['size'] )
+ ? $this->mParams['size']
+ : 60;
}
}
+
diff --git a/includes/specials/SpecialUploadMogile.php b/includes/specials/SpecialUploadMogile.php
deleted file mode 100644
index 7ff8fda6..00000000
--- a/includes/specials/SpecialUploadMogile.php
+++ /dev/null
@@ -1,135 +0,0 @@
-<?php
-/**
- * @file
- * @ingroup SpecialPage
- */
-
-/**
- * You will need the extension MogileClient to use this special page.
- */
-require_once( 'MogileFS.php' );
-
-/**
- * Entry point
- */
-function wfSpecialUploadMogile() {
- global $wgRequest;
- $form = new UploadFormMogile( $wgRequest );
- $form->execute();
-}
-
-/**
- * Extends Special:Upload with MogileFS.
- * @ingroup SpecialPage
- */
-class UploadFormMogile extends UploadForm {
- /**
- * Move the uploaded file from its temporary location to the final
- * destination. If a previous version of the file exists, move
- * it into the archive subdirectory.
- *
- * @todo If the later save fails, we may have disappeared the original file.
- *
- * @param string $saveName
- * @param string $tempName full path to the temporary file
- * @param bool $useRename Not used in this implementation
- */
- function saveUploadedFile( $saveName, $tempName, $useRename = false ) {
- global $wgOut;
- $mfs = MogileFS::NewMogileFS();
-
- $this->mSavedFile = "image!{$saveName}";
-
- if( $mfs->getPaths( $this->mSavedFile )) {
- $this->mUploadOldVersion = gmdate( 'YmdHis' ) . "!{$saveName}";
- if( !$mfs->rename( $this->mSavedFile, "archive!{$this->mUploadOldVersion}" ) ) {
- $wgOut->showFileRenameError( $this->mSavedFile,
- "archive!{$this->mUploadOldVersion}" );
- return false;
- }
- } else {
- $this->mUploadOldVersion = '';
- }
-
- if ( $this->mStashed ) {
- if (!$mfs->rename($tempName,$this->mSavedFile)) {
- $wgOut->showFileRenameError($tempName, $this->mSavedFile );
- return false;
- }
- } else {
- if ( !$mfs->saveFile($this->mSavedFile,'normal',$tempName )) {
- $wgOut->showFileCopyError( $tempName, $this->mSavedFile );
- return false;
- }
- unlink($tempName);
- }
- return true;
- }
-
- /**
- * Stash a file in a temporary directory for later processing
- * after the user has confirmed it.
- *
- * If the user doesn't explicitly cancel or accept, these files
- * can accumulate in the temp directory.
- *
- * @param string $saveName - the destination filename
- * @param string $tempName - the source temporary file to save
- * @return string - full path the stashed file, or false on failure
- * @access private
- */
- function saveTempUploadedFile( $saveName, $tempName ) {
- global $wgOut;
-
- $stash = 'stash!' . gmdate( "YmdHis" ) . '!' . $saveName;
- $mfs = MogileFS::NewMogileFS();
- if ( !$mfs->saveFile( $stash, 'normal', $tempName ) ) {
- $wgOut->showFileCopyError( $tempName, $stash );
- return false;
- }
- unlink($tempName);
- return $stash;
- }
-
- /**
- * Stash a file in a temporary directory for later processing,
- * and save the necessary descriptive info into the session.
- * Returns a key value which will be passed through a form
- * to pick up the path info on a later invocation.
- *
- * @return int
- * @access private
- */
- function stashSession() {
- $stash = $this->saveTempUploadedFile(
- $this->mUploadSaveName, $this->mUploadTempName );
-
- if( !$stash ) {
- # Couldn't save the file.
- return false;
- }
-
- $key = mt_rand( 0, 0x7fffffff );
- $_SESSION['wsUploadData'][$key] = array(
- 'mUploadTempName' => $stash,
- 'mUploadSize' => $this->mUploadSize,
- 'mOname' => $this->mOname );
- return $key;
- }
-
- /**
- * Remove a temporarily kept file stashed by saveTempUploadedFile().
- * @access private
- * @return success
- */
- function unsaveUploadedFile() {
- global $wgOut;
- $mfs = MogileFS::NewMogileFS();
- if ( ! $mfs->delete( $this->mUploadTempName ) ) {
- $wgOut->showFileDeleteError( $this->mUploadTempName );
- return false;
- } else {
- return true;
- }
- }
-}
diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php
index 8616ae28..8b8d0e9e 100644
--- a/includes/specials/SpecialUserlogin.php
+++ b/includes/specials/SpecialUserlogin.php
@@ -35,21 +35,23 @@ class LoginForm {
const CREATE_BLOCKED = 9;
const THROTTLED = 10;
const USER_BLOCKED = 11;
- const NEED_TOKEN = 12;
- const WRONG_TOKEN = 13;
+ const NEED_TOKEN = 12;
+ const WRONG_TOKEN = 13;
var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted;
var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword;
- var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage, $mSkipCookieCheck;
- var $mToken;
+ var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage;
+ var $mSkipCookieCheck, $mReturnToQuery, $mToken;
+
+ private $mExtUser = null;
/**
* Constructor
- * @param WebRequest $request A WebRequest object passed by reference
+ * @param $request WebRequest: a WebRequest object passed by reference
+ * @param $par String: subpage parameter
*/
function LoginForm( &$request, $par = '' ) {
- global $wgLang, $wgAllowRealName, $wgEnableEmail;
- global $wgAuth, $wgRedirectOnLogin;
+ global $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin;
$this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]]
$this->mName = $request->getText( 'wpName' );
@@ -57,6 +59,7 @@ class LoginForm {
$this->mRetype = $request->getText( 'wpRetype' );
$this->mDomain = $request->getText( 'wpDomain' );
$this->mReturnTo = $request->getVal( 'returnto' );
+ $this->mReturnToQuery = $request->getVal( 'returntoquery' );
$this->mCookieCheck = $request->getVal( 'wpCookieCheck' );
$this->mPosted = $request->wasPosted();
$this->mCreateaccount = $request->getCheck( 'wpCreateaccount' );
@@ -73,6 +76,7 @@ class LoginForm {
if ( $wgRedirectOnLogin ) {
$this->mReturnTo = $wgRedirectOnLogin;
+ $this->mReturnToQuery = '';
}
if( $wgEnableEmail ) {
@@ -80,7 +84,7 @@ class LoginForm {
} else {
$this->mEmail = '';
}
- if( $wgAllowRealName ) {
+ if( !in_array( 'realname', $wgHiddenPrefs ) ) {
$this->mRealName = $request->getText( 'wpRealName' );
} else {
$this->mRealName = '';
@@ -92,8 +96,10 @@ class LoginForm {
$wgAuth->setDomain( $this->mDomain );
# When switching accounts, it sucks to get automatically logged out
- if( $this->mReturnTo == $wgLang->specialPage( 'Userlogout' ) ) {
+ $returnToTitle = Title::newFromText( $this->mReturnTo );
+ if( is_object( $returnToTitle ) && $returnToTitle->isSpecial( 'Userlogout' ) ) {
$this->mReturnTo = '';
+ $this->mReturnToQuery = '';
}
}
@@ -121,14 +127,14 @@ class LoginForm {
function addNewAccountMailPassword() {
global $wgOut;
- if ('' == $this->mEmail) {
+ if ( $this->mEmail == '' ) {
$this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) );
return;
}
$u = $this->addNewaccountInternal();
- if ($u == NULL) {
+ if ($u == null) {
return;
}
@@ -162,7 +168,7 @@ class LoginForm {
# Create the account and abort if there's a problem doing so
$u = $this->addNewAccountInternal();
- if( $u == NULL )
+ if( $u == null )
return;
# If we showed up language selection links, and one was in use, be
@@ -191,7 +197,7 @@ class LoginForm {
if( $wgUser->isAnon() ) {
$wgUser = $u;
$wgUser->setCookies();
- wfRunHooks( 'AddNewAccount', array( $wgUser ) );
+ wfRunHooks( 'AddNewAccount', array( $wgUser, false ) );
$wgUser->addNewUserLogEntry();
if( $this->hasSessionCookie() ) {
return $this->successfulCreation();
@@ -207,7 +213,7 @@ class LoginForm {
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) );
$wgOut->returnToMain( false, $self );
- wfRunHooks( 'AddNewAccount', array( $u ) );
+ wfRunHooks( 'AddNewAccount', array( $u, false ) );
$u->addNewUserLogEntry();
return true;
}
@@ -218,7 +224,6 @@ class LoginForm {
*/
function addNewAccountInternal() {
global $wgUser, $wgOut;
- global $wgEnableSorbs, $wgProxyWhitelist;
global $wgMemc, $wgAccountCreationThrottle;
global $wgAuth, $wgMinimalPasswordLength;
global $wgEmailConfirmToEdit;
@@ -234,7 +239,7 @@ class LoginForm {
// cation server before they create an account (otherwise, they can
// create a local account and login as any domain user). We only need
// to check this for domains that aren't local.
- if( 'local' != $this->mDomain && '' != $this->mDomain ) {
+ if( 'local' != $this->mDomain && $this->mDomain != '' ) {
if( !$wgAuth->canCreateAccounts() && ( !$wgAuth->userExists( $this->mName ) || !$wgAuth->authenticate( $this->mName, $this->mPassword ) ) ) {
$this->mainLoginForm( wfMsg( 'wrongpassword' ) );
return false;
@@ -275,9 +280,7 @@ class LoginForm {
}
$ip = wfGetIP();
- if ( $wgEnableSorbs && !in_array( $ip, $wgProxyWhitelist ) &&
- $wgUser->inSorbsBlacklist( $ip ) )
- {
+ if ( $wgUser->isDnsBlacklisted( $ip, true /* check $wgProxyWhitelist */ ) ) {
$this->mainLoginForm( wfMsg( 'sorbs_create_account_reason' ) . ' (' . htmlspecialchars( $ip ) . ')' );
return false;
}
@@ -285,7 +288,7 @@ class LoginForm {
# Now create a dummy user ($u) and check if it is valid
$name = trim( $this->mName );
$u = User::newFromName( $name, 'creatable' );
- if ( is_null( $u ) ) {
+ if ( !is_object( $u ) ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
return false;
}
@@ -301,9 +304,10 @@ class LoginForm {
}
# check for minimal password length
- if ( !$u->isValidPassword( $this->mPassword ) ) {
+ $valid = $u->getPasswordValidity( $this->mPassword );
+ if ( $valid !== true ) {
if ( !$this->mCreateaccountMail ) {
- $this->mainLoginForm( wfMsgExt( 'passwordtooshort', array( 'parsemag' ), $wgMinimalPasswordLength ) );
+ $this->mainLoginForm( wfMsgExt( $valid, array( 'parsemag' ), $wgMinimalPasswordLength ) );
return false;
} else {
# do not force a password for account creation by email
@@ -383,6 +387,14 @@ class LoginForm {
$wgAuth->initUser( $u, $autocreate );
+ if ( $this->mExtUser ) {
+ $this->mExtUser->linkToLocal( $u->getId() );
+ $email = $this->mExtUser->getPref( 'emailaddress' );
+ if ( $email && !$this->mEmail ) {
+ $u->setEmail( $email );
+ }
+ }
+
$u->setOption( 'rememberpassword', $this->mRemember ? 1 : 0 );
$u->saveSettings();
@@ -399,15 +411,13 @@ class LoginForm {
* This may create a local account as a side effect if the
* authentication plugin allows transparent local account
* creation.
- *
- * @public
*/
- function authenticateUserData() {
+ public function authenticateUserData() {
global $wgUser, $wgAuth;
- if ( '' == $this->mName ) {
+ if ( $this->mName == '' ) {
return self::NO_NAME;
}
-
+
// We require a login token to prevent login CSRF
// Handle part of this before incrementing the throttle so
// token-less login attempts don't count towards the throttle
@@ -422,17 +432,17 @@ class LoginForm {
if ( !$this->mToken ) {
return self::NEED_TOKEN;
}
-
+
global $wgPasswordAttemptThrottle;
- $throttleCount=0;
- if ( is_array($wgPasswordAttemptThrottle) ) {
+ $throttleCount = 0;
+ if ( is_array( $wgPasswordAttemptThrottle ) ) {
$throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) );
$count = $wgPasswordAttemptThrottle['count'];
$period = $wgPasswordAttemptThrottle['seconds'];
global $wgMemc;
- $throttleCount = $wgMemc->get($throttleKey);
+ $throttleCount = $wgMemc->get( $throttleKey );
if ( !$throttleCount ) {
$wgMemc->add( $throttleKey, 1, $period ); // start counter
} else if ( $throttleCount < $count ) {
@@ -457,8 +467,13 @@ class LoginForm {
wfDebug( __METHOD__.": already logged in as {$this->mName}\n" );
return self::SUCCESS;
}
+
+ $this->mExtUser = ExternalUser::newFromName( $this->mName );
+
+ # TODO: Allow some magic here for invalid external names, e.g., let the
+ # user choose a different wiki name.
$u = User::newFromName( $this->mName );
- if( is_null( $u ) || !User::isUsableName( $u->getName() ) ) {
+ if( !( $u instanceof User ) || !User::isUsableName( $u->getName() ) ) {
return self::ILLEGAL;
}
@@ -471,6 +486,15 @@ class LoginForm {
$isAutoCreated = true;
}
} else {
+ global $wgExternalAuthType, $wgAutocreatePolicy;
+ if ( $wgExternalAuthType && $wgAutocreatePolicy != 'never'
+ && is_object( $this->mExtUser )
+ && $this->mExtUser->authenticate( $this->mPassword ) ) {
+ # The external user and local user have the same name and
+ # password, so we assume they're the same.
+ $this->mExtUser->linkToLocal( $u->getID() );
+ }
+
$u->load();
}
@@ -480,6 +504,7 @@ class LoginForm {
return $abort;
}
+ global $wgBlockDisablesLogin;
if (!$u->checkPassword( $this->mPassword )) {
if( $u->checkTemporaryPassword( $this->mPassword ) ) {
// The e-mailed temporary password should not be used for actu-
@@ -508,8 +533,11 @@ class LoginForm {
// faces etc will probably just fail cleanly here.
$retval = self::RESET_PASS;
} else {
- $retval = '' == $this->mPassword ? self::EMPTY_PASS : self::WRONG_PASS;
+ $retval = ($this->mPassword == '') ? self::EMPTY_PASS : self::WRONG_PASS;
}
+ } elseif ( $wgBlockDisablesLogin && $u->isBlocked() ) {
+ // If we've enabled it, make it so that a blocked user cannot login
+ $retval = self::USER_BLOCKED;
} else {
$wgAuth->updateUser( $u );
$wgUser = $u;
@@ -536,26 +564,40 @@ class LoginForm {
* @return integer Status code
*/
function attemptAutoCreate( $user ) {
- global $wgAuth, $wgUser;
+ global $wgAuth, $wgUser, $wgAutocreatePolicy;
+
+ if ( $wgUser->isBlockedFromCreateAccount() ) {
+ wfDebug( __METHOD__.": user is blocked from account creation\n" );
+ return self::CREATE_BLOCKED;
+ }
+
/**
* If the external authentication plugin allows it, automatically cre-
* ate a new account for users that are externally defined but have not
* yet logged in.
*/
- if ( !$wgAuth->autoCreate() ) {
- return self::NOT_EXISTS;
- }
- if ( !$wgAuth->userExists( $user->getName() ) ) {
- wfDebug( __METHOD__.": user does not exist\n" );
- return self::NOT_EXISTS;
- }
- if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
- wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" );
- return self::WRONG_PLUGIN_PASS;
- }
- if ( $wgUser->isBlockedFromCreateAccount() ) {
- wfDebug( __METHOD__.": user is blocked from account creation\n" );
- return self::CREATE_BLOCKED;
+ if ( $this->mExtUser ) {
+ # mExtUser is neither null nor false, so use the new ExternalAuth
+ # system.
+ if ( $wgAutocreatePolicy == 'never' ) {
+ return self::NOT_EXISTS;
+ }
+ if ( !$this->mExtUser->authenticate( $this->mPassword ) ) {
+ return self::WRONG_PLUGIN_PASS;
+ }
+ } else {
+ # Old AuthPlugin.
+ if ( !$wgAuth->autoCreate() ) {
+ return self::NOT_EXISTS;
+ }
+ if ( !$wgAuth->userExists( $user->getName() ) ) {
+ wfDebug( __METHOD__.": user does not exist\n" );
+ return self::NOT_EXISTS;
+ }
+ if ( !$wgAuth->authenticate( $user->getName(), $this->mPassword ) ) {
+ wfDebug( __METHOD__.": \$wgAuth->authenticate() returned false, aborting\n" );
+ return self::WRONG_PLUGIN_PASS;
+ }
}
wfDebug( __METHOD__.": creating account\n" );
@@ -566,8 +608,7 @@ class LoginForm {
function processLogin() {
global $wgUser, $wgAuth;
- switch ($this->authenticateUserData())
- {
+ switch ( $this->authenticateUserData() ) {
case self::SUCCESS:
# We've verified now, update the real record
if( (bool)$this->mRemember != (bool)$wgUser->getOption( 'rememberpassword' ) ) {
@@ -630,6 +671,10 @@ class LoginForm {
case self::THROTTLED:
$this->mainLoginForm( wfMsg( 'login-throttled' ) );
break;
+ case self::USER_BLOCKED:
+ $this->mainLoginForm( wfMsgExt( 'login-userblocked',
+ array( 'parsemag', 'escape' ), $this->mName ) );
+ break;
default:
throw new MWException( "Unhandled case value" );
}
@@ -664,6 +709,13 @@ class LoginForm {
$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() ) {
@@ -684,12 +736,12 @@ class LoginForm {
return;
}
- if ( '' == $this->mName ) {
+ if ( $this->mName == '' ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
return;
}
$u = User::newFromName( $this->mName );
- if( is_null( $u ) ) {
+ if( !$u instanceof User ) {
$this->mainLoginForm( wfMsg( 'noname' ) );
return;
}
@@ -725,17 +777,17 @@ class LoginForm {
/**
- * @param object user
- * @param bool throttle
- * @param string message name of email title
- * @param string message name of email text
- * @return mixed true on success, WikiError on failure
+ * @param $u User object
+ * @param $throttle Boolean
+ * @param $emailTitle String: message name of email title
+ * @param $emailText String: message name of email text
+ * @return Mixed: true on success, WikiError on failure
* @private
*/
function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) {
global $wgServer, $wgScript, $wgUser, $wgNewPasswordExpiry;
- if ( '' == $u->getEmail() ) {
+ if ( $u->getEmail() == '' ) {
return new WikiError( wfMsg( 'noemail', $u->getName() ) );
}
$ip = wfGetIP();
@@ -748,10 +800,10 @@ class LoginForm {
$np = $u->randomPassword();
$u->setNewpassword( $np, $throttle );
$u->saveSettings();
-
- $m = wfMsgExt( $emailText, array( 'parsemag' ), $ip, $u->getName(), $np,
+ $userLanguage = $u->getOption( 'language' );
+ $m = wfMsgExt( $emailText, array( 'parsemag', 'language' => $userLanguage ), $ip, $u->getName(), $np,
$wgServer . $wgScript, round( $wgNewPasswordExpiry / 86400 ) );
- $result = $u->sendMail( wfMsg( $emailTitle ), $m );
+ $result = $u->sendMail( wfMsgExt( $emailTitle, array( 'parsemag', 'language' => $userLanguage ) ), $m );
return $result;
}
@@ -781,8 +833,7 @@ class LoginForm {
if ( !$titleObj instanceof Title ) {
$titleObj = Title::newMainPage();
}
-
- $wgOut->redirect( $titleObj->getFullURL() );
+ $wgOut->redirect( $titleObj->getFullURL( $this->mReturnToQuery ) );
}
}
@@ -815,7 +866,7 @@ class LoginForm {
$wgOut->addHTML( $injected_html );
if ( !empty( $this->mReturnTo ) ) {
- $wgOut->returnToMain( null, $this->mReturnTo );
+ $wgOut->returnToMain( null, $this->mReturnTo, $this->mReturnToQuery );
} else {
$wgOut->returnToMain( null );
}
@@ -868,7 +919,7 @@ class LoginForm {
* @private
*/
function mainLoginForm( $msg, $msgtype = 'error' ) {
- global $wgUser, $wgOut, $wgAllowRealName, $wgEnableEmail;
+ global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail;
global $wgCookiePrefix, $wgLoginLanguageSelector;
global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration;
@@ -890,7 +941,7 @@ class LoginForm {
}
}
- if ( '' == $this->mName ) {
+ if ( $this->mName == '' ) {
if ( $wgUser->isLoggedIn() ) {
$this->mName = $wgUser->getName();
} else {
@@ -914,6 +965,9 @@ class LoginForm {
if ( !empty( $this->mReturnTo ) ) {
$returnto = '&returnto=' . wfUrlencode( $this->mReturnTo );
+ if ( !empty( $this->mReturnToQuery ) )
+ $returnto .= '&returntoquery=' .
+ wfUrlencode( $this->mReturnToQuery );
$q .= $returnto;
$linkq .= $returnto;
}
@@ -928,7 +982,7 @@ class LoginForm {
# Don't show a "create account" link if the user can't
if( $this->showCreateOrLoginLink( $wgUser ) )
- $template->set( 'link', wfMsgHtml( $linkmsg, $link ) );
+ $template->set( 'link', wfMsgWikiHtml( $linkmsg, $link ) );
else
$template->set( 'link', '' );
@@ -944,7 +998,7 @@ class LoginForm {
$template->set( 'message', $msg );
$template->set( 'messagetype', $msgtype );
$template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() );
- $template->set( 'userealname', $wgAllowRealName );
+ $template->set( 'userealname', !in_array( 'realname', $wgHiddenPrefs ) );
$template->set( 'useemail', $wgEnableEmail );
$template->set( 'emailrequired', $wgEmailConfirmToEdit );
$template->set( 'canreset', $wgAuth->allowPasswordChange() );
@@ -971,14 +1025,20 @@ class LoginForm {
}
// Give authentication and captcha plugins a chance to modify the form
- $wgAuth->modifyUITemplate( $template );
+ $wgAuth->modifyUITemplate( $template, $this->mType );
if ( $this->mType == 'signup' ) {
wfRunHooks( 'UserCreateForm', array( &$template ) );
} else {
wfRunHooks( 'UserLoginForm', array( &$template ) );
}
- $wgOut->setPageTitle( wfMsg( 'userlogin' ) );
+ //Changes the title depending on permissions for creating account
+ if ( $wgUser->isAllowed( 'createaccount' ) ) {
+ $wgOut->setPageTitle( wfMsg( 'userlogin' ) );
+ } else {
+ $wgOut->setPageTitle( wfMsg( 'userloginnocreate' ) );
+ }
+
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->setArticleRelated( false );
$wgOut->disallowUserJs(); // just in case...
@@ -1080,8 +1140,6 @@ class LoginForm {
* @private
*/
function onCookieRedirectCheck( $type ) {
- global $wgUser;
-
if ( !$this->hasSessionCookie() ) {
if ( $type == 'new' ) {
return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) );
@@ -1139,12 +1197,17 @@ class LoginForm {
function makeLanguageSelectorLink( $text, $lang ) {
global $wgUser;
$self = SpecialPage::getTitleFor( 'Userlogin' );
- $attr[] = 'uselang=' . $lang;
+ $attr = array( 'uselang' => $lang );
if( $this->mType == 'signup' )
- $attr[] = 'type=signup';
+ $attr['type'] = 'signup';
if( $this->mReturnTo )
- $attr[] = 'returnto=' . $this->mReturnTo;
+ $attr['returnto'] = $this->mReturnTo;
$skin = $wgUser->getSkin();
- return $skin->makeKnownLinkObj( $self, htmlspecialchars( $text ), implode( '&', $attr ) );
+ return $skin->linkKnown(
+ $self,
+ htmlspecialchars( $text ),
+ array(),
+ $attr
+ );
}
}
diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php
index 3d497bd7..e23df612 100644
--- a/includes/specials/SpecialUserlogout.php
+++ b/includes/specials/SpecialUserlogout.php
@@ -10,6 +10,16 @@
function wfSpecialUserlogout() {
global $wgUser, $wgOut;
+ /**
+ * Some satellite ISPs use broken precaching schemes that log people out straight after
+ * they're logged in (bug 17790). Luckily, there's a way to detect such requests.
+ */
+ if ( isset( $_SERVER['REQUEST_URI'] ) && strpos( $_SERVER['REQUEST_URI'], '&amp;' ) !== false ) {
+ wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" );
+ wfHttpError( 400, wfMsg( 'loginerror' ), wfMsg( 'suspicious-userlogout' ) );
+ return;
+ }
+
$oldName = $wgUser->getName();
$wgUser->logout();
$wgOut->setRobotPolicy( 'noindex,nofollow' );
diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php
index 90619109..36caf9a6 100644
--- a/includes/specials/SpecialUserrights.php
+++ b/includes/specials/SpecialUserrights.php
@@ -34,8 +34,8 @@ class UserrightsPage extends SpecialPage {
return !empty( $available['add'] )
or !empty( $available['remove'] )
or ( ( $this->isself || !$checkIfSelf ) and
- (!empty( $available['add-self'] )
- or !empty( $available['remove-self'] )));
+ ( !empty( $available['add-self'] )
+ or !empty( $available['remove-self'] ) ) );
}
/**
@@ -44,10 +44,10 @@ class UserrightsPage extends SpecialPage {
*
* @param $par Mixed: string if any subpage provided, else null
*/
- function execute( $par ) {
+ public function execute( $par ) {
// If the visitor doesn't have permissions to assign or remove
// any groups, it's a bit silly to give them the user search prompt.
- global $wgUser, $wgRequest;
+ global $wgUser, $wgRequest, $wgOut;
if( $par ) {
$this->mTarget = $par;
@@ -55,32 +55,41 @@ class UserrightsPage extends SpecialPage {
$this->mTarget = $wgRequest->getVal( 'user' );
}
- if (!$this->mTarget) {
+ /*
+ * If the user is blocked and they only have "partial" access
+ * (e.g. they don't have the userrights permission), then don't
+ * allow them to use Special:UserRights.
+ */
+ if( $wgUser->isBlocked() && !$wgUser->isAllowed( 'userrights' ) ) {
+ $wgOut->blockedPage();
+ return;
+ }
+
+ $available = $this->changeableGroups();
+
+ if ( !$this->mTarget ) {
/*
* If the user specified no target, and they can only
* edit their own groups, automatically set them as the
* target.
*/
- $available = $this->changeableGroups();
- if (empty($available['add']) && empty($available['remove']))
+ if ( !count( $available['add'] ) && !count( $available['remove'] ) )
$this->mTarget = $wgUser->getName();
}
- if ($this->mTarget == $wgUser->getName())
+ if ( $this->mTarget == $wgUser->getName() )
$this->isself = true;
if( !$this->userCanChangeRights( $wgUser, true ) ) {
// fixme... there may be intermediate groups we can mention.
- global $wgOut;
- $wgOut->showPermissionsErrorPage( array(
+ $wgOut->showPermissionsErrorPage( array( array(
$wgUser->isAnon()
? 'userrights-nologin'
- : 'userrights-notallowed' ) );
+ : 'userrights-notallowed' ) ) );
return;
}
if ( wfReadOnly() ) {
- global $wgOut;
$wgOut->readOnlyPage();
return;
}
@@ -90,7 +99,8 @@ class UserrightsPage extends SpecialPage {
$this->setHeaders();
// show the general form
- $this->switchForm();
+ if ( count( $available['add'] ) || count( $available['remove'] ) )
+ $this->switchForm();
if( $wgRequest->wasPosted() ) {
// save settings
@@ -102,9 +112,7 @@ class UserrightsPage extends SpecialPage {
$this->mTarget,
$reason
);
-
- global $wgOut;
-
+
$url = $this->getSuccessURL();
$wgOut->redirect( $url );
return;
@@ -117,7 +125,7 @@ class UserrightsPage extends SpecialPage {
$this->editUserGroupsForm( $this->mTarget );
}
}
-
+
function getSuccessURL() {
return $this->getTitle( $this->mTarget )->getFullURL();
}
@@ -130,11 +138,12 @@ class UserrightsPage extends SpecialPage {
* @param $reason String: reason for group change
* @return null
*/
- function saveUserGroups( $username, $reason = '') {
+ function saveUserGroups( $username, $reason = '' ) {
global $wgRequest, $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
$user = $this->fetchUser( $username );
- if( !$user ) {
+ if( $user instanceof WikiErrorMsg ) {
+ $wgOut->addWikiMsgArray( $user->getMessageKey(), $user->getMessageArgs() );
return;
}
@@ -144,38 +153,58 @@ class UserrightsPage extends SpecialPage {
// This could possibly create a highly unlikely race condition if permissions are changed between
// when the form is loaded and when the form is saved. Ignoring it for the moment.
- foreach ($allgroups as $group) {
+ foreach ( $allgroups as $group ) {
// We'll tell it to remove all unchecked groups, and add all checked groups.
// Later on, this gets filtered for what can actually be removed
- if ($wgRequest->getCheck( "wpGroup-$group" )) {
+ if ( $wgRequest->getCheck( "wpGroup-$group" ) ) {
$addgroup[] = $group;
} else {
$removegroup[] = $group;
}
}
+
+ $this->doSaveUserGroups( $user, $addgroup, $removegroup, $reason );
+ }
+
+ /**
+ * Save user groups changes in the database.
+ *
+ * @param $user User object
+ * @param $add Array of groups to add
+ * @param $remove Array of groups to remove
+ * @param $reason String: reason for group change
+ * @return Array: Tuple of added, then removed groups
+ */
+ function doSaveUserGroups( $user, $add, $remove, $reason = '' ) {
+ global $wgUser;
// Validate input set...
+ $isself = ( $user->getName() == $wgUser->getName() );
+ $groups = $user->getGroups();
$changeable = $this->changeableGroups();
- $addable = array_merge( $changeable['add'], $this->isself ? $changeable['add-self'] : array() );
- $removable = array_merge( $changeable['remove'], $this->isself ? $changeable['remove-self'] : array() );
-
- $removegroup = array_unique(
- array_intersect( (array)$removegroup, $removable ) );
- $addgroup = array_unique(
- array_intersect( (array)$addgroup, $addable ) );
+ $addable = array_merge( $changeable['add'], $isself ? $changeable['add-self'] : array() );
+ $removable = array_merge( $changeable['remove'], $isself ? $changeable['remove-self'] : array() );
+
+ $remove = array_unique(
+ array_intersect( (array)$remove, $removable, $groups ) );
+ $add = array_unique( array_diff(
+ array_intersect( (array)$add, $addable ),
+ $groups )
+ );
$oldGroups = $user->getGroups();
$newGroups = $oldGroups;
+
// remove then add groups
- if( $removegroup ) {
- $newGroups = array_diff($newGroups, $removegroup);
- foreach( $removegroup as $group ) {
+ if( $remove ) {
+ $newGroups = array_diff( $newGroups, $remove );
+ foreach( $remove as $group ) {
$user->removeGroup( $group );
}
}
- if( $addgroup ) {
- $newGroups = array_merge($newGroups, $addgroup);
- foreach( $addgroup as $group ) {
+ if( $add ) {
+ $newGroups = array_merge( $newGroups, $add );
+ foreach( $add as $group ) {
$user->addGroup( $group );
}
}
@@ -186,26 +215,24 @@ class UserrightsPage extends SpecialPage {
wfDebug( 'oldGroups: ' . print_r( $oldGroups, true ) );
wfDebug( 'newGroups: ' . print_r( $newGroups, true ) );
- if( $user instanceof User ) {
- // hmmm
- wfRunHooks( 'UserRights', array( &$user, $addgroup, $removegroup ) );
- }
+ wfRunHooks( 'UserRights', array( &$user, $add, $remove ) );
if( $newGroups != $oldGroups ) {
- $this->addLogEntry( $user, $oldGroups, $newGroups );
+ $this->addLogEntry( $user, $oldGroups, $newGroups, $reason );
}
+ return array( $add, $remove );
}
-
+
+
/**
* Add a rights log entry for an action.
*/
- function addLogEntry( $user, $oldGroups, $newGroups ) {
- global $wgRequest;
+ function addLogEntry( $user, $oldGroups, $newGroups, $reason ) {
$log = new LogPage( 'rights' );
$log->addEntry( 'rights',
$user->getUserPage(),
- $wgRequest->getText( 'user-reason' ),
+ $reason,
array(
$this->makeGroupNameListForLog( $oldGroups ),
$this->makeGroupNameListForLog( $newGroups )
@@ -221,7 +248,8 @@ class UserrightsPage extends SpecialPage {
global $wgOut;
$user = $this->fetchUser( $username );
- if( !$user ) {
+ if( $user instanceof WikiErrorMsg ) {
+ $wgOut->addWikiMsgArray( $user->getMessageKey(), $user->getMessageArgs() );
return;
}
@@ -239,10 +267,10 @@ class UserrightsPage extends SpecialPage {
* return a user (or proxy) object for manipulating it.
*
* Side effects: error output for invalid access
- * @return mixed User, UserRightsProxy, or null
+ * @return mixed User, UserRightsProxy, or WikiErrorMsg
*/
- function fetchUser( $username ) {
- global $wgOut, $wgUser, $wgUserrightsInterwikiDelimiter;
+ public function fetchUser( $username ) {
+ global $wgUser, $wgUserrightsInterwikiDelimiter;
$parts = explode( $wgUserrightsInterwikiDelimiter, $username );
if( count( $parts ) < 2 ) {
@@ -250,20 +278,21 @@ class UserrightsPage extends SpecialPage {
$database = '';
} else {
list( $name, $database ) = array_map( 'trim', $parts );
-
- if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) {
- $wgOut->addWikiMsg( 'userrights-no-interwiki' );
- return null;
- }
- if( !UserRightsProxy::validDatabase( $database ) ) {
- $wgOut->addWikiMsg( 'userrights-nodatabase', $database );
- return null;
+
+ if( $database == wfWikiID() ) {
+ $database = '';
+ } else {
+ if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) {
+ return new WikiErrorMsg( 'userrights-no-interwiki' );
+ }
+ if( !UserRightsProxy::validDatabase( $database ) ) {
+ return new WikiErrorMsg( 'userrights-nodatabase', $database );
+ }
}
}
if( $name == '' ) {
- $wgOut->addWikiMsg( 'nouserspecified' );
- return false;
+ return new WikiErrorMsg( 'nouserspecified' );
}
if( $name{0} == '#' ) {
@@ -278,8 +307,13 @@ class UserrightsPage extends SpecialPage {
}
if( !$name ) {
- $wgOut->addWikiMsg( 'noname' );
- return null;
+ return new WikiErrorMsg( 'noname' );
+ }
+ } else {
+ $name = User::getCanonicalName( $name );
+ if( !$name ) {
+ // invalid name
+ return new WikiErrorMsg( 'nosuchusershort', $username );
}
}
@@ -290,8 +324,7 @@ class UserrightsPage extends SpecialPage {
}
if( !$user || $user->isAnon() ) {
- $wgOut->addWikiMsg( 'nosuchusershort', $username );
- return null;
+ return new WikiErrorMsg( 'nosuchusershort', $username );
}
return $user;
@@ -339,14 +372,16 @@ class UserrightsPage extends SpecialPage {
* @return Array: Tuple of addable, then removable groups
*/
protected function splitGroups( $groups ) {
- list($addable, $removable, $addself, $removeself) = array_values( $this->changeableGroups() );
+ list( $addable, $removable, $addself, $removeself ) = array_values( $this->changeableGroups() );
$removable = array_intersect(
- array_merge( $this->isself ? $removeself : array(), $removable ),
- $groups ); // Can't remove groups the user doesn't have
- $addable = array_diff(
- array_merge( $this->isself ? $addself : array(), $addable ),
- $groups ); // Can't add groups the user does have
+ array_merge( $this->isself ? $removeself : array(), $removable ),
+ $groups
+ ); // Can't remove groups the user doesn't have
+ $addable = array_diff(
+ array_merge( $this->isself ? $addself : array(), $addable ),
+ $groups
+ ); // Can't add groups the user does have
return array( $addable, $removable );
}
@@ -364,10 +399,21 @@ class UserrightsPage extends SpecialPage {
foreach( $groups as $group )
$list[] = self::buildGroupLink( $group );
+ $autolist = array();
+ if ( $user instanceof User ) {
+ foreach( Autopromote::getAutopromoteGroups( $user ) as $group ) {
+ $autolist[] = self::buildGroupLink( $group );
+ }
+ }
+
$grouplist = '';
if( count( $list ) > 0 ) {
$grouplist = wfMsgHtml( 'userrights-groupsmember' );
- $grouplist = '<p>' . $grouplist . ' ' . $wgLang->listToText( $list ) . '</p>';
+ $grouplist = '<p>' . $grouplist . ' ' . $wgLang->listToText( $list ) . "</p>\n";
+ }
+ if( count( $autolist ) > 0 ) {
+ $autogrouplistintro = wfMsgHtml( 'userrights-groupsmember-auto' );
+ $grouplist .= '<p>' . $autogrouplistintro . ' ' . $wgLang->listToText( $autolist ) . "</p>\n";
}
$wgOut->addHTML(
Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) .
@@ -409,17 +455,17 @@ class UserrightsPage extends SpecialPage {
private static function buildGroupLink( $group ) {
static $cache = array();
if( !isset( $cache[$group] ) )
- $cache[$group] = User::makeGroupLinkHtml( $group, User::getGroupName( $group ) );
+ $cache[$group] = User::makeGroupLinkHtml( $group, htmlspecialchars( User::getGroupName( $group ) ) );
return $cache[$group];
}
-
+
/**
* Returns an array of all groups that may be edited
* @return array Array of groups that may be edited.
*/
- protected static function getAllGroups() {
- return User::getAllGroups();
- }
+ protected static function getAllGroups() {
+ return User::getAllGroups();
+ }
/**
* Adds a table with checkboxes where you can select what groups to add/remove
@@ -431,11 +477,11 @@ class UserrightsPage extends SpecialPage {
$allgroups = $this->getAllGroups();
$ret = '';
- $column = 1;
- $settable_col = '';
- $unsettable_col = '';
+ # Put all column info into an associative array so that extensions can
+ # more easily manage it.
+ $columns = array( 'unchangeable' => array(), 'changeable' => array() );
- foreach ($allgroups as $group) {
+ foreach( $allgroups as $group ) {
$set = in_array( $group, $usergroups );
# Should the checkbox be disabled?
$disabled = !(
@@ -443,53 +489,54 @@ class UserrightsPage extends SpecialPage {
( !$set && $this->canAdd( $group ) ) );
# Do we need to point out that this action is irreversible?
$irreversible = !$disabled && (
- ($set && !$this->canAdd( $group )) ||
- (!$set && !$this->canRemove( $group ) ) );
-
- $attr = $disabled ? array( 'disabled' => 'disabled' ) : array();
- $text = $irreversible
- ? wfMsgHtml( 'userrights-irreversible-marker', User::getGroupMember( $group ) )
- : User::getGroupMember( $group );
- $checkbox = Xml::checkLabel( $text, "wpGroup-$group",
- "wpGroup-$group", $set, $attr );
- $checkbox = $disabled ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkbox ) : $checkbox;
-
- if ($disabled) {
- $unsettable_col .= "$checkbox<br />\n";
+ ( $set && !$this->canAdd( $group ) ) ||
+ ( !$set && !$this->canRemove( $group ) ) );
+
+ $checkbox = array(
+ 'set' => $set,
+ 'disabled' => $disabled,
+ 'irreversible' => $irreversible
+ );
+
+ if( $disabled ) {
+ $columns['unchangeable'][$group] = $checkbox;
} else {
- $settable_col .= "$checkbox<br />\n";
+ $columns['changeable'][$group] = $checkbox;
}
}
- if ($column) {
- $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) .
- "<tr>
-";
- if( $settable_col !== '' ) {
- $ret .= xml::element( 'th', null, wfMsg( 'userrights-changeable-col' ) );
- }
- if( $unsettable_col !== '' ) {
- $ret .= xml::element( 'th', null, wfMsg( 'userrights-unchangeable-col' ) );
- }
- $ret.= "</tr>
- <tr>
-";
- if( $settable_col !== '' ) {
- $ret .=
-" <td style='vertical-align:top;'>
- $settable_col
- </td>
-";
- }
- if( $unsettable_col !== '' ) {
- $ret .=
-" <td style='vertical-align:top;'>
- $unsettable_col
- </td>
-";
+ # Build the HTML table
+ $ret .= Xml::openElement( 'table', array( 'border' => '0', 'class' => 'mw-userrights-groups' ) ) .
+ "<tr>\n";
+ foreach( $columns as $name => $column ) {
+ if( $column === array() )
+ continue;
+ $ret .= xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) );
+ }
+ $ret.= "</tr>\n<tr>\n";
+ foreach( $columns as $column ) {
+ if( $column === array() )
+ continue;
+ $ret .= "\t<td style='vertical-align:top;'>\n";
+ foreach( $column as $group => $checkbox ) {
+ $attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array();
+
+ if ( $checkbox['irreversible'] ) {
+ $text = htmlspecialchars( wfMsg( 'userrights-irreversible-marker',
+ User::getGroupMember( $group ) ) );
+ } else {
+ $text = htmlspecialchars( User::getGroupMember( $group ) );
+ }
+ $checkboxHtml = Xml::checkLabel( $text, "wpGroup-" . $group,
+ "wpGroup-" . $group, $checkbox['set'], $attr );
+ $ret .= "\t\t" . ( $checkbox['disabled']
+ ? Xml::tags( 'span', array( 'class' => 'mw-userrights-disabled' ), $checkboxHtml )
+ : $checkboxHtml
+ ) . "<br />\n";
}
- $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' );
+ $ret .= "\t</td>\n";
}
+ $ret .= Xml::closeElement( 'tr' ) . Xml::closeElement( 'table' );
return $ret;
}
@@ -502,7 +549,7 @@ class UserrightsPage extends SpecialPage {
// $this->changeableGroups()['remove'] doesn't work, of course. Thanks,
// PHP.
$groups = $this->changeableGroups();
- return in_array( $group, $groups['remove'] ) || ($this->isself && in_array( $group, $groups['remove-self'] ));
+ return in_array( $group, $groups['remove'] ) || ( $this->isself && in_array( $group, $groups['remove-self'] ) );
}
/**
@@ -511,116 +558,17 @@ class UserrightsPage extends SpecialPage {
*/
private function canAdd( $group ) {
$groups = $this->changeableGroups();
- return in_array( $group, $groups['add'] ) || ($this->isself && in_array( $group, $groups['add-self'] ));
+ return in_array( $group, $groups['add'] ) || ( $this->isself && in_array( $group, $groups['add-self'] ) );
}
/**
- * Returns an array of the groups that the user can add/remove.
+ * Returns $wgUser->changeableGroups()
*
* @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) )
*/
function changeableGroups() {
global $wgUser;
-
- if( $wgUser->isAllowed( 'userrights' ) ) {
- // This group gives the right to modify everything (reverse-
- // compatibility with old "userrights lets you change
- // everything")
- // Using array_merge to make the groups reindexed
- $all = array_merge( User::getAllGroups() );
- return array(
- 'add' => $all,
- 'remove' => $all,
- 'add-self' => array(),
- 'remove-self' => array()
- );
- }
-
- // Okay, it's not so simple, we will have to go through the arrays
- $groups = array(
- 'add' => array(),
- 'remove' => array(),
- 'add-self' => array(),
- 'remove-self' => array() );
- $addergroups = $wgUser->getEffectiveGroups();
-
- foreach ($addergroups as $addergroup) {
- $groups = array_merge_recursive(
- $groups, $this->changeableByGroup($addergroup)
- );
- $groups['add'] = array_unique( $groups['add'] );
- $groups['remove'] = array_unique( $groups['remove'] );
- $groups['add-self'] = array_unique( $groups['add-self'] );
- $groups['remove-self'] = array_unique( $groups['remove-self'] );
- }
-
- // Run a hook because we can
- wfRunHooks( 'UserrightsChangeableGroups', array( $this, $wgUser, $addergroups, &$groups ) );
-
- return $groups;
- }
-
- /**
- * Returns an array of the groups that a particular group can add/remove.
- *
- * @param $group String: the group to check for whether it can add/remove
- * @return Array array( 'add' => array( addablegroups ), 'remove' => array( removablegroups ) , 'add-self' => array( addablegroups to self), 'remove-self' => array( removable groups from self) )
- */
- private function changeableByGroup( $group ) {
- global $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf;
-
- $groups = array( 'add' => array(), 'remove' => array(), 'add-self' => array(), 'remove-self' => array() );
- if( empty($wgAddGroups[$group]) ) {
- // Don't add anything to $groups
- } elseif( $wgAddGroups[$group] === true ) {
- // You get everything
- $groups['add'] = User::getAllGroups();
- } elseif( is_array($wgAddGroups[$group]) ) {
- $groups['add'] = $wgAddGroups[$group];
- }
-
- // Same thing for remove
- if( empty($wgRemoveGroups[$group]) ) {
- } elseif($wgRemoveGroups[$group] === true ) {
- $groups['remove'] = User::getAllGroups();
- } elseif( is_array($wgRemoveGroups[$group]) ) {
- $groups['remove'] = $wgRemoveGroups[$group];
- }
-
- // Re-map numeric keys of AddToSelf/RemoveFromSelf to the 'user' key for backwards compatibility
- if( empty($wgGroupsAddToSelf['user']) || $wgGroupsAddToSelf['user'] !== true ) {
- foreach($wgGroupsAddToSelf as $key => $value) {
- if( is_int($key) ) {
- $wgGroupsAddToSelf['user'][] = $value;
- }
- }
- }
-
- if( empty($wgGroupsRemoveFromSelf['user']) || $wgGroupsRemoveFromSelf['user'] !== true ) {
- foreach($wgGroupsRemoveFromSelf as $key => $value) {
- if( is_int($key) ) {
- $wgGroupsRemoveFromSelf['user'][] = $value;
- }
- }
- }
-
- // Now figure out what groups the user can add to him/herself
- if( empty($wgGroupsAddToSelf[$group]) ) {
- } elseif( $wgGroupsAddToSelf[$group] === true ) {
- // No idea WHY this would be used, but it's there
- $groups['add-self'] = User::getAllGroups();
- } elseif( is_array($wgGroupsAddToSelf[$group]) ) {
- $groups['add-self'] = $wgGroupsAddToSelf[$group];
- }
-
- if( empty($wgGroupsRemoveFromSelf[$group]) ) {
- } elseif( $wgGroupsRemoveFromSelf[$group] === true ) {
- $groups['remove-self'] = User::getAllGroups();
- } elseif( is_array($wgGroupsRemoveFromSelf[$group]) ) {
- $groups['remove-self'] = $wgGroupsRemoveFromSelf[$group];
- }
-
- return $groups;
+ return $wgUser->changeableGroups();
}
/**
diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php
index 95e06f4b..7da6023e 100644
--- a/includes/specials/SpecialVersion.php
+++ b/includes/specials/SpecialVersion.php
@@ -12,21 +12,29 @@
class SpecialVersion extends SpecialPage {
private $firstExtOpened = true;
+ 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:
+ 'https://svn.wikimedia.org/viewvc/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki',
+ );
+
function __construct(){
- parent::__construct( 'Version' );
+ parent::__construct( 'Version' );
}
/**
* main()
*/
function execute( $par ) {
- global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks;
+ global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks, $wgContLang;
$wgMessageCache->loadAllMessages();
$this->setHeaders();
$this->outputHeader();
- $wgOut->addHTML( '<div dir="ltr">' );
+ $wgOut->addHTML( Xml::openElement( 'div',
+ array( 'dir' => $wgContLang->getDir() ) ) );
$text =
$this->MediaWikiCredits() .
$this->softwareInformation() .
@@ -47,13 +55,19 @@ class SpecialVersion extends SpecialPage {
* @return wiki text showing the license information
*/
static function MediaWikiCredits() {
- $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) ) .
- "__NOTOC__
+ global $wgContLang;
+
+ $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) );
+
+ // This text is always left-to-right.
+ $ret .= '<div dir="ltr">';
+ $ret .= "__NOTOC__
This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''',
- copyright (C) 2001-2009 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
+ copyright © 2001-2010 Magnus Manske, Brion Vibber, Lee Daniel Crocker,
Tim Starling, Erik Möller, Gabriel Wicke, Ævar Arnfjörð Bjarmason,
Niklas Laxström, Domas Mituzas, Rob Church, Yuri Astrakhan, Aryeh Gregor,
- Aaron Schulz and others.
+ Aaron Schulz, Andrew Garrett, Raimond Spekking, Alexandre Emsenhuber,
+ Siebrand Mazeland, Chad Horohoe and others.
MediaWiki is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -70,6 +84,7 @@ class SpecialVersion extends SpecialPage {
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online].
";
+ $ret .= '</div>';
return str_replace( "\t\t", '', $ret ) . "\n";
}
@@ -80,25 +95,30 @@ class SpecialVersion extends SpecialPage {
static function softwareInformation() {
$dbr = wfGetDB( DB_SLAVE );
- return Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
- Xml::openElement( 'table', array( 'id' => 'sv-software' ) ) .
+ // Put the software in an array of form 'name' => 'version'. All messages should
+ // be loaded here, so feel free to use wfMsg*() in the 'name'. Raw HTML or wikimarkup
+ // can be used
+ $software = array();
+ $software['[http://www.mediawiki.org/ MediaWiki]'] = self::getVersionLinked();
+ $software['[http://www.php.net/ PHP]'] = phpversion() . " (" . php_sapi_name() . ")";
+ $software[$dbr->getSoftwareLink()] = $dbr->getServerVersion();
+
+ // Allow a hook to add/remove items
+ wfRunHooks( 'SoftwareInfo', array( &$software ) );
+
+ $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-software' ) ) .
"<tr>
<th>" . wfMsg( 'version-software-product' ) . "</th>
<th>" . wfMsg( 'version-software-version' ) . "</th>
- </tr>\n
- <tr>
- <td>[http://www.mediawiki.org/ MediaWiki]</td>
- <td>" . self::getVersionLinked() . "</td>
- </tr>\n
- <tr>
- <td>[http://www.php.net/ PHP]</td>
- <td>" . phpversion() . " (" . php_sapi_name() . ")</td>
- </tr>\n
- <tr>
- <td>" . $dbr->getSoftwareLink() . "</td>
- <td>" . $dbr->getServerVersion() . "</td>
- </tr>\n" .
- Xml::closeElement( 'table' );
+ </tr>\n";
+ foreach( $software as $name => $version ) {
+ $out .= "<tr>
+ <td>" . $name . "</td>
+ <td>" . $version . "</td>
+ </tr>\n";
+ }
+ return $out . Xml::closeElement( 'table' );
}
/**
@@ -106,27 +126,52 @@ class SpecialVersion extends SpecialPage {
*
* @return mixed
*/
- public static function getVersion() {
+ public static function getVersion( $flags = '' ) {
global $wgVersion, $IP;
wfProfileIn( __METHOD__ );
- $svn = self::getSvnRevision( $IP );
- $version = $svn ? "$wgVersion (r$svn)" : $wgVersion;
+
+ $info = self::getSvnInfo( $IP );
+ if ( !$info ) {
+ $version = $wgVersion;
+ } elseif( $flags === 'nodb' ) {
+ $version = "$wgVersion (r{$info['checkout-rev']})";
+ } else {
+ $version = $wgVersion . ' ' .
+ wfMsg(
+ 'version-svn-revision',
+ isset( $info['directory-rev'] ) ? $info['directory-rev'] : '',
+ $info['checkout-rev']
+ );
+ }
+
wfProfileOut( __METHOD__ );
return $version;
}
/**
- * Return a string of the MediaWiki version with a link to SVN revision if
- * available
+ * Return a wikitext-formatted string of the MediaWiki version with a link to
+ * the SVN revision if available
*
* @return mixed
*/
public static function getVersionLinked() {
global $wgVersion, $IP;
wfProfileIn( __METHOD__ );
- $svn = self::getSvnRevision( $IP );
- $viewvc = 'http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/?pathrev=';
- $version = $svn ? "$wgVersion ([{$viewvc}{$svn} r$svn])" : $wgVersion;
+ $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 {
+ $version = "$wgVersion $linkText";
+ }
+ } else {
+ $version = $wgVersion;
+ }
wfProfileOut( __METHOD__ );
return $version;
}
@@ -148,64 +193,40 @@ class SpecialVersion extends SpecialPage {
wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) );
$out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) .
- Xml::openElement( 'table', array( 'id' => 'sv-ext' ) );
+ Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-ext' ) );
foreach ( $extensionTypes as $type => $text ) {
if ( isset ( $wgExtensionCredits[$type] ) && count ( $wgExtensionCredits[$type] ) ) {
- $out .= $this->openExtType( $text );
+ $out .= $this->openExtType( $text, 'credits-' . $type );
usort( $wgExtensionCredits[$type], array( $this, 'compare' ) );
foreach ( $wgExtensionCredits[$type] as $extension ) {
- $version = null;
- $subVersion = '';
- if ( isset( $extension['version'] ) ) {
- $version = $extension['version'];
- }
- if ( isset( $extension['svn-revision'] ) &&
- preg_match( '/\$(?:Rev|LastChangedRevision|Revision): *(\d+)/',
- $extension['svn-revision'], $m ) ) {
- $subVersion = 'r' . $m[1];
- }
-
- if( $version && $subVersion ) {
- $version = $version . ' [' . $subVersion . ']';
- } elseif ( !$version && $subVersion ) {
- $version = $subVersion;
- }
-
- $out .= $this->formatCredits(
- isset ( $extension['name'] ) ? $extension['name'] : '',
- $version,
- isset ( $extension['author'] ) ? $extension['author'] : '',
- isset ( $extension['url'] ) ? $extension['url'] : null,
- isset ( $extension['description'] ) ? $extension['description'] : '',
- isset ( $extension['descriptionmsg'] ) ? $extension['descriptionmsg'] : ''
- );
+ $out .= $this->formatCredits( $extension );
}
}
}
if ( count( $wgExtensionFunctions ) ) {
- $out .= $this->openExtType( wfMsg( 'version-extension-functions' ) );
- $out .= '<tr><td colspan="3">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
+ $out .= $this->openExtType( wfMsg( 'version-extension-functions' ), 'extension-functions' );
+ $out .= '<tr><td colspan="4">' . $this->listToText( $wgExtensionFunctions ) . "</td></tr>\n";
}
if ( $cnt = count( $tags = $wgParser->getTags() ) ) {
for ( $i = 0; $i < $cnt; ++$i )
$tags[$i] = "&lt;{$tags[$i]}&gt;";
- $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ) );
- $out .= '<tr><td colspan="3">' . $this->listToText( $tags ). "</td></tr>\n";
+ $out .= $this->openExtType( wfMsg( 'version-parser-extensiontags' ), 'parser-tags' );
+ $out .= '<tr><td colspan="4">' . $this->listToText( $tags ). "</td></tr>\n";
}
if( $cnt = count( $fhooks = $wgParser->getFunctionHooks() ) ) {
- $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ) );
- $out .= '<tr><td colspan="3">' . $this->listToText( $fhooks ) . "</td></tr>\n";
+ $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' ) );
- $out .= '<tr><td colspan="3">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n";
+ $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;
@@ -223,23 +244,72 @@ class SpecialVersion extends SpecialPage {
}
}
- function formatCredits( $name, $version = null, $author = null, $url = null, $description = null, $descriptionMsg = null ) {
- $extension = isset( $url ) ? "[$url $name]" : $name;
- $version = isset( $version ) ? "(" . wfMsg( 'version-version' ) . " $version)" : '';
+ function formatCredits( $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;
+ $checkoutRev = isset( $svnInfo['checkout-rev'] ) ? $svnInfo['checkout-rev'] : null;
+ $viewvcUrl = isset( $svnInfo['viewvc-url'] ) ? $svnInfo['viewvc-url'] : null;
+ } else {
+ $directoryRev = null;
+ $checkoutRev = null;
+ $viewvcUrl = null;
+ }
+
+ # Make main link (or just the name if there is no URL)
+ if ( isset( $extension['url'] ) ) {
+ $mainLink = "[{$extension['url']} $name]";
+ } else {
+ $mainLink = $name;
+ }
+ if ( isset( $extension['version'] ) ) {
+ $versionText = '<span class="mw-version-ext-version">' .
+ wfMsg( 'version-version', $extension['version'] ) .
+ '</span>';
+ } else {
+ $versionText = '';
+ }
- # Look for a localized description
- if( isset( $descriptionMsg ) ) {
- $msg = wfMsg( $descriptionMsg );
- if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
- $description = $msg;
+ # Make subversion text/link
+ if ( $checkoutRev ) {
+ $svnText = wfMsg( 'version-svn-revision', $directoryRev, $checkoutRev );
+ $svnText = isset( $viewvcUrl ) ? "[$viewvcUrl $svnText]" : $svnText;
+ } else {
+ $svnText = false;
+ }
+
+ # 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 );
+ } else {
+ $msg = wfMsg( $descriptionMsg );
}
+ if ( !wfEmptyMsg( $descriptionMsg, $msg ) && $msg != '' ) {
+ $description = $msg;
+ }
}
- return "<tr>
- <td><em>$extension $version</em></td>
- <td>$description</td>
- <td>" . $this->listToText( (array)$author ) . "</td>
+ if ( $svnText !== false ) {
+ $extNameVer = "<tr>
+ <td><em>$mainLink $versionText</em></td>
+ <td><em>$svnText</em></td>";
+ } else {
+ $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>
</tr>\n";
+ return $extNameVer . $extDescAuthor;
}
/**
@@ -253,7 +323,7 @@ class SpecialVersion extends SpecialPage {
ksort( $myWgHooks );
$ret = Xml::element( 'h2', array( 'id' => 'mw-version-hooks' ), wfMsg( 'version-hooks' ) ) .
- Xml::openElement( 'table', array( 'id' => 'sv-hooks' ) ) .
+ Xml::openElement( 'table', array( 'class' => 'wikitable', 'id' => 'sv-hooks' ) ) .
"<tr>
<th>" . wfMsg( 'version-hook-name' ) . "</th>
<th>" . wfMsg( 'version-hook-subscribedby' ) . "</th>
@@ -271,19 +341,20 @@ class SpecialVersion extends SpecialPage {
return '';
}
- private function openExtType($text, $name = null) {
- $opt = array( 'colspan' => 3 );
+ private function openExtType( $text, $name = null ) {
+ $opt = array( 'colspan' => 4 );
$out = '';
- if(!$this->firstExtOpened) {
+ if( !$this->firstExtOpened ) {
// Insert a spacing line
$out .= '<tr class="sv-space">' . Xml::element( 'td', $opt ) . "</tr>\n";
}
$this->firstExtOpened = false;
- if($name) { $opt['id'] = "sv-$name"; }
+ if( $name )
+ $opt['id'] = "sv-$name";
- $out .= "<tr>" . Xml::element( 'th', $opt, $text) . "</tr>\n";
+ $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n";
return $out;
}
@@ -298,9 +369,10 @@ class SpecialVersion extends SpecialPage {
/**
* @param array $list
+ * @param bool $sort
* @return string
*/
- function listToText( $list ) {
+ function listToText( $list, $sort = true ) {
$cnt = count( $list );
if ( $cnt == 1 ) {
@@ -310,7 +382,9 @@ class SpecialVersion extends SpecialPage {
return '';
} else {
global $wgLang;
- sort( $list );
+ if ( $sort ) {
+ sort( $list );
+ }
return $wgLang->listToText( array_map( array( __CLASS__, 'arrayToString' ), $list ) );
}
}
@@ -338,12 +412,20 @@ class SpecialVersion extends SpecialPage {
}
/**
- * Retrieve the revision number of a Subversion working directory.
+ * 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.
*
- * @param string $dir
- * @return mixed revision number as int, or false if not a SVN checkout
+ * Returned keys are:
+ * Required:
+ * checkout-rev The revision which was checked out
+ * Optional:
+ * directory-rev The revision when the directory was last modified
+ * url The subversion URL of the directory
+ * repo-url The base URL of the repository
+ * viewvc-url A ViewVC URL pointing to the checked-out revision
*/
- public static function getSvnRevision( $dir ) {
+ public static function getSvnInfo( $dir ) {
// http://svnbook.red-bean.com/nightly/en/svn.developer.insidewc.html
$entries = $dir . '/.svn/entries';
@@ -351,10 +433,13 @@ class SpecialVersion extends SpecialPage {
return false;
}
- $content = file( $entries );
+ $lines = file( $entries );
+ if ( !count( $lines ) ) {
+ return false;
+ }
// check if file is xml (subversion release <= 1.3) or not (subversion release = 1.4)
- if( preg_match( '/^<\?xml/', $content[0] ) ) {
+ if( preg_match( '/^<\?xml/', $lines[0] ) ) {
// subversion is release <= 1.3
if( !function_exists( 'simplexml_load_file' ) ) {
// We could fall back to expat... YUCK
@@ -371,15 +456,52 @@ class SpecialVersion extends SpecialPage {
if( $xml->entry[0]['name'] == '' ) {
// The directory entry should always have a revision marker.
if( $entry['revision'] ) {
- return intval( $entry['revision'] );
+ return array( 'checkout-rev' => intval( $entry['revision'] ) );
}
}
}
}
return false;
+ }
+
+ // subversion is release 1.4 or above
+ 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'],
+ self::$viewvcUrls[$info['repo-url']],
+ $info['url']
+ );
+ $pathRelativeToRepo = substr( $info['url'], strlen( $info['repo-url'] ) );
+ $viewvc .= '/?pathrev=';
+ $viewvc .= urlencode( $info['checkout-rev'] );
+ $info['viewvc-url'] = $viewvc;
+ }
+ return $info;
+ }
+
+ /**
+ * Retrieve the revision number of a Subversion working directory.
+ *
+ * @param String $dir Directory of the svn checkout
+ * @return int revision number as int
+ */
+ public static function getSvnRevision( $dir ) {
+ $info = self::getSvnInfo( $dir );
+ if ( $info === false ) {
+ return false;
+ } elseif ( isset( $info['checkout-rev'] ) ) {
+ return $info['checkout-rev'];
} else {
- // subversion is release 1.4
- return intval( $content[3] );
+ return false;
}
}
diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php
index 7497f9be..5e5a4f17 100644
--- a/includes/specials/SpecialWantedcategories.php
+++ b/includes/specials/SpecialWantedcategories.php
@@ -13,20 +13,12 @@
* @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
-class WantedCategoriesPage extends QueryPage {
+class WantedCategoriesPage extends WantedQueryPage {
function getName() {
return 'Wantedcategories';
}
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
function getSQL() {
$dbr = wfGetDB( DB_SLAVE );
list( $categorylinks, $page ) = $dbr->tableNamesN( 'categorylinks', 'page' );
@@ -45,32 +37,21 @@ class WantedCategoriesPage extends QueryPage {
";
}
- function sortDescending() { return true; }
-
- /**
- * Fetch user page links and cache their existence
- */
- function preprocessResults( $db, $res ) {
- $batch = new LinkBatch;
- while ( $row = $db->fetchObject( $res ) )
- $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 $wgLang, $wgContLang;
$nt = Title::makeTitle( $result->namespace, $result->title );
- $text = $wgContLang->convert( $nt->getText() );
+ $text = htmlspecialchars( $wgContLang->convert( $nt->getText() ) );
$plink = $this->isCached() ?
- $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) :
- $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) );
+ $skin->link( $nt, $text ) :
+ $skin->link(
+ $nt,
+ $text,
+ array(),
+ array(),
+ array( 'broken' )
+ );
$nlinks = wfMsgExt( 'nmembers', array( 'parsemag', 'escape'),
$wgLang->formatNum( $result->value ) );
diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php
index 4957531e..189b9d8b 100644
--- a/includes/specials/SpecialWantedfiles.php
+++ b/includes/specials/SpecialWantedfiles.php
@@ -13,20 +13,12 @@
* @copyright Copyright © 2008, Soxred93
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
-class WantedFilesPage extends QueryPage {
+class WantedFilesPage extends WantedQueryPage {
function getName() {
return 'Wantedfiles';
}
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
function getSQL() {
$dbr = wfGetDB( DB_SLAVE );
list( $imagelinks, $page ) = $dbr->tableNamesN( 'imagelinks', 'page' );
@@ -44,55 +36,6 @@ class WantedFilesPage extends QueryPage {
GROUP BY il_to
";
}
-
- function sortDescending() { return true; }
-
- /**
- * Fetch user page links and cache their existence
- */
- function preprocessResults( $db, $res ) {
- $batch = new LinkBatch;
- while ( $row = $db->fetchObject( $res ) )
- $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 $wgLang, $wgContLang;
-
- $nt = Title::makeTitle( $result->namespace, $result->title );
- $text = $wgContLang->convert( $nt->getText() );
-
- $plink = $this->isCached() ?
- $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) :
- $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) );
-
- return wfSpecialList(
- $plink,
- $this->makeWlhLink( $nt, $skin, $result )
- );
- }
-
- /**
- * Make a "what links here" link for a given title
- *
- * @param Title $title Title to make the link for
- * @param Skin $skin Skin to use
- * @param object $result Result row
- * @return string
- */
- private function makeWlhLink( $title, $skin, $result ) {
- global $wgLang;
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' );
- $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
- return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) );
- }
}
/**
diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php
index 7307b335..eeca87ab 100644
--- a/includes/specials/SpecialWantedpages.php
+++ b/includes/specials/SpecialWantedpages.php
@@ -8,7 +8,7 @@
* implements Special:Wantedpages
* @ingroup SpecialPage
*/
-class WantedPagesPage extends QueryPage {
+class WantedPagesPage extends WantedQueryPage {
var $nlinks;
function WantedPagesPage( $inc = false, $nlinks = true ) {
@@ -20,11 +20,6 @@ class WantedPagesPage extends QueryPage {
return 'Wantedpages';
}
- function isExpensive() {
- return true;
- }
- function isSyndicated() { return false; }
-
function getSQL() {
global $wgWantedPagesThreshold;
$count = $wgWantedPagesThreshold - 1;
@@ -32,83 +27,23 @@ class WantedPagesPage extends QueryPage {
$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 ( 2, 3 )
- AND pg2.page_namespace != 8
- GROUP BY pl_namespace, pl_title
- HAVING COUNT(*) > $count";
+ 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;
}
-
- /**
- * Cache page existence for performance
- */
- function preprocessResults( $db, $res ) {
- $batch = new LinkBatch;
- while ( $row = $db->fetchObject( $res ) )
- $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 );
- }
-
- /**
- * Format an individual result
- *
- * @param $skin Skin to use for UI elements
- * @param $result Result row
- * @return string
- */
- public function formatResult( $skin, $result ) {
- $title = Title::makeTitleSafe( $result->namespace, $result->title );
- if( $title instanceof Title ) {
- if( $this->isCached() ) {
- $pageLink = $title->exists()
- ? '<s>' . $skin->makeLinkObj( $title ) . '</s>'
- : $skin->makeBrokenLinkObj( $title );
- } else {
- $pageLink = $skin->makeBrokenLinkObj( $title );
- }
- return wfSpecialList( $pageLink, $this->makeWlhLink( $title, $skin, $result ) );
- } else {
- $tsafe = htmlspecialchars( $result->title );
- return wfMsg( 'wantedpages-badtitle', $tsafe );
- }
- }
-
- /**
- * Make a "what links here" link for a specified result if required
- *
- * @param $title Title to make the link for
- * @param $skin Skin to use
- * @param $result Result row
- * @return string
- */
- private function makeWlhLink( $title, $skin, $result ) {
- global $wgLang;
- if( $this->nlinks ) {
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' );
- $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
- return $skin->makeKnownLinkObj( $wlh, $label, 'target=' . $title->getPrefixedUrl() );
- } else {
- return null;
- }
- }
-
}
/**
diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php
index 7dd9a262..329d7a3f 100644
--- a/includes/specials/SpecialWantedtemplates.php
+++ b/includes/specials/SpecialWantedtemplates.php
@@ -15,20 +15,12 @@
* @copyright Copyright © 2008, Danny B.
* @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later
*/
-class WantedTemplatesPage extends QueryPage {
+class WantedTemplatesPage extends WantedQueryPage {
function getName() {
return 'Wantedtemplates';
}
- function isExpensive() {
- return true;
- }
-
- function isSyndicated() {
- return false;
- }
-
function getSQL() {
$dbr = wfGetDB( DB_SLAVE );
list( $templatelinks, $page ) = $dbr->tableNamesN( 'templatelinks', 'page' );
@@ -45,55 +37,6 @@ class WantedTemplatesPage extends QueryPage {
GROUP BY tl_namespace, tl_title
";
}
-
- function sortDescending() { return true; }
-
- /**
- * Fetch user page links and cache their existence
- */
- function preprocessResults( $db, $res ) {
- $batch = new LinkBatch;
- while ( $row = $db->fetchObject( $res ) )
- $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 $wgLang, $wgContLang;
-
- $nt = Title::makeTitle( $result->namespace, $result->title );
- $text = $wgContLang->convert( $nt->getText() );
-
- $plink = $this->isCached() ?
- $skin->makeLinkObj( $nt, htmlspecialchars( $text ) ) :
- $skin->makeBrokenLinkObj( $nt, htmlspecialchars( $text ) );
-
- return wfSpecialList(
- $plink,
- $this->makeWlhLink( $nt, $skin, $result )
- );
- }
-
- /**
- * Make a "what links here" link for a given title
- *
- * @param Title $title Title to make the link for
- * @param Skin $skin Skin to use
- * @param object $result Result row
- * @return string
- */
- private function makeWlhLink( $title, $skin, $result ) {
- global $wgLang;
- $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' );
- $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ),
- $wgLang->formatNum( $result->value ) );
- return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) );
- }
}
/**
diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php
index b14577b5..c32af2ae 100644
--- a/includes/specials/SpecialWatchlist.php
+++ b/includes/specials/SpecialWatchlist.php
@@ -12,7 +12,25 @@
function wfSpecialWatchlist( $par ) {
global $wgUser, $wgOut, $wgLang, $wgRequest;
global $wgRCShowWatchingUsers, $wgEnotifWatchlist, $wgShowUpdatedMarker;
- global $wgEnotifWatchlist;
+
+ // Add feed links
+ $wlToken = $wgUser->getOption( 'watchlisttoken' );
+ if (!$wlToken) {
+ $wlToken = sha1( mt_rand() . microtime( true ) );
+ $wgUser->setOption( 'watchlisttoken', $wlToken );
+ $wgUser->saveSettings();
+ }
+
+ global $wgServer, $wgScriptPath, $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' );
@@ -21,8 +39,12 @@ function wfSpecialWatchlist( $par ) {
# Anons don't get a watchlist
if( $wgUser->isAnon() ) {
$wgOut->setPageTitle( wfMsg( 'watchnologin' ) );
- $llink = $skin->makeKnownLinkObj( SpecialPage::getTitleFor( 'Userlogin' ),
- wfMsgHtml( 'loginreqlink' ), 'returnto=' . $specialTitle->getPrefixedUrl() );
+ $llink = $skin->linkKnown(
+ SpecialPage::getTitleFor( 'Userlogin' ),
+ wfMsgHtml( 'loginreqlink' ),
+ array(),
+ array( 'returnto' => $specialTitle->getPrefixedText() )
+ );
$wgOut->addHTML( wfMsgWikiHtml( 'watchlistanontext', $llink ) );
return;
}
@@ -248,42 +270,17 @@ function wfSpecialWatchlist( $par ) {
$cutofflinks = "\n" . wlCutoffLinks( $days, 'Watchlist', $nondefaults ) . "<br />\n";
- # Spit out some control panel links
$thisTitle = SpecialPage::getTitleFor( 'Watchlist' );
- $skin = $wgUser->getSkin();
- $showLinktext = wfMsgHtml( 'show' );
- $hideLinktext = wfMsgHtml( 'hide' );
- # Hide/show minor edits
- $label = $hideMinor ? $showLinktext : $hideLinktext;
- $linkBits = wfArrayToCGI( array( 'hideMinor' => 1 - (int)$hideMinor ), $nondefaults );
- $links[] = wfMsgHtml( 'rcshowhideminor', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) );
-
- # Hide/show bot edits
- $label = $hideBots ? $showLinktext : $hideLinktext;
- $linkBits = wfArrayToCGI( array( 'hideBots' => 1 - (int)$hideBots ), $nondefaults );
- $links[] = wfMsgHtml( 'rcshowhidebots', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) );
-
- # Hide/show anonymous edits
- $label = $hideAnons ? $showLinktext : $hideLinktext;
- $linkBits = wfArrayToCGI( array( 'hideAnons' => 1 - (int)$hideAnons ), $nondefaults );
- $links[] = wfMsgHtml( 'rcshowhideanons', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) );
-
- # Hide/show logged in edits
- $label = $hideLiu ? $showLinktext : $hideLinktext;
- $linkBits = wfArrayToCGI( array( 'hideLiu' => 1 - (int)$hideLiu ), $nondefaults );
- $links[] = wfMsgHtml( 'rcshowhideliu', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) );
-
- # Hide/show own edits
- $label = $hideOwn ? $showLinktext : $hideLinktext;
- $linkBits = wfArrayToCGI( array( 'hideOwn' => 1 - (int)$hideOwn ), $nondefaults );
- $links[] = wfMsgHtml( 'rcshowhidemine', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) );
-
- # Hide/show patrolled edits
+ # 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 );
+
if( $wgUser->useRCPatrol() ) {
- $label = $hidePatrolled ? $showLinktext : $hideLinktext;
- $linkBits = wfArrayToCGI( array( 'hidePatrolled' => 1 - (int)$hidePatrolled ), $nondefaults );
- $links[] = wfMsgHtml( 'rcshowhidepatr', $skin->makeKnownLinkObj( $thisTitle, $label, $linkBits ) );
+ $links[] = wlShowHideLink( $nondefaults, 'rcshowhidepatr', 'hidePatrolled', $hidePatrolled );
}
# Namespace filter and put the whole form together.
@@ -311,6 +308,8 @@ function wfSpecialWatchlist( $par ) {
$form .= Xml::closeElement( 'fieldset' );
$wgOut->addHTML( $form );
+ $wgOut->addHTML( ChangesList::flagLegend() );
+
# If there's nothing to show, stop here
if( $numRows == 0 ) {
$wgOut->addWikiMsg( 'watchnochange' );
@@ -334,7 +333,8 @@ function wfSpecialWatchlist( $par ) {
$dbr->dataSeek( $res, 0 );
$list = ChangesList::newFromUser( $wgUser );
-
+ $list->setWatchlistDivs();
+
$s = $list->beginRecentChangesList();
$counter = 1;
while ( $obj = $dbr->fetchObject( $res ) ) {
@@ -368,23 +368,53 @@ function wfSpecialWatchlist( $par ) {
$wgOut->addHTML( $s );
}
+function wlShowHideLink( $options, $message, $name, $value ) {
+ global $wgUser;
+
+ $showLinktext = wfMsgHtml( 'show' );
+ $hideLinktext = wfMsgHtml( 'hide' );
+ $title = SpecialPage::getTitleFor( 'Watchlist' );
+ $skin = $wgUser->getSkin();
+
+ $label = $value ? $showLinktext : $hideLinktext;
+ $options[$name] = 1 - (int) $value;
+
+ return wfMsgHtml( $message, $skin->linkKnown( $title, $label, array(), $options ) );
+}
+
+
function wlHoursLink( $h, $page, $options = array() ) {
global $wgUser, $wgLang, $wgContLang;
+
$sk = $wgUser->getSkin();
- $s = $sk->makeKnownLink(
- $wgContLang->specialPage( $page ),
- $wgLang->formatNum( $h ),
- wfArrayToCGI( array('days' => ($h / 24.0)), $options ) );
+ $title = Title::newFromText( $wgContLang->specialPage( $page ) );
+ $options['days'] = ($h / 24.0);
+
+ $s = $sk->linkKnown(
+ $title,
+ $wgLang->formatNum( $h ),
+ array(),
+ $options
+ );
+
return $s;
}
function wlDaysLink( $d, $page, $options = array() ) {
global $wgUser, $wgLang, $wgContLang;
+
$sk = $wgUser->getSkin();
- $s = $sk->makeKnownLink(
- $wgContLang->specialPage( $page ),
- ($d ? $wgLang->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) ),
- wfArrayToCGI( array('days' => $d), $options ) );
+ $title = Title::newFromText( $wgContLang->specialPage( $page ) );
+ $options['days'] = $d;
+ $message = ($d ? $wgLang->formatNum( $d ) : wfMsgHtml( 'watchlistall2' ) );
+
+ $s = $sk->linkKnown(
+ $title,
+ $message,
+ array(),
+ $options
+ );
+
return $s;
}
diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php
index 3f485bd8..b63c0eee 100644
--- a/includes/specials/SpecialWhatlinkshere.php
+++ b/includes/specials/SpecialWhatlinkshere.php
@@ -7,40 +7,29 @@
*/
/**
- * Entry point
- * @param $par String: An article name ??
- */
-function wfSpecialWhatlinkshere($par = NULL) {
- global $wgRequest;
- $page = new WhatLinksHerePage( $wgRequest, $par );
- $page->execute();
-}
-
-/**
* implements Special:Whatlinkshere
* @ingroup SpecialPage
*/
-class WhatLinksHerePage {
- // Stored data
- protected $par;
+class SpecialWhatLinksHere extends SpecialPage {
// Stored objects
protected $opts, $target, $selfTitle;
// Stored globals
- protected $skin, $request;
+ protected $skin;
protected $limits = array( 20, 50, 100, 250, 500 );
- function WhatLinksHerePage( $request, $par = null ) {
+ public function __construct() {
+ parent::__construct( 'Whatlinkshere' );
global $wgUser;
- $this->request = $request;
$this->skin = $wgUser->getSkin();
- $this->par = $par;
}
- function execute() {
- global $wgOut;
+ function execute( $par ) {
+ global $wgOut, $wgRequest;
+
+ $this->setHeaders();
$opts = new FormOptions();
@@ -54,12 +43,12 @@ class WhatLinksHerePage {
$opts->add( 'hidelinks', false );
$opts->add( 'hideimages', false );
- $opts->fetchValuesFromRequest( $this->request );
+ $opts->fetchValuesFromRequest( $wgRequest );
$opts->validateIntBounds( 'limit', 0, 5000 );
// Give precedence to subpage syntax
- if ( isset($this->par) ) {
- $opts->setValue( 'target', $this->par );
+ if ( isset($par) ) {
+ $opts->setValue( 'target', $par );
}
// Bind to member variable
@@ -271,8 +260,18 @@ class WhatLinksHerePage {
}
}
- $suppressRedirect = $row->page_is_redirect ? 'redirect=no' : '';
- $link = $this->skin->makeKnownLinkObj( $nt, '', $suppressRedirect );
+ if( $row->page_is_redirect ) {
+ $query = array( 'redirect' => 'no' );
+ } else {
+ $query = array();
+ }
+
+ $link = $this->skin->linkKnown(
+ $nt,
+ null,
+ array(),
+ $query
+ );
// Display properties (redirect or template)
$propsText = '';
@@ -306,12 +305,21 @@ class WhatLinksHerePage {
if ( $title === null )
$title = SpecialPage::getTitleFor( 'Whatlinkshere' );
- $targetText = $target->getPrefixedUrl();
- return $this->skin->makeKnownLinkObj( $title, $text, 'target=' . $targetText );
+ return $this->skin->linkKnown(
+ $title,
+ $text,
+ array(),
+ array( 'target' => $target->getPrefixedText() )
+ );
}
function makeSelfLink( $text, $query ) {
- return $this->skin->makeKnownLinkObj( $this->selfTitle, $text, $query );
+ return $this->skin->linkKnown(
+ $this->selfTitle,
+ $text,
+ array(),
+ $query
+ );
}
function getPrevNext( $prevId, $nextId ) {
@@ -326,18 +334,18 @@ class WhatLinksHerePage {
if ( 0 != $prevId ) {
$overrides = array( 'from' => $this->opts->getValue( 'back' ) );
- $prev = $this->makeSelfLink( $prev, wfArrayToCGI( $overrides, $changed ) );
+ $prev = $this->makeSelfLink( $prev, array_merge( $changed, $overrides ) );
}
if ( 0 != $nextId ) {
$overrides = array( 'from' => $nextId, 'back' => $prevId );
- $next = $this->makeSelfLink( $next, wfArrayToCGI( $overrides, $changed ) );
+ $next = $this->makeSelfLink( $next, array_merge( $changed, $overrides ) );
}
$limitLinks = array();
foreach ( $this->limits as $limit ) {
$prettyLimit = $wgLang->formatNum( $limit );
$overrides = array( 'limit' => $limit );
- $limitLinks[] = $this->makeSelfLink( $prettyLimit, wfArrayToCGI( $overrides, $changed ) );
+ $limitLinks[] = $this->makeSelfLink( $prettyLimit, array_merge( $changed, $overrides ) );
}
$nums = $wgLang->pipeList( $limitLinks );
@@ -346,7 +354,7 @@ class WhatLinksHerePage {
}
function whatlinkshereForm() {
- global $wgScript, $wgTitle;
+ global $wgScript;
// We get nicer value from the title object
$this->opts->consumeValue( 'target' );
@@ -360,7 +368,7 @@ class WhatLinksHerePage {
$f = Xml::openElement( 'form', array( 'action' => $wgScript ) );
# Values that should not be forgotten
- $f .= Xml::hidden( 'title', $wgTitle->getPrefixedText() );
+ $f .= Xml::hidden( 'title', SpecialPage::getTitleFor( 'Whatlinkshere' )->getPrefixedText() );
foreach ( $this->opts->getUnconsumedValues() as $name => $value ) {
$f .= Xml::hidden( $name, $value );
}
@@ -388,6 +396,11 @@ class WhatLinksHerePage {
return $f;
}
+ /**
+ * Create filter panel
+ *
+ * @return string HTML fieldset and filter panel with the show/hide links
+ */
function getFilterPanel() {
global $wgLang;
$show = wfMsgHtml( 'show' );
@@ -400,11 +413,14 @@ class WhatLinksHerePage {
$types = array( 'hidetrans', 'hidelinks', 'hideredirs' );
if( $this->target->getNamespace() == NS_FILE )
$types[] = 'hideimages';
+
+ // Combined message keys: 'whatlinkshere-hideredirs', 'whatlinkshere-hidetrans', 'whatlinkshere-hidelinks', 'whatlinkshere-hideimages'
+ // To be sure they will be find by grep
foreach( $types as $type ) {
$chosen = $this->opts->getValue( $type );
- $msg = wfMsgHtml( "whatlinkshere-{$type}", $chosen ? $show : $hide );
+ $msg = $chosen ? $show : $hide;
$overrides = array( $type => !$chosen );
- $links[] = $this->makeSelfLink( $msg, wfArrayToCGI( $overrides, $changed ) );
+ $links[] = wfMsgHtml( "whatlinkshere-{$type}", $this->makeSelfLink( $msg, array_merge( $changed, $overrides ) ) );
}
return Xml::fieldset( wfMsg( 'whatlinkshere-filters' ), $wgLang->pipeList( $links ) );
}
diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php
index 2092e43b..a5d60d2f 100644
--- a/includes/specials/SpecialWithoutinterwiki.php
+++ b/includes/specials/SpecialWithoutinterwiki.php
@@ -53,7 +53,7 @@ class WithoutInterwikiPage extends PageQueryPage {
function getSQL() {
$dbr = wfGetDB( DB_SLAVE );
list( $page, $langlinks ) = $dbr->tableNamesN( 'page', 'langlinks' );
- $prefix = $this->prefix ? "AND page_title LIKE '" . $dbr->escapeLike( $this->prefix ) . "%'" : '';
+ $prefix = $this->prefix ? 'AND page_title' . $dbr->buildLike( $this->prefix , $dbr->anyString() ) : '';
return
"SELECT 'Withoutinterwiki' AS type,
page_namespace AS namespace,
@@ -75,13 +75,10 @@ class WithoutInterwikiPage extends PageQueryPage {
}
function wfSpecialWithoutinterwiki() {
- global $wgRequest, $wgContLang, $wgCapitalLinks;
+ global $wgRequest, $wgContLang;
list( $limit, $offset ) = wfCheckLimits();
- if( $wgCapitalLinks ) {
- $prefix = $wgContLang->ucfirst( $wgRequest->getVal( 'prefix' ) );
- } else {
- $prefix = $wgRequest->getVal( 'prefix' );
- }
+ // Only searching the mainspace anyway
+ $prefix = Title::capitalize( $wgRequest->getVal( 'prefix' ), NS_MAIN );
$wip = new WithoutInterwikiPage();
$wip->setPrefix( $prefix );
$wip->doQuery( $offset, $limit );