diff options
Diffstat (limited to 'includes/specials')
84 files changed, 4874 insertions, 3763 deletions
diff --git a/includes/specials/SpecialActiveusers.php b/includes/specials/SpecialActiveusers.php index 7d907fb5..f016ab92 100644 --- a/includes/specials/SpecialActiveusers.php +++ b/includes/specials/SpecialActiveusers.php @@ -1,37 +1,40 @@ <?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 +/** + * Implements Special:Activeusers + * + * Copyright © 2008 Aaron Schulz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** * 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 - + global $wgRequest, $wgActiveUserDays; + $this->RCMaxAge = $wgActiveUserDays; $un = $wgRequest->getText( 'username' ); $this->requestedUser = ''; if ( $un != '' ) { @@ -40,15 +43,15 @@ class ActiveUsersPager extends UsersPager { $this->requestedUser = $username->getText(); } } - + $this->setupOptions(); - + parent::__construct(); } public function setupOptions() { global $wgRequest; - + $this->opts = new FormOptions(); $this->opts->add( 'hidebots', false, FormOptions::BOOL ); @@ -57,10 +60,12 @@ class ActiveUsersPager extends UsersPager { $this->opts->fetchValuesFromRequest( $wgRequest ); $this->groups = array(); - if ($this->opts->getValue('hidebots') == 1) + if ( $this->opts->getValue( 'hidebots' ) == 1 ) { $this->groups['bot'] = true; - if ($this->opts->getValue('hidesysops') == 1) + } + if ( $this->opts->getValue( 'hidesysops' ) == 1 ) { $this->groups['sysop'] = true; + } } function getIndexField() { @@ -72,7 +77,8 @@ class ActiveUsersPager extends UsersPager { $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'"; - + $conds[] = "rc_timestamp >= '{$dbr->timestamp( wfTimestamp( TS_UNIX ) - $this->RCMaxAge*24*3600 )}'"; + if( $this->requestedUser != '' ) { $conds[] = 'rc_user_text >= ' . $dbr->addQuotes( $this->requestedUser ); } @@ -101,14 +107,15 @@ class ActiveUsersPager extends UsersPager { 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])) + if ( isset( $this->groups[$group] ) ) { return; + } $list[] = self::buildGroupLink( $group ); } $groups = $wgLang->commaList( $list ); @@ -126,14 +133,14 @@ class ActiveUsersPager extends UsersPager { } function getPageHeader() { - global $wgScript, $wgRequest; + global $wgScript; $self = $this->getTitle(); - $limit = $this->mLimit ? Xml::hidden( 'limit', $this->mLimit ) : ''; + $limit = $this->mLimit ? Html::hidden( 'limit', $this->mLimit ) : ''; - $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); # Form tag + $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 .= Html::hidden( 'title', $self->getPrefixedDBkey() ) . $limit . "\n"; $out .= Xml::inputLabel( wfMsg( 'activeusers-from' ), 'username', 'offset', 20, $this->requestedUser ) . '<br />';# Username field @@ -141,10 +148,10 @@ class ActiveUsersPager extends UsersPager { $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::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n";# Submit button and form bottom $out .= Xml::closeElement( 'fieldset' ); $out .= Xml::closeElement( 'form' ); - + return $out; } } @@ -157,7 +164,7 @@ class SpecialActiveUsers extends SpecialPage { /** * Constructor */ - public function __construct() { + public function __construct() { parent::__construct( 'Activeusers' ); } @@ -167,18 +174,19 @@ class SpecialActiveUsers extends SpecialPage { * @param $par Mixed: parameter passed to the page or null */ public function execute( $par ) { - global $wgOut, $wgLang, $wgRCMaxAge; + global $wgOut, $wgLang, $wgActiveUserDays; $this->setHeaders(); + $this->outputHeader(); $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 = Html::rawElement( 'div', array( 'class' => 'mw-activeusers-intro' ), + wfMsgExt( 'activeusers-intro', array( 'parsemag', 'escape' ), $wgLang->formatNum( $wgActiveUserDays ) ) + ); $s .= $up->getPageHeader(); if( $usersbody ) { diff --git a/includes/specials/SpecialAllmessages.php b/includes/specials/SpecialAllmessages.php index 1745bf6c..296c6f50 100644 --- a/includes/specials/SpecialAllmessages.php +++ b/includes/specials/SpecialAllmessages.php @@ -1,6 +1,29 @@ <?php /** + * Implements Special:Allmessages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** * Use this special page to get a list of the MediaWiki system messages. + * * @file * @ingroup SpecialPage */ @@ -58,7 +81,7 @@ class SpecialAllmessages extends SpecialPage { $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-allmessages-form' ) ) . Xml::fieldset( wfMsg( 'allmessages-filter-legend' ) ) . - Xml::hidden( 'title', $this->getTitle() ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . Xml::openElement( 'table', array( 'class' => 'mw-allmessages-table' ) ) . "\n" . '<tr> <td class="mw-label">' . @@ -77,19 +100,19 @@ class SpecialAllmessages extends SpecialPage { 'filter', 'unmodified', 'mw-allmessages-form-filter-unmodified', - ( $this->filter == 'unmodified' ? true : false ) + ( $this->filter == 'unmodified' ) ) . Xml::radioLabel( wfMsg( 'allmessages-filter-all' ), 'filter', 'all', 'mw-allmessages-form-filter-all', - ( $this->filter == 'all' ? true : false ) + ( $this->filter == 'all' ) ) . Xml::radioLabel( wfMsg( 'allmessages-filter-modified' ), 'filter', 'modified', 'mw-allmessages-form-filter-modified', - ( $this->filter == 'modified' ? true : false ) + ( $this->filter == 'modified' ) ) . "</td>\n </tr> @@ -101,7 +124,7 @@ class SpecialAllmessages extends SpecialPage { Xml::openElement( 'select', array( 'id' => 'mw-allmessages-form-lang', 'name' => 'lang' ) ); foreach( $languages as $lang => $name ) { - $selected = $lang == $this->langCode ? true : false; + $selected = $lang == $this->langCode; $out .= Xml::option( $lang . ' - ' . $name, $lang, $selected ) . "\n"; } $out .= Xml::closeElement( 'select' ) . @@ -134,10 +157,7 @@ class AllmessagesTablePager extends TablePager { $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') ); + $this->mLimitsShown = array( 20, 50, 100, 250, 500, 5000 ); global $wgLang, $wgContLang, $wgRequest; @@ -182,8 +202,8 @@ class AllmessagesTablePager extends TablePager { // Normalise message names so they look like page titles $messageNames = array_map( array( $this->lang, 'ucfirst' ), $messageNames ); - wfProfileIn( __METHOD__ ); + wfProfileOut( __METHOD__ ); return $messageNames; } @@ -207,7 +227,7 @@ class AllmessagesTablePager extends TablePager { $pageFlags = $talkFlags = array(); - while( $s = $dbr->fetchObject( $res ) ) { + foreach ( $res as $s ) { if( $s->page_namespace == NS_MEDIAWIKI ) { if( $this->foreign ) { $title = explode( '/', $s->page_title ); @@ -223,7 +243,6 @@ class AllmessagesTablePager extends TablePager { $talkFlags[$s->page_title] = true; } } - $dbr->freeResult( $res ); wfProfileOut( __METHOD__ . '-db' ); @@ -327,7 +346,7 @@ class AllmessagesTablePager extends TablePager { $s .= Xml::openElement( 'tr', $this->getRowAttrs( $row, true ) ); $formatted = strval( $this->formatValue( 'am_actual', $row->am_actual ) ); if ( $formatted == '' ) { - $formatted = ' '; + $formatted = ' '; } $s .= Xml::tags( 'td', $this->getCellAttrs( 'am_actual', $row->am_actual ), $formatted ) . "</tr>\n"; @@ -375,43 +394,4 @@ class AllmessagesTablePager extends TablePager { 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 19816dcd..5fa1aa47 100644 --- a/includes/specials/SpecialAllpages.php +++ b/includes/specials/SpecialAllpages.php @@ -1,7 +1,29 @@ <?php - /** * Implements Special:Allpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** + * Implements Special:Allpages + * * @ingroup SpecialPage */ class SpecialAllpages extends IncludableSpecialPage { @@ -32,8 +54,8 @@ class SpecialAllpages extends IncludableSpecialPage { /** * Entry point : initialise variables and call subfunctions. + * * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL) - * @param $specialPage See the SpecialPage object. */ function execute( $par ) { global $wgRequest, $wgOut, $wgContLang; @@ -66,9 +88,10 @@ class SpecialAllpages extends IncludableSpecialPage { /** * HTML for the top form - * @param integer $namespace A namespace constant (default NS_MAIN). - * @param string $from dbKey we are starting listing at. - * @param string $to dbKey we are ending listing at. + * + * @param $namespace Integer: a namespace constant (default NS_MAIN). + * @param $from String: dbKey we are starting listing at. + * @param $to String: dbKey we are ending listing at. */ function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '' ) { global $wgScript; @@ -76,7 +99,7 @@ class SpecialAllpages extends IncludableSpecialPage { $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $out .= Xml::hidden( 'title', $t->getPrefixedText() ); + $out .= Html::hidden( 'title', $t->getPrefixedText() ); $out .= Xml::openElement( 'fieldset' ); $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) ); $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); @@ -113,7 +136,9 @@ class SpecialAllpages extends IncludableSpecialPage { } /** - * @param integer $namespace (default NS_MAIN) + * @param $namespace Integer (default NS_MAIN) + * @param $from String: list all pages from this name + * @param $to String: list all pages to this name */ function showToplevel( $namespace = NS_MAIN, $from = '', $to = '' ) { global $wgOut; @@ -164,7 +189,8 @@ class SpecialAllpages extends IncludableSpecialPage { array ('LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC') ); - if( $s = $dbr->fetchObject( $res ) ) { + $s = $dbr->fetchObject( $res ); + if( $s ) { array_push( $lines, $s->page_title ); } else { // Final chunk, but ended prematurely. Go back and find the end. @@ -174,7 +200,8 @@ class SpecialAllpages extends IncludableSpecialPage { array_push( $lines, $endTitle ); $done = true; } - if( $s = $res->fetchObject() ) { + $s = $res->fetchObject(); + if( $s ) { array_push( $lines, $s->page_title ); $lastTitle = $s->page_title; } else { @@ -234,9 +261,10 @@ class SpecialAllpages extends IncludableSpecialPage { /** * Show a line of "ABC to DEF" ranges of articles - * @param string $inpoint Lower limit of pagenames - * @param string $outpout Upper limit of pagenames - * @param integer $namespace (Default NS_MAIN) + * + * @param $inpoint String: lower limit of pagenames + * @param $outpoint String: upper limit of pagenames + * @param $namespace Integer (Default NS_MAIN) */ function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) { global $wgContLang; @@ -258,9 +286,9 @@ class SpecialAllpages extends IncludableSpecialPage { } /** - * @param integer $namespace (Default NS_MAIN) - * @param string $from list all pages from this name (default FALSE) - * @param string $to list all pages to this name (default FALSE) + * @param $namespace Integer (Default NS_MAIN) + * @param $from String: list all pages from this name (default FALSE) + * @param $to String: list all pages to this name (default FALSE) */ function showChunk( $namespace = NS_MAIN, $from = false, $to = false ) { global $wgOut, $wgUser, $wgContLang, $wgLang; @@ -280,7 +308,7 @@ class SpecialAllpages extends IncludableSpecialPage { $namespace = NS_MAIN; } else { list( $namespace, $fromKey, $from ) = $fromList; - list( $namespace2, $toKey, $to ) = $toList; + list( , $toKey, $to ) = $toList; $dbr = wfGetDB( DB_SLAVE ); $conds = array( @@ -316,7 +344,7 @@ class SpecialAllpages extends IncludableSpecialPage { if( $n % 3 == 0 ) { $out .= '<tr>'; } - $out .= "<td width=\"33%\">$link</td>"; + $out .= "<td style=\"width:33%\">$link</td>"; $n++; if( $n % 3 == 0 ) { $out .= "</tr>\n"; @@ -437,8 +465,8 @@ class SpecialAllpages extends IncludableSpecialPage { } /** - * @param int $ns the namespace of the article - * @param string $text the name of the article + * @param $ns Integer: the namespace of the article + * @param $text String: the name of the article * @return array( int namespace, string dbkey, string pagename ) or NULL on error * @static (sort of) * @access private diff --git a/includes/specials/SpecialAncientpages.php b/includes/specials/SpecialAncientpages.php index 92192435..2d5047d2 100644 --- a/includes/specials/SpecialAncientpages.php +++ b/includes/specials/SpecialAncientpages.php @@ -1,11 +1,29 @@ <?php /** + * Implements Special:Ancientpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** * Implements Special:Ancientpages + * * @ingroup SpecialPage */ class AncientPagesPage extends QueryPage { @@ -21,28 +39,10 @@ class AncientPagesPage extends QueryPage { function isSyndicated() { return false; } function getSQL() { - global $wgDBtype; $db = wfGetDB( DB_SLAVE ); $page = $db->tableName( 'page' ); $revision = $db->tableName( 'revision' ); - - 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)'; - } + $epoch = $db->unixTimestamp( 'rev_timestamp' ); return "SELECT 'Ancientpages' as type, diff --git a/includes/specials/SpecialBlankpage.php b/includes/specials/SpecialBlankpage.php index e1fadd02..aaa45a80 100644 --- a/includes/specials/SpecialBlankpage.php +++ b/includes/specials/SpecialBlankpage.php @@ -1,5 +1,27 @@ <?php /** + * Implements Special:Blankpage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** * Special page designed for basic benchmarking of * MediaWiki since it doesn't really do much. * diff --git a/includes/specials/SpecialBlockip.php b/includes/specials/SpecialBlockip.php index 16720dd1..28a0f3f1 100644 --- a/includes/specials/SpecialBlockip.php +++ b/includes/specials/SpecialBlockip.php @@ -1,52 +1,78 @@ <?php /** - * Constructor for Special:Blockip page + * Implements Special:Blockip + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage */ /** - * Constructor + * A special page that allows users with 'block' right to block users from + * editing pages and other actions + * + * @ingroup SpecialPage */ -function wfSpecialBlockip( $par ) { - global $wgUser, $wgOut, $wgRequest; +class IPBlockForm extends SpecialPage { + var $BlockAddress, $BlockExpiry, $BlockReason, $BlockReasonList, $BlockOther, $BlockAnonOnly, $BlockCreateAccount, + $BlockEnableAutoblock, $BlockEmail, $BlockHideName, $BlockAllowUsertalk, $BlockReblock; + // The maximum number of edits a user can have and still be hidden + const HIDEUSER_CONTRIBLIMIT = 1000; - # Can't block when the database is locked - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Permission check - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; + public function __construct() { + parent::__construct( 'Blockip', 'block' ); } - $ipb = new IPBlockForm( $par ); + public function execute( $par ) { + global $wgUser, $wgOut, $wgRequest; - $action = $wgRequest->getVal( 'action' ); - if( 'success' == $action ) { - $ipb->showSuccess(); - } elseif( $wgRequest->wasPosted() && 'submit' == $action && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $ipb->doSubmit(); - } else { - $ipb->showForm( '' ); - } -} + # Can't block when the database is locked + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + # Permission check + if( !$this->userCanExecute( $wgUser ) ) { + $wgOut->permissionRequired( 'block' ); + return; + } -/** - * Form object for the Special:Blockip page. - * - * @ingroup SpecialPage - */ -class IPBlockForm { - var $BlockAddress, $BlockExpiry, $BlockReason; - // The maximum number of edits a user can have and still be hidden - const HIDEUSER_CONTRIBLIMIT = 1000; + $this->setup( $par ); + + # bug 15810: blocked admins should have limited access here + if ( $wgUser->isBlocked() ) { + $status = IPBlockForm::checkUnblockSelf( $this->BlockAddress ); + if ( $status !== true ) { + throw new ErrorPageError( 'badaccess', $status ); + } + } - public function __construct( $par ) { + $action = $wgRequest->getVal( 'action' ); + if( 'success' == $action ) { + $this->showSuccess(); + } elseif( $wgRequest->wasPosted() && 'submit' == $action && + $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $this->doSubmit(); + } else { + $this->showForm( '' ); + } + } + + private function setup( $par ) { global $wgRequest, $wgUser, $wgBlockAllowsUTEdit; $this->BlockAddress = $wgRequest->getVal( 'wpBlockAddress', $wgRequest->getVal( 'ip', $par ) ); @@ -105,7 +131,7 @@ class IPBlockForm { $msg = wfMsgReal( $key, $err ); $wgOut->setSubtitle( wfMsgHtml( 'formerror' ) ); $wgOut->addHTML( Xml::tags( 'p', array( 'class' => 'error' ), $msg ) ); - } elseif( $this->BlockAddress ) { + } elseif( $this->BlockAddress !== null ) { # Get other blocks, i.e. from GlobalBlocking or TorBlock extension wfRunHooks( 'OtherBlockLogLink', array( &$otherBlockedMsgs, $this->BlockAddress ) ); @@ -149,7 +175,7 @@ class IPBlockForm { # Username/IP is blocked already locally if( $alreadyBlocked ) { - $wgOut->addWikiMsg( 'ipb-needreblock', $this->BlockAddress ); + $wgOut->wrapWikiMsg( "<div class='mw-ipb-needreblock'>\n$1\n</div>", array( 'ipb-needreblock', $this->BlockAddress ) ); } $scBlockExpiryOptions = wfMsgForContent( 'ipboptions' ); @@ -163,16 +189,15 @@ class IPBlockForm { list( $show, $value ) = explode( ':', $option ); $show = htmlspecialchars( $show ); $value = htmlspecialchars( $value ); - $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ? true : false ) . "\n"; + $blockExpiryFormOptions .= Xml::option( $show, $value, $this->BlockExpiry === $value ) . "\n"; } $reasonDropDown = Xml::listDropDown( 'wpBlockReasonList', wfMsgForContent( 'ipbreason-dropdown' ), wfMsgForContent( 'ipbreasonotherlist' ), $this->BlockReasonList, 'wpBlockDropDown', 4 ); - global $wgStylePath, $wgStyleVersion; + $wgOut->addModules( 'mediawiki.legacy.block' ); $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( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'blockip-legend' ) ) . @@ -242,7 +267,7 @@ class IPBlockForm { </td> </tr> <tr id='wpAnonOnlyRow'> - <td> </td> + <td> </td> <td class='mw-input'>" . Xml::checkLabel( wfMsg( 'ipbanononly' ), 'wpAnonOnly', 'wpAnonOnly', $this->BlockAnonOnly, @@ -250,7 +275,7 @@ class IPBlockForm { </td> </tr> <tr id='wpCreateAccountRow'> - <td> </td> + <td> </td> <td class='mw-input'>" . Xml::checkLabel( wfMsg( 'ipbcreateaccount' ), 'wpCreateAccount', 'wpCreateAccount', $this->BlockCreateAccount, @@ -258,7 +283,7 @@ class IPBlockForm { </td> </tr> <tr id='wpEnableAutoblockRow'> - <td> </td> + <td> </td> <td class='mw-input'>" . Xml::checkLabel( wfMsg( 'ipbenableautoblock' ), 'wpEnableAutoblock', 'wpEnableAutoblock', $this->BlockEnableAutoblock, @@ -270,7 +295,7 @@ class IPBlockForm { if( self::canBlockEmail( $wgUser ) ) { $wgOut->addHTML(" <tr id='wpEnableEmailBan'> - <td> </td> + <td> </td> <td class='mw-input'>" . Xml::checkLabel( wfMsg( 'ipbemailban' ), 'wpEmailBan', 'wpEmailBan', $this->BlockEmail, @@ -284,7 +309,7 @@ class IPBlockForm { if( $wgUser->isAllowed( 'hideuser' ) ) { $wgOut->addHTML(" <tr id='wpEnableHideUser'> - <td> </td> + <td> </td> <td class='mw-input'><strong>" . Xml::checkLabel( wfMsg( 'ipbhidename' ), 'wpHideName', 'wpHideName', $this->BlockHideName, @@ -299,7 +324,7 @@ class IPBlockForm { if( $wgUser->isLoggedIn() ) { $wgOut->addHTML(" <tr id='wpEnableWatchUser'> - <td> </td> + <td> </td> <td class='mw-input'>" . Xml::checkLabel( wfMsg( 'ipbwatchuser' ), 'wpWatchUser', 'wpWatchUser', $this->BlockWatchUser, @@ -314,7 +339,7 @@ class IPBlockForm { if( $wgBlockAllowsUTEdit ){ $wgOut->addHTML(" <tr id='wpAllowUsertalkRow'> - <td> </td> + <td> </td> <td class='mw-input'>" . Xml::checkLabel( wfMsg( 'ipballowusertalk' ), 'wpAllowUsertalk', 'wpAllowUsertalk', $this->BlockAllowUsertalk, @@ -326,18 +351,18 @@ class IPBlockForm { $wgOut->addHTML(" <tr> - <td style='padding-top: 1em'> </td> + <td style='padding-top: 1em'> </td> <td class='mw-submit' style='padding-top: 1em'>" . Xml::submitButton( wfMsg( $alreadyBlocked ? 'ipb-change-block' : 'ipbsubmit' ), - array( 'name' => 'wpBlock', 'tabindex' => '13', 'accesskey' => 's' ) ) . " + array( 'name' => 'wpBlock', 'tabindex' => '13' ) + + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'blockip-block' ) ). " </td> </tr>" . Xml::closeElement( 'table' ) . - Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . - ( $alreadyBlocked ? Xml::hidden( 'wpChangeBlock', 1 ) : "" ) . + Html::hidden( 'wpEditToken', $wgUser->editToken() ) . + ( $alreadyBlocked ? Html::hidden( 'wpChangeBlock', 1 ) : "" ) . Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . - Xml::tags( 'script', array( 'type' => 'text/javascript' ), 'updateBlockOptions()' ) . "\n" + Xml::closeElement( 'form' ) ); $wgOut->addHTML( $this->getConvenienceLinks() ); @@ -353,13 +378,39 @@ class IPBlockForm { /** * Can we do an email block? - * @param User $user The sysop wanting to make a block - * @return boolean + * @param $user User: the sysop wanting to make a block + * @return Boolean */ public static function canBlockEmail( $user ) { global $wgEnableUserEmail, $wgSysopEmailBans; return ( $wgEnableUserEmail && $wgSysopEmailBans && $user->isAllowed( 'blockemail' ) ); } + + /** + * bug 15810: blocked admins should not be able to block/unblock + * others, and probably shouldn't be able to unblock themselves + * either. + * @param $user User, Int or String + */ + public static function checkUnblockSelf( $user ) { + global $wgUser; + if ( is_int( $user ) ) { + $user = User::newFromId( $user ); + } elseif ( is_string( $user ) ) { + $user = User::newFromName( $user ); + } + if( $user instanceof User && $user->getId() == $wgUser->getId() ) { + # User is trying to unblock themselves + if ( $wgUser->isAllowed( 'unblockself' ) ) { + return true; + } else { + return 'ipbnounblockself'; + } + } else { + # User is trying to block/unblock someone else + return 'ipbblocked'; + } + } /** * Backend block code. @@ -382,7 +433,7 @@ class IPBlockForm { $matches = array(); if( preg_match( "/^($rxIP4)\\/(\\d{1,2})$/", $this->BlockAddress, $matches ) ) { # IPv4 - if( $wgSysopRangeBans ) { + if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv4'] != 32 ) { if( !IP::isIPv4( $this->BlockAddress ) || $matches[2] > 32 ) { return array( 'ip_range_invalid' ); } elseif ( $matches[2] < $wgBlockCIDRLimit['IPv4'] ) { @@ -395,7 +446,7 @@ class IPBlockForm { } } elseif( preg_match( "/^($rxIP6)\\/(\\d{1,3})$/", $this->BlockAddress, $matches ) ) { # IPv6 - if( $wgSysopRangeBans ) { + if( $wgSysopRangeBans && $wgBlockCIDRLimit['IPv6'] != 128 ) { if( !IP::isIPv6( $this->BlockAddress ) || $matches[2] > 128 ) { return array( 'ip_range_invalid' ); } elseif( $matches[2] < $wgBlockCIDRLimit['IPv6'] ) { @@ -410,7 +461,7 @@ class IPBlockForm { # Username block if( $wgSysopUserBans ) { $user = User::newFromName( $this->BlockAddress ); - if( !is_null( $user ) && $user->getId() ) { + if( $user instanceof User && $user->getId() ) { # Use canonical name $userId = $user->getId(); $this->BlockAddress = $user->getName(); @@ -642,7 +693,7 @@ class IPBlockForm { ); // Add suppression block entries if allowed - if( $wgUser->isAllowed( 'hideuser' ) ) { + if( $wgUser->isAllowed( 'suppressionlog' ) ) { LogEventsList::showLogExtract( $out, 'suppress', $title->getPrefixedText(), '', array( 'lim' => 10, @@ -759,32 +810,20 @@ class IPBlockForm { * @return string */ private function getBlockListLink( $skin ) { - $list = SpecialPage::getTitleFor( 'Ipblocklist' ); - $query = array(); - - if( $this->BlockAddress ) { - $addr = strtr( $this->BlockAddress, '_', ' ' ); - $message = wfMsg( 'ipb-blocklist-addr', $addr ); - $query['ip'] = $this->BlockAddress; - } else { - $message = wfMsg( 'ipb-blocklist' ); - } - return $skin->linkKnown( - $list, - htmlspecialchars( $message ), - array(), - $query + SpecialPage::getTitleFor( 'Ipblocklist' ), + wfMsg( 'ipb-blocklist' ) ); } /** * 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 + * + * @param $users Array + * @param $reason String + * @param $tag String: replaces user pages + * @param $talkTag String: replaces user talk pages + * @return Array: list of html-safe usernames */ public static function doMassUserBlock( $users, $reason = '', $tag = '', $talkTag = '' ) { global $wgUser; diff --git a/includes/specials/SpecialBlockme.php b/includes/specials/SpecialBlockme.php index f222e3c6..f5131f5f 100644 --- a/includes/specials/SpecialBlockme.php +++ b/includes/specials/SpecialBlockme.php @@ -1,37 +1,59 @@ <?php /** + * Implements Special:Blockme + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** + * A special page called by proxy_check.php to block open proxies * + * @ingroup SpecialPage */ -function wfSpecialBlockme() { - global $wgRequest, $wgBlockOpenProxies, $wgOut, $wgProxyKey; - - $ip = wfGetIP(); +class SpecialBlockme extends UnlistedSpecialPage { - if( !$wgBlockOpenProxies || $wgRequest->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) { - $wgOut->addWikiMsg( 'proxyblocker-disabled' ); - return; + function __construct() { + parent::__construct( 'Blockme' ); } - $blockerName = wfMsg( "proxyblocker" ); - $reason = wfMsg( "proxyblockreason" ); - - $u = User::newFromName( $blockerName ); - $id = $u->idForName(); - if ( !$id ) { - $u = User::newFromName( $blockerName ); - $u->addToDatabase(); - $u->setPassword( bin2hex( mt_rand(0, 0x7fffffff ) ) ); - $u->saveSettings(); - $id = $u->getID(); - } + function execute( $par ) { + global $wgRequest, $wgOut, $wgBlockOpenProxies, $wgProxyKey; + + $this->setHeaders(); + $this->outputHeader(); + + $ip = wfGetIP(); + if( !$wgBlockOpenProxies || $wgRequest->getText( 'ip' ) != md5( $ip . $wgProxyKey ) ) { + $wgOut->addWikiMsg( 'proxyblocker-disabled' ); + return; + } - $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() ); - $block->insert(); + $user = User::newFromName( wfMsgForContent( 'proxyblocker' ) ); + if ( !$user->isLoggedIn() ) { + $user->addToDatabase(); + } + $id = $user->getId(); + $reason = wfMsg( 'proxyblockreason' ); - $wgOut->addWikiMsg( "proxyblocksuccess" ); + $block = new Block( $ip, 0, $id, $reason, wfTimestampNow() ); + $block->insert(); + + $wgOut->addWikiMsg( 'proxyblocksuccess' ); + } } diff --git a/includes/specials/SpecialBooksources.php b/includes/specials/SpecialBooksources.php index 8ee5467a..67fb5404 100644 --- a/includes/specials/SpecialBooksources.php +++ b/includes/specials/SpecialBooksources.php @@ -1,4 +1,25 @@ <?php +/** + * Implements Special:Booksources + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** * Special page outputs information on sourcing a book with a particular ISBN @@ -35,7 +56,7 @@ class SpecialBookSources extends SpecialPage { $wgOut->addHTML( $this->makeForm() ); if( strlen( $this->isbn ) > 0 ) { if( !self::isValidISBN( $this->isbn ) ) { - $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1</div>", 'booksources-invalid-isbn' ); + $wgOut->wrapWikiMsg( "<div class=\"error\">\n$1\n</div>", 'booksources-invalid-isbn' ); } $this->showList(); } @@ -48,7 +69,6 @@ class SpecialBookSources extends SpecialPage { public static function isValidISBN( $isbn ) { $isbn = self::cleanIsbn( $isbn ); $sum = 0; - $check = -1; if( strlen( $isbn ) == 13 ) { for( $i = 0; $i < 12; $i++ ) { if($i % 2 == 0) { @@ -78,7 +98,6 @@ class SpecialBookSources extends SpecialPage { return false; } - /** * Trim ISBN and remove characters which aren't required * @@ -99,9 +118,9 @@ class SpecialBookSources extends SpecialPage { $title = self::getTitleFor( 'Booksources' ); $form = '<fieldset><legend>' . wfMsgHtml( 'booksources-search-legend' ) . '</legend>'; $form .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $form .= Xml::hidden( 'title', $title->getPrefixedText() ); + $form .= Html::hidden( 'title', $title->getPrefixedText() ); $form .= '<p>' . Xml::inputLabel( wfMsg( 'booksources-isbn' ), 'isbn', 'isbn', 20, $this->isbn ); - $form .= ' ' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>'; + $form .= ' ' . Xml::submitButton( wfMsg( 'booksources-go' ) ) . '</p>'; $form .= Xml::closeElement( 'form' ); $form .= '</fieldset>'; return $form; @@ -147,6 +166,6 @@ class SpecialBookSources extends SpecialPage { */ private function makeListItem( $label, $url ) { $url = str_replace( '$1', $this->isbn, $url ); - return '<li><a href="' . htmlspecialchars( $url ) . '">' . htmlspecialchars( $label ) . '</a></li>'; + return '<li><a href="' . htmlspecialchars( $url ) . '" class="external">' . htmlspecialchars( $label ) . '</a></li>'; } } diff --git a/includes/specials/SpecialBrokenRedirects.php b/includes/specials/SpecialBrokenRedirects.php index b6ae2ada..98b02126 100644 --- a/includes/specials/SpecialBrokenRedirects.php +++ b/includes/specials/SpecialBrokenRedirects.php @@ -1,12 +1,30 @@ <?php /** + * Implements Special:Brokenredirects + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * A special page listing redirects to non existent page. Those should be + * A special page listing redirects tonon existent page. Those should be * fixed to point to an existing page. + * * @ingroup SpecialPage */ class BrokenRedirectsPage extends PageQueryPage { @@ -61,7 +79,7 @@ class BrokenRedirectsPage extends PageQueryPage { // $toObj may very easily be false if the $result list is cached if ( !is_object( $toObj ) ) { - return '<s>' . $skin->link( $fromObj ) . '</s>'; + return '<del>' . $skin->link( $fromObj ) . '</del>'; } $from = $skin->linkKnown( diff --git a/includes/specials/SpecialCategories.php b/includes/specials/SpecialCategories.php index eb49fdbc..c2dd40cd 100644 --- a/includes/specials/SpecialCategories.php +++ b/includes/specials/SpecialCategories.php @@ -1,29 +1,57 @@ <?php /** + * Implements Special:Categories + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ -function wfSpecialCategories( $par=null ) { - global $wgOut, $wgRequest; +/** + * @ingroup SpecialPage + */ +class SpecialCategories extends SpecialPage { + + function __construct() { + parent::__construct( 'Categories' ); + } + + function execute( $par ) { + global $wgOut, $wgRequest; + + $this->setHeaders(); + $this->outputHeader(); + $wgOut->allowClickjacking(); - if( $par == '' ) { - $from = $wgRequest->getText( 'from' ); - } else { - $from = $par; + $from = $wgRequest->getText( 'from', $par ); + + $cap = new CategoryPager( $from ); + $cap->doQuery(); + + $wgOut->addHTML( + Html::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) . + wfMsgExt( 'categoriespagetext', array( 'parse' ), $cap->getNumRows() ) . + $cap->getStartForm( $from ) . + $cap->getNavigationBar() . + '<ul>' . $cap->getBody() . '</ul>' . + $cap->getNavigationBar() . + Html::closeElement( 'div' ) + ); } - $wgOut->allowClickjacking(); - $cap = new CategoryPager( $from ); - $cap->doQuery(); - $wgOut->addHTML( - XML::openElement( 'div', array('class' => 'mw-spcontent') ) . - wfMsgExt( 'categoriespagetext', array( 'parse' ), $cap->getNumRows() ) . - $cap->getStartForm( $from ) . - $cap->getNavigationBar() . - '<ul>' . $cap->getBody() . '</ul>' . - $cap->getNavigationBar() . - XML::closeElement( 'div' ) - ); } /** @@ -77,7 +105,7 @@ class CategoryPager extends AlphabeticPager { $this->mResult->rewind(); - while ( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { $batch->addObj( Title::makeTitleSafe( NS_CATEGORY, $row->cat_title ) ); } $batch->execute(); @@ -100,7 +128,7 @@ class CategoryPager extends AlphabeticPager { return Xml::tags( 'form', array( 'method' => 'get', 'action' => $wgScript ), - Xml::hidden( 'title', $t->getPrefixedText() ) . + Html::hidden( 'title', $t->getPrefixedText() ) . Xml::fieldset( wfMsg( 'categories' ), Xml::inputLabel( wfMsg( 'categoriesfrom' ), 'from', 'from', 20, $from ) . diff --git a/includes/specials/SpecialComparePages.php b/includes/specials/SpecialComparePages.php new file mode 100644 index 00000000..4650fc94 --- /dev/null +++ b/includes/specials/SpecialComparePages.php @@ -0,0 +1,170 @@ +<?php +/** + * Implements Special:ComparePages + * + * Copyright © 2010 Derk-Jan Hartman <hartman@videolan.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 + * + * @file + * @ingroup SpecialPage + */ + +/** + * Implements Special:ComparePages + * + * @ingroup SpecialPage + */ +class SpecialComparePages extends SpecialPage { + + // Stored objects + protected $opts, $skin; + + // Some internal settings + protected $showNavigation = false; + + public function __construct() { + parent::__construct( 'ComparePages' ); + } + + protected function setup( $par ) { + global $wgRequest, $wgUser; + + // Options + $opts = new FormOptions(); + $this->opts = $opts; // bind + $opts->add( 'page1', '' ); + $opts->add( 'page2', '' ); + $opts->add( 'rev1', '' ); + $opts->add( 'rev2', '' ); + $opts->add( 'action', '' ); + + // Set values + $opts->fetchValuesFromRequest( $wgRequest ); + + $title1 = Title::newFromText( $opts->getValue( 'page1' ) ); + $title2 = Title::newFromText( $opts->getValue( 'page2' ) ); + + if( $title1 && $title1->exists() && $opts->getValue( 'rev1' ) == '' ) { + $pda = new Article( $title1 ); + $pdi = $pda->getID(); + $pdLastRevision = Revision::loadFromPageId( wfGetDB( DB_SLAVE ), $pdi ); + $opts->setValue( 'rev1', $pdLastRevision->getId() ); + } elseif ( $opts->getValue( 'rev1' ) != '' ) { + $pdrev = Revision::newFromId( $opts->getValue( 'rev1' ) ); + if( $pdrev ) $opts->setValue( 'page1', $pdrev->getTitle()->getPrefixedText() ); + } + if( $title2 && $title2->exists() && $opts->getValue( 'rev2' ) == '' ) { + $pda = new Article( $title2 ); + $pdi = $pda->getID(); + $pdLastRevision = Revision::loadFromPageId( wfGetDB( DB_SLAVE ), $pdi ); + $opts->setValue('rev2', $pdLastRevision->getId() ); + } elseif ( $opts->getValue( 'rev2' ) != '' ) { + $pdrev = Revision::newFromId( $opts->getValue( 'rev2' ) ); + if( $pdrev ) $opts->setValue( 'page2', $pdrev->getTitle()->getPrefixedText() ); + } + + // Store some objects + $this->skin = $wgUser->getSkin(); + } + + /** + * Show a form for filtering namespace and username + * + * @param $par String + * @return String + */ + public function execute( $par ) { + $this->setHeaders(); + $this->outputHeader(); + + $this->setup( $par ); + + // Settings + $this->form(); + + if( $this->opts->getValue( 'rev1' ) && $this->opts->getValue( 'rev2' ) ) { + $de = new DifferenceEngine( null, + $this->opts->getValue( 'rev1' ), + $this->opts->getValue( 'rev2' ), + null, // rcid + ( $this->opts->getValue( 'action' ) == 'purge' ), + false ); + $de->showDiffPage( true ); + } + } + + protected function form() { + global $wgOut, $wgScript; + + // Consume values + $page1 = $this->opts->consumeValue( 'page1' ); + $page2 = $this->opts->consumeValue( 'page2' ); + $rev1 = $this->opts->consumeValue( 'rev1' ); + $rev2 = $this->opts->consumeValue( 'rev2' ); + + // Store query values in hidden fields so that form submission doesn't lose them + $hidden = array(); + foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { + $hidden[] = Html::hidden( $key, $value ); + } + $hidden = implode( "\n", $hidden ); + + $form = Html::openElement( 'form', array( 'action' => $wgScript ) ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . + Xml::fieldset( wfMsg( 'compare-selector' ) ) . + Html::openElement( 'table', array( 'id' => 'mw-diff-table', 'style' => 'width:100%' ) ) . + "<tr> + <td class='mw-label' style='width:10%'>" . + Html::element( 'label', array( 'for' => 'page1' ), wfMsg( 'compare-page1' ) ) . + "</td> + <td class='mw-input' style='width:40%'>" . + Html::input( 'page1', $page1, 'text', array( 'size' => 40, 'id' => 'page1' ) ) . + "</td> + <td class='mw-label' style='width:10%'>" . + Html::element( 'label', array( 'for' => 'page2' ), wfMsg( 'compare-page2' ) ) . + "</td> + <td class='mw-input' style='width:40%'>" . + Html::input( 'page2', $page2, 'text', array( 'size' => 40, 'id' => 'page2' ) ) . + "</td> + </tr>" . + "<tr> + <td class='mw-label'>" . + Html::element( 'label', array( 'for' => 'rev1' ), wfMsg( 'compare-rev1' ) ) . + "</td> + <td class='mw-input'>" . + Html::input( 'rev1', $rev1, 'text', array( 'size' => 8, 'id' => 'rev1' ) ) . + "</td> + <td class='mw-label'>" . + Html::element( 'label', array( 'for' => 'rev2' ), wfMsg( 'compare-rev2' ) ) . + "</td> + <td class='mw-input'>" . + Html::input( 'rev2', $rev2, 'text', array( 'size' => 8, 'id' => 'rev2' ) ) . + "</td> + </tr>" . + "<tr> <td></td> + <td class='mw-submit' colspan='3'>" . + Xml::submitButton( wfMsg( 'compare-submit' ) ) . + "</td> + </tr>" . + Html::closeElement( 'table' ) . + Html::closeElement( 'fieldset' ) . + $hidden . + Html::closeElement( 'form' ); + + $wgOut->addHTML( $form ); + } +} diff --git a/includes/specials/SpecialConfirmemail.php b/includes/specials/SpecialConfirmemail.php index 372a574c..e556a60b 100644 --- a/includes/specials/SpecialConfirmemail.php +++ b/includes/specials/SpecialConfirmemail.php @@ -1,4 +1,25 @@ <?php +/** + * Implements Special:Confirmemail and Special:Invalidateemail + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** * Special page allows users to request email confirmation message, and handles @@ -25,6 +46,12 @@ class EmailConfirmation extends UnlistedSpecialPage { function execute( $code ) { global $wgUser, $wgOut; $this->setHeaders(); + + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + if( empty( $code ) ) { if( $wgUser->isLoggedIn() ) { if( User::isValidEmailAddr( $wgUser->getEmail() ) ) { @@ -54,11 +81,11 @@ class EmailConfirmation extends UnlistedSpecialPage { function showRequestForm() { global $wgOut, $wgUser, $wgLang, $wgRequest; if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getText( 'token' ) ) ) { - $ok = $wgUser->sendConfirmationMail(); - if ( WikiError::isError( $ok ) ) { - $wgOut->addWikiMsg( 'confirmemail_sendfailed', $ok->toString() ); - } else { + $status = $wgUser->sendConfirmationMail(); + if ( $status->isGood() ) { $wgOut->addWikiMsg( 'confirmemail_sent' ); + } else { + $wgOut->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); } } else { if( $wgUser->isEmailConfirmed() ) { @@ -71,11 +98,11 @@ class EmailConfirmation extends UnlistedSpecialPage { $wgOut->addWikiMsg( 'emailauthenticated', $time, $d, $t ); } if( $wgUser->isEmailConfirmationPending() ) { - $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1</div>", 'confirmemail_pending' ); + $wgOut->wrapWikiMsg( "<div class=\"error mw-confirmemail-pending\">\n$1\n</div>", 'confirmemail_pending' ); } $wgOut->addWikiMsg( 'confirmemail_text' ); $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl() ) ); - $form .= Xml::hidden( 'token', $wgUser->editToken() ); + $form .= Html::hidden( 'token', $wgUser->editToken() ); $form .= Xml::submitButton( wfMsg( 'confirmemail_send' ) ); $form .= Xml::closeElement( 'form' ); $wgOut->addHTML( $form ); @@ -121,6 +148,13 @@ class EmailInvalidation extends UnlistedSpecialPage { function execute( $code ) { $this->setHeaders(); + + if ( wfReadOnly() ) { + global $wgOut; + $wgOut->readOnlyPage(); + return; + } + $this->attemptInvalidate( $code ); } diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index b5d6107a..cee01a7f 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -1,10 +1,32 @@ <?php /** - * Special:Contributions, show user contributions in a paged list + * Implements Special:Contributions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ - + +/** + * Special:Contributions, show user contributions in a paged list + * + * @ingroup SpecialPage + */ + class SpecialContributions extends SpecialPage { public function __construct() { @@ -12,7 +34,7 @@ class SpecialContributions extends SpecialPage { } public function execute( $par ) { - global $wgUser, $wgOut, $wgLang, $wgRequest; + global $wgUser, $wgOut, $wgRequest; $this->setHeaders(); $this->outputHeader(); @@ -34,6 +56,8 @@ class SpecialContributions extends SpecialPage { $this->opts['contribs'] = 'newbie'; } + $this->opts['deletedOnly'] = $wgRequest->getBool( 'deletedOnly' ); + if( !strlen( $target ) ) { $wgOut->addHTML( $this->getForm() ); return; @@ -41,6 +65,7 @@ class SpecialContributions extends SpecialPage { $this->opts['limit'] = $wgRequest->getInt( 'limit', $wgUser->getOption('rclimit') ); $this->opts['target'] = $target; + $this->opts['topOnly'] = $wgRequest->getBool( 'topOnly' ); $nt = Title::makeTitleSafe( NS_USER, $target ); if( !$nt ) { @@ -64,10 +89,10 @@ class SpecialContributions extends SpecialPage { $this->opts['namespace'] = ''; } - $this->opts['tagfilter'] = (string) $wgRequest->getVal( 'tagfilter' ); - + $this->opts['tagFilter'] = (string) $wgRequest->getVal( 'tagFilter' ); + // Allows reverts to have the bot flag in recent changes. It is just here to - // be passed in the form at the top of the page + // be passed in the form at the top of the page if( $wgUser->isAllowed( 'markbotedits' ) && $wgRequest->getBool( 'bot' ) ) { $this->opts['bot'] = '1'; } @@ -81,7 +106,7 @@ class SpecialContributions extends SpecialPage { $this->opts['year'] = $wgRequest->getIntOrNull( 'year' ); $this->opts['month'] = $wgRequest->getIntOrNull( 'month' ); } - + // Add RSS/atom links $this->setSyndicated(); $feedType = $wgRequest->getVal( 'feed' ); @@ -93,7 +118,14 @@ class SpecialContributions extends SpecialPage { $wgOut->addHTML( $this->getForm() ); - $pager = new ContribsPager( $target, $this->opts['namespace'], $this->opts['year'], $this->opts['month'] ); + $pager = new ContribsPager( array( + 'target' => $target, + 'namespace' => $this->opts['namespace'], + 'year' => $this->opts['year'], + 'month' => $this->opts['month'], + 'deletedOnly' => $this->opts['deletedOnly'], + 'topOnly' => $this->opts['topOnly'], + ) ); if( !$pager->getNumRows() ) { $wgOut->addWikiMsg( 'nocontribs', $target ); } else { @@ -132,7 +164,7 @@ class SpecialContributions extends SpecialPage { } } } - + protected function setSyndicated() { global $wgOut; $wgOut->setSyndicated( true ); @@ -141,8 +173,8 @@ class SpecialContributions extends SpecialPage { /** * Generates the subheading with links - * @param Title $nt @see Title object for the target - * @param integer $id User ID for the target + * @param $nt Title object for the target + * @param $id Integer: User ID for the target * @return String: appropriately-escaped HTML to be output literally * @todo Fixme: almost the same as getSubTitle in SpecialDeletedContributions.php. Could be combined. */ @@ -169,12 +201,12 @@ class SpecialContributions extends SpecialPage { wfMsgHtml( 'change-blocklink' ) ); $tools[] = $sk->linkKnown( # Unblock link - SpecialPage::getTitleFor( 'BlockList' ), + SpecialPage::getTitleFor( 'Ipblocklist' ), wfMsgHtml( 'unblocklink' ), array(), array( 'action' => 'unblock', - 'ip' => $nt->getDBkey() + 'ip' => $nt->getDBkey() ) ); } @@ -196,6 +228,14 @@ class SpecialContributions extends SpecialPage { ) ); } + # Uploads + $tools[] = $sk->linkKnown( + SpecialPage::getTitleFor( 'Listfiles' ), + wfMsgHtml( 'sp-contributions-uploads' ), + array(), + array( 'user' => $nt->getText() ) + ); + # Other logs link $tools[] = $sk->linkKnown( SpecialPage::getTitleFor( 'Log' ), @@ -236,7 +276,9 @@ class SpecialContributions extends SpecialPage { 'lim' => 1, 'showIfEmpty' => false, 'msgKey' => array( - 'sp-contributions-blocked-notice', + $userObj->isAnon() ? + 'sp-contributions-blocked-notice-anon' : + 'sp-contributions-blocked-notice', $nt->getText() # Support GENDER in 'sp-contributions-blocked-notice' ), 'offset' => '' # don't use $wgRequest parameter offset @@ -258,99 +300,103 @@ class SpecialContributions extends SpecialPage { /** * Generates the namespace selector form with hidden attributes. - * @param $this->opts Array: the options to be included. + * @return String: HTML fragment */ protected function getForm() { global $wgScript; - + $this->opts['title'] = $this->getTitle()->getPrefixedText(); if( !isset( $this->opts['target'] ) ) { $this->opts['target'] = ''; } else { $this->opts['target'] = str_replace( '_' , ' ' , $this->opts['target'] ); } - + if( !isset( $this->opts['namespace'] ) ) { $this->opts['namespace'] = ''; } - + if( !isset( $this->opts['contribs'] ) ) { $this->opts['contribs'] = 'user'; } - + if( !isset( $this->opts['year'] ) ) { $this->opts['year'] = ''; } - + if( !isset( $this->opts['month'] ) ) { $this->opts['month'] = ''; } - + if( $this->opts['contribs'] == 'newbie' ) { $this->opts['target'] = ''; } - if( !isset( $this->opts['tagfilter'] ) ) { - $this->opts['tagfilter'] = ''; + if( !isset( $this->opts['tagFilter'] ) ) { + $this->opts['tagFilter'] = ''; } - - $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - # Add hidden params for tracking + + if( !isset( $this->opts['topOnly'] ) ) { + $this->opts['topOnly'] = false; + } + + $f = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'class' => 'mw-contributions-form' ) ); + + # Add hidden params for tracking except for parameters in $skipParameters + $skipParameters = array( 'namespace', 'deletedOnly', 'target', 'contribs', 'year', 'month', 'topOnly' ); foreach ( $this->opts as $name => $value ) { - if( in_array( $name, array( 'namespace', 'target', 'contribs', 'year', 'month' ) ) ) { + if( in_array( $name, $skipParameters ) ) { continue; } - $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; + $f .= "\t" . Html::hidden( $name, $value ) . "\n"; } - $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagfilter'] ); - - $f .= '<fieldset>' . - Xml::element( 'legend', array(), wfMsg( 'sp-contributions-search' ) ) . - 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( 'parsemag' ) ), - 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ? true : false ) . ' ' . + $tagFilter = ChangeTags::buildTagFilterSelector( $this->opts['tagFilter'] ); + + $f .= Xml::fieldset( wfMsg( 'sp-contributions-search' ) ) . + Xml::radioLabel( wfMsgExt( 'sp-contributions-newbies', array( 'parsemag' ) ), + 'contribs', 'newbie' , 'newbie', $this->opts['contribs'] == 'newbie' ) . '<br />' . + Xml::radioLabel( wfMsgExt( 'sp-contributions-username', array( 'parsemag' ) ), + 'contribs' , 'user', 'user', $this->opts['contribs'] == 'user' ) . ' ' . 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'], '' ) . - '</span>' . - ( $tagFilter ? Xml::tags( 'p', null, implode( ' ', $tagFilter ) ) : '' ) . - Xml::openElement( 'p' ) . - '<span style="white-space: nowrap">' . - Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) . - '</span>' . ' ' . - Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) . - Xml::closeElement( 'p' ); - + Html::rawElement( 'span', array( 'style' => 'white-space: nowrap' ), + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + Xml::namespaceSelector( $this->opts['namespace'], '' ) + ) . + Xml::checkLabel( wfMsg( 'history-show-deleted' ), + 'deletedOnly', 'mw-show-deleted-only', $this->opts['deletedOnly'] ) . '<br />' . + Xml::tags( 'p', null, Xml::checkLabel( wfMsg( 'sp-contributions-toponly' ), + 'topOnly', 'mw-show-top-only', $this->opts['topOnly'] ) ) . + ( $tagFilter ? Xml::tags( 'p', null, implode( ' ', $tagFilter ) ) : '' ) . + Html::rawElement( 'p', array( 'style' => 'white-space: nowrap' ), + Xml::dateMenu( $this->opts['year'], $this->opts['month'] ) . ' ' . + Xml::submitButton( wfMsg( 'sp-contributions-submit' ) ) + ) . ' '; $explain = wfMsgExt( 'sp-contributions-explain', 'parseinline' ); - if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) + if( !wfEmptyMsg( 'sp-contributions-explain', $explain ) ) { $f .= "<p id='mw-sp-contributions-explain'>{$explain}</p>"; - - $f .= '</fieldset>' . + } + $f .= Xml::closeElement('fieldset' ) . Xml::closeElement( 'form' ); return $f; } - + /** * Output a subscription feed listing recent edits to this page. - * @param string $type + * @param $type String */ protected function feed( $type ) { - global $wgRequest, $wgFeed, $wgFeedClasses, $wgFeedLimit; + global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut; if( !$wgFeed ) { - global $wgOut; $wgOut->addWikiMsg( 'feed-unavailable' ); return; } if( !isset( $wgFeedClasses[$type] ) ) { - global $wgOut; $wgOut->addWikiMsg( 'feed-invalid' ); return; } @@ -360,19 +406,26 @@ class SpecialContributions extends SpecialPage { wfMsgExt( 'tagline', 'parsemag' ), $this->getTitle()->getFullUrl() . "/" . urlencode($this->opts['target']) ); - + // Already valid title $nt = Title::makeTitleSafe( NS_USER, $this->opts['target'] ); $target = $this->opts['target'] == 'newbies' ? 'newbies' : $nt->getText(); - - $pager = new ContribsPager( $target, $this->opts['namespace'], - $this->opts['year'], $this->opts['month'], $this->opts['tagfilter'] ); + + $pager = new ContribsPager( array( + 'target' => $target, + 'namespace' => $this->opts['namespace'], + 'year' => $this->opts['year'], + 'month' => $this->opts['month'], + 'tagFilter' => $this->opts['tagFilter'], + 'deletedOnly' => $this->opts['deletedOnly'], + 'topOnly' => $this->opts['topOnly'], + ) ); $pager->mLimit = min( $this->opts['limit'], $wgFeedLimit ); $feed->outHeader(); if( $pager->getNumRows() > 0 ) { - while( $row = $pager->mResult->fetchObject() ) { + foreach ( $pager->mResult as $row ) { $feed->outItem( $this->feedItem( $row ) ); } } @@ -380,10 +433,10 @@ class SpecialContributions extends SpecialPage { } protected function feedTitle() { - global $wgContLanguageCode, $wgSitename; + global $wgLanguageCode, $wgSitename; $page = SpecialPage::getPage( 'Contributions' ); $desc = $page->getDescription(); - return "$wgSitename - $desc [$wgContLanguageCode]"; + return "$wgSitename - $desc [$wgLanguageCode]"; } protected function feedItem( $row ) { @@ -413,7 +466,7 @@ class SpecialContributions extends SpecialPage { protected function feedItemDesc( $revision ) { if( $revision ) { return '<p>' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) . - htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . + htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . "</p>\n<hr />\n<div>" . nl2br( htmlspecialchars( $revision->getText() ) ) . "</div>"; } @@ -431,7 +484,7 @@ class ContribsPager extends ReverseChronologicalPager { var $namespace = '', $mDb; var $preventClickjacking = false; - function __construct( $target, $namespace = false, $year = false, $month = false, $tagFilter = false ) { + function __construct( $options ) { parent::__construct(); $msgs = array( 'uctop', 'diff', 'newarticle', 'rollbacklink', 'diff', 'hist', 'rev-delundel', 'pipe-separator' ); @@ -440,10 +493,15 @@ class ContribsPager extends ReverseChronologicalPager { $this->messages[$msg] = wfMsgExt( $msg, array( 'escapenoentities' ) ); } - $this->target = $target; - $this->namespace = $namespace; - $this->tagFilter = $tagFilter; + $this->target = isset( $options['target'] ) ? $options['target'] : ''; + $this->namespace = isset( $options['namespace'] ) ? $options['namespace'] : ''; + $this->tagFilter = isset( $options['tagFilter'] ) ? $options['tagFilter'] : false; + + $this->deletedOnly = !empty( $options['deletedOnly'] ); + $this->topOnly = !empty( $options['topOnly'] ); + $year = isset( $options['year'] ) ? $options['year'] : false; + $month = isset( $options['month'] ) ? $options['month'] : false; $this->getDateCond( $year, $month ); $this->mDb = wfGetDB( DB_SLAVE, 'contributions' ); @@ -458,7 +516,7 @@ class ContribsPager extends ReverseChronologicalPager { function getQueryInfo() { global $wgUser; list( $tables, $index, $userCond, $join_cond ) = $this->getUserCond(); - + $conds = array_merge( $userCond, $this->getNamespaceCond() ); // Paranoia: avoid brute force searches (bug 17342) if( !$wgUser->isAllowed( 'deletedhistory' ) ) { @@ -468,12 +526,12 @@ class ContribsPager extends ReverseChronologicalPager { ' != ' . Revision::SUPPRESSED_USER; } $join_cond['page'] = array( 'INNER JOIN', 'page_id=rev_page' ); - + $queryInfo = array( 'tables' => $tables, 'fields' => array( 'page_namespace', 'page_title', 'page_is_new', 'page_latest', 'page_is_redirect', - 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment', + 'page_len','rev_id', 'rev_page', 'rev_text_id', 'rev_timestamp', 'rev_comment', 'rev_minor_edit', 'rev_user', 'rev_user_text', 'rev_parent_id', 'rev_deleted' ), 'conds' => $conds, @@ -510,6 +568,12 @@ class ContribsPager extends ReverseChronologicalPager { $condition['rev_user_text'] = $this->target; $index = 'usertext_timestamp'; } + if( $this->deletedOnly ) { + $condition[] = "rev_deleted != '0'"; + } + if( $this->topOnly ) { + $condition[] = "rev_id = page_latest"; + } return array( $tables, $index, $condition, $join_conds ); } @@ -552,7 +616,6 @@ class ContribsPager extends ReverseChronologicalPager { $classes = array(); $page = Title::newFromRow( $row ); - $page->resetArticleId( $row->rev_page ); // use process cache $link = $sk->link( $page, htmlspecialchars( $page->getPrefixedText() ), @@ -560,7 +623,7 @@ class ContribsPager extends ReverseChronologicalPager { $page->isRedirect() ? array( 'redirect' => 'no' ) : array() ); # Mark current revisions - $difftext = $topmarktext = ''; + $topmarktext = ''; if( $row->rev_id == $row->page_latest ) { $topmarktext .= '<span class="mw-uctop">' . $this->messages['uctop'] . '</span>'; # Add rollback link @@ -594,15 +657,18 @@ class ContribsPager extends ReverseChronologicalPager { $comment = $wgContLang->getDirMark() . $sk->revComment( $rev, false, true ); $date = $wgLang->timeanddate( wfTimestamp( TS_MW, $row->rev_timestamp ), true ); - if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { - $d = '<span class="history-deleted">' . $date . '</span>'; - } else { + if( $rev->userCan( Revision::DELETED_TEXT ) ) { $d = $sk->linkKnown( $page, htmlspecialchars($date), array(), array( 'oldid' => intval( $row->rev_id ) ) ); + } else { + $d = htmlspecialchars( $date ); + } + if( $rev->isDeleted( Revision::DELETED_TEXT ) ) { + $d = '<span class="history-deleted">' . $d . '</span>'; } if( $this->target == 'newbies' ) { @@ -645,7 +711,7 @@ class ContribsPager extends ReverseChronologicalPager { $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>"; @@ -674,6 +740,17 @@ class ContribsPager extends ReverseChronologicalPager { return $this->mDb; } + /** + * Overwrite Pager function and return a helpful comment + */ + function getSqlComment() { + if ( $this->namespace || $this->deletedOnly ) { + return 'contributions page filtered for namespace or RevisionDeleted edits'; // potentially slow, see CR r58153 + } else { + return 'contributions page unfiltered'; + } + } + protected function preventClickjacking() { $this->preventClickjacking = true; } diff --git a/includes/specials/SpecialDeadendpages.php b/includes/specials/SpecialDeadendpages.php index a8416c97..dfa053aa 100644 --- a/includes/specials/SpecialDeadendpages.php +++ b/includes/specials/SpecialDeadendpages.php @@ -1,10 +1,29 @@ <?php /** + * Implements Special:Deadenpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** + * A special page that list pages that contain no link to other pages + * * @ingroup SpecialPage */ class DeadendPagesPage extends PageQueryPage { diff --git a/includes/specials/SpecialDeletedContributions.php b/includes/specials/SpecialDeletedContributions.php index 8884bb22..92e22586 100644 --- a/includes/specials/SpecialDeletedContributions.php +++ b/includes/specials/SpecialDeletedContributions.php @@ -1,5 +1,27 @@ <?php /** + * Implements Special:DeletedContributions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** * Implements Special:DeletedContributions to display archived revisions * @ingroup SpecialPage */ @@ -258,7 +280,7 @@ class DeletedContributionsPage extends SpecialPage { return; } - global $wgOut, $wgLang, $wgRequest; + global $wgOut, $wgRequest; $wgOut->setPageTitle( wfMsgExt( 'deletedcontributions-title', array( 'parsemag' ) ) ); @@ -328,8 +350,8 @@ class DeletedContributionsPage extends SpecialPage { /** * Generates the subheading with links - * @param Title $nt @see Title object for the target - * @param integer $id User ID for the target + * @param $nt Title object for the target + * @param $id Integer: User ID for the target * @return String: appropriately-escaped HTML to be output literally * @todo Fixme: almost the same as contributionsSub in SpecialContributions.php. Could be combined. */ @@ -445,7 +467,7 @@ class DeletedContributionsPage extends SpecialPage { * @param $options Array: the options to be included. */ function getForm( $options ) { - global $wgScript, $wgRequest; + global $wgScript; $options['title'] = SpecialPage::getTitleFor( 'DeletedContributions' )->getPrefixedText(); if ( !isset( $options['target'] ) ) { @@ -472,7 +494,7 @@ class DeletedContributionsPage extends SpecialPage { if ( in_array( $name, array( 'namespace', 'target', 'contribs' ) ) ) { continue; } - $f .= "\t" . Xml::hidden( $name, $value ) . "\n"; + $f .= "\t" . Html::hidden( $name, $value ) . "\n"; } $f .= Xml::openElement( 'fieldset' ) . diff --git a/includes/specials/SpecialDisambiguations.php b/includes/specials/SpecialDisambiguations.php index 1941112a..3e706189 100644 --- a/includes/specials/SpecialDisambiguations.php +++ b/includes/specials/SpecialDisambiguations.php @@ -1,10 +1,29 @@ <?php /** + * Implements Special:Disambiguations + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** + * A special page that lists pages containing links to disambiguations pages + * * @ingroup SpecialPage */ class DisambiguationsPage extends PageQueryPage { @@ -22,6 +41,8 @@ class DisambiguationsPage extends PageQueryPage { } function getSQL() { + global $wgContentNamespaces; + $dbr = wfGetDB( DB_SLAVE ); $dMsgText = wfMsgForContent('disambiguationspage'); @@ -48,11 +69,9 @@ class DisambiguationsPage extends PageQueryPage { 'page_namespace' => $disPageObj->getNamespace(), 'page_title' => $disPageObj->getDBkey()), __METHOD__ ); - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { $linkBatch->addObj( Title::makeTitle( NS_TEMPLATE, $row->pl_title )); } - - $dbr->freeResult( $res ); } $set = $linkBatch->constructSet( 'lb.tl', $dbr ); @@ -64,12 +83,18 @@ class DisambiguationsPage extends PageQueryPage { list( $page, $pagelinks, $templatelinks) = $dbr->tableNamesN( 'page', 'pagelinks', 'templatelinks' ); + if ( $wgContentNamespaces ) { + $nsclause = 'IN (' . $dbr->makeList( $wgContentNamespaces ) . ')'; + } else { + $nsclause = '= ' . NS_MAIN; + } + $sql = "SELECT 'Disambiguations' AS \"type\", pb.page_namespace AS namespace," ." pb.page_title AS title, la.pl_from AS value" ." FROM {$templatelinks} AS lb, {$page} AS pb, {$pagelinks} AS la, {$page} AS pa" ." WHERE $set" # disambiguation template(s) .' AND pa.page_id = la.pl_from' - .' AND pa.page_namespace = ' . NS_MAIN # Limit to just articles in the main namespace + .' AND pa.page_namespace ' . $nsclause .' AND pb.page_id = lb.tl_from' .' AND pb.page_namespace = la.pl_namespace' .' AND pb.page_title = la.pl_title' diff --git a/includes/specials/SpecialDoubleRedirects.php b/includes/specials/SpecialDoubleRedirects.php index 893fee9e..c7f63210 100644 --- a/includes/specials/SpecialDoubleRedirects.php +++ b/includes/specials/SpecialDoubleRedirects.php @@ -1,5 +1,22 @@ <?php /** + * Implements Special:DoubleRedirects + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ @@ -7,6 +24,7 @@ /** * A special page listing redirects to redirecting page. * The software will automatically not follow double redirects, to prevent loops. + * * @ingroup SpecialPage */ class DoubleRedirectsPage extends PageQueryPage { @@ -70,11 +88,10 @@ class DoubleRedirectsPage extends PageQueryPage { $res = $dbr->query( $sql, $fname ); if ( $res ) { $result = $dbr->fetchObject( $res ); - $dbr->freeResult( $res ); } } if ( !$result ) { - return '<s>' . $skin->link( $titleA, null, array(), array( 'redirect' => 'no' ) ) . '</s>'; + return '<del>' . $skin->link( $titleA, null, array(), array( 'redirect' => 'no' ) ) . '</del>'; } $titleB = Title::makeTitle( $result->nsb, $result->tb ); diff --git a/includes/specials/SpecialEmailuser.php b/includes/specials/SpecialEmailuser.php index 48088ded..61271227 100644 --- a/includes/specials/SpecialEmailuser.php +++ b/includes/specials/SpecialEmailuser.php @@ -1,329 +1,303 @@ <?php /** + * Implements Special:Emailuser + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * Constructor for Special:Emailuser. + * A special page that allows users to send e-mails to other users + * + * @ingroup SpecialPage */ -function wfSpecialEmailuser( $par ) { - global $wgRequest, $wgUser, $wgOut; - - if ( !EmailUserForm::userEmailEnabled() ) { - $wgOut->showErrorPage( 'nosuchspecialpage', 'nospecialpagetext' ); - return; - } - - $action = $wgRequest->getVal( 'action' ); - $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); - $targetUser = EmailUserForm::validateEmailTarget( $target ); +class SpecialEmailUser extends UnlistedSpecialPage { + protected $mTarget; - if ( !( $targetUser instanceof User ) ) { - $wgOut->showErrorPage( $targetUser.'title', $targetUser.'text' ); - return; + public function __construct(){ + parent::__construct( 'Emailuser' ); } - $form = new EmailUserForm( $targetUser, - $wgRequest->getText( 'wpText' ), - $wgRequest->getText( 'wpSubject' ), - $wgRequest->getBool( 'wpCCMe' ) ); - if ( $action == 'success' ) { - $form->showSuccess(); - return; + protected function getFormFields(){ + global $wgUser; + return array( + 'From' => array( + 'type' => 'info', + 'raw' => 1, + 'default' => $wgUser->getSkin()->link( + $wgUser->getUserPage(), + htmlspecialchars( $wgUser->getName() ) + ), + 'label-message' => 'emailfrom', + 'id' => 'mw-emailuser-sender', + ), + 'To' => array( + 'type' => 'info', + 'raw' => 1, + 'default' => $wgUser->getSkin()->link( + $this->mTargetObj->getUserPage(), + htmlspecialchars( $this->mTargetObj->getName() ) + ), + 'label-message' => 'emailto', + 'id' => 'mw-emailuser-recipient', + ), + 'Target' => array( + 'type' => 'hidden', + 'default' => $this->mTargetObj->getName(), + ), + 'Subject' => array( + 'type' => 'text', + 'default' => wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ) ), + 'label-message' => 'emailsubject', + 'maxlength' => 200, + 'size' => 60, + 'required' => 1, + ), + 'Text' => array( + 'type' => 'textarea', + 'rows' => 20, + 'cols' => 80, + 'label-message' => 'emailmessage', + 'required' => 1, + ), + 'CCMe' => array( + 'type' => 'check', + 'label-message' => 'emailccme', + 'default' => $wgUser->getBoolOption( 'ccmeonemails' ), + ), + ); } - - $error = EmailUserForm::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) ); - if ( $error ) { + + public function execute( $par ) { + global $wgRequest, $wgOut, $wgUser; + + $this->setHeaders(); + $this->outputHeader(); + + $this->mTarget = is_null( $par ) + ? $wgRequest->getVal( 'wpTarget', $wgRequest->getVal( 'target', '' ) ) + : $par; + + $ret = self::getTarget( $this->mTarget ); + if( $ret instanceof User ){ + $this->mTargetObj = $ret; + } else { + $wgOut->showErrorPage( "{$ret}title", "{$ret}text" ); + return false; + } + + $error = self::getPermissionsError( $wgUser, $wgRequest->getVal( 'wpEditToken' ) ); switch ( $error ) { + case null: + # Wahey! + break; + case 'badaccess': + $wgOut->permissionRequired( 'sendemail' ); + return; case 'blockedemailuser': $wgOut->blockedPage(); return; case 'actionthrottledtext': $wgOut->rateLimited(); return; - case 'sessionfailure': - $form->showForm(); - return; case 'mailnologin': - $wgOut->showErrorPage( 'mailnologin', 'mailnologintext' ); + case 'usermaildisabled': + $wgOut->showErrorPage( $error, "{$error}text" ); return; default: - // It's a hook error + # It's a hook error list( $title, $msg, $params ) = $error; $wgOut->showErrorPage( $title, $msg, $params ); return; - } - } - - if ( "submit" == $action && $wgRequest->wasPosted() ) { - $result = $form->doSubmit(); - if ( !is_null( $result ) ) { - $wgOut->addHTML( wfMsg( "usermailererror" ) . - ' ' . htmlspecialchars( $result->getMessage() ) ); - } else { - $titleObj = SpecialPage::getTitleFor( "Emailuser" ); - $encTarget = wfUrlencode( $form->getTarget()->getName() ); - $wgOut->redirect( $titleObj->getFullURL( "target={$encTarget}&action=success" ) ); - } - } else { - $form->showForm(); - } -} - -/** - * Implements the Special:Emailuser web interface, and invokes userMailer for sending the email message. - * @ingroup SpecialPage - */ -class EmailUserForm { - - var $target; - var $text, $subject; - var $cc_me; // Whether user requested to be sent a separate copy of their email. - - /** - * @param User $target - */ - function EmailUserForm( $target, $text, $subject, $cc_me ) { - $this->target = $target; - $this->text = $text; - $this->subject = $subject; - $this->cc_me = $cc_me; - } - - function showForm() { - global $wgOut, $wgUser; - $skin = $wgUser->getSkin(); - - $wgOut->setPagetitle( wfMsg( "emailpage" ) ); - $wgOut->addWikiMsg( "emailpagetext" ); - - if ( $this->subject === "" ) { - $this->subject = wfMsgExt( 'defemailsubject', array( 'content', 'parsemag' ) ); - } - - $titleObj = SpecialPage::getTitleFor( "Emailuser" ); - $action = $titleObj->getLocalURL( "target=" . - urlencode( $this->target->getName() ) . "&action=submit" ); - - $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'emailuser' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsgExt( 'email-legend', 'parsemag' ) ) . - Xml::openElement( 'table', array( 'class' => 'mw-emailuser-table' ) ) . - "<tr> - <td class='mw-label'>" . - Xml::label( wfMsg( 'emailfrom' ), 'emailfrom' ) . - "</td> - <td class='mw-input' id='mw-emailuser-sender'>" . - $skin->link( $wgUser->getUserPage(), htmlspecialchars( $wgUser->getName() ) ) . - "</td> - </tr> - <tr> - <td class='mw-label'>" . - Xml::label( wfMsg( 'emailto' ), 'emailto' ) . - "</td> - <td class='mw-input' id='mw-emailuser-recipient'>" . - $skin->link( $this->target->getUserPage(), htmlspecialchars( $this->target->getName() ) ) . - "</td> - </tr> - <tr> - <td class='mw-label'>" . - Xml::label( wfMsg( 'emailsubject' ), 'wpSubject' ) . - "</td> - <td class='mw-input'>" . - Xml::input( 'wpSubject', 60, $this->subject, array( 'type' => 'text', 'maxlength' => 200 ) ) . - "</td> - </tr> - <tr> - <td class='mw-label'>" . - Xml::label( wfMsg( 'emailmessage' ), 'wpText' ) . - "</td> - <td class='mw-input'>" . - Xml::textarea( 'wpText', $this->text, 80, 20, array( 'id' => 'wpText' ) ) . - "</td> - </tr> - <tr> - <td></td> - <td class='mw-input'>" . - Xml::checkLabel( wfMsg( 'emailccme' ), 'wpCCMe', 'wpCCMe', $wgUser->getBoolOption( 'ccmeonemails' ) ) . - "</td> - </tr> - <tr> - <td></td> - <td class='mw-submit'>" . - Xml::submitButton( wfMsg( 'emailsend' ), array( 'name' => 'wpSend', 'accesskey' => 's' ) ) . - "</td> - </tr>" . - Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) - ); - } - - /* - * Really send a mail. Permissions should have been checked using - * EmailUserForm::getPermissionsError. It is probably also a good idea to - * check the edit token and ping limiter in advance. - */ - function doSubmit() { - global $wgUser, $wgUserEmailUseReplyTo, $wgSiteName; - - $to = new MailAddress( $this->target ); - $from = new MailAddress( $wgUser ); - $subject = $this->subject; - - // Add a standard footer and trim up trailing newlines - $this->text = rtrim($this->text) . "\n\n-- \n" . wfMsgExt( 'emailuserfooter', - array( 'content', 'parsemag' ), array( $from->name, $to->name ) ); + $form = new HTMLForm( $this->getFormFields() ); + $form->addPreText( wfMsgExt( 'emailpagetext', 'parseinline' ) ); + $form->setSubmitText( wfMsg( 'emailsend' ) ); + $form->setTitle( $this->getTitle() ); + $form->setSubmitCallback( array( __CLASS__, 'submit' ) ); + $form->setWrapperLegend( wfMsgExt( 'email-legend', 'parsemag' ) ); + $form->loadData(); - if( wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$this->text ) ) ) { - - if( $wgUserEmailUseReplyTo ) { - // Put the generic wiki autogenerated address in the From: - // header and reserve the user for Reply-To. - // - // This is a bit ugly, but will serve to differentiate - // wiki-borne mails from direct mails and protects against - // SPF and bounce problems with some mailers (see below). - global $wgPasswordSender; - $mailFrom = new MailAddress( $wgPasswordSender ); - $replyTo = $from; - } else { - // Put the sending user's e-mail address in the From: header. - // - // This is clean-looking and convenient, but has issues. - // One is that it doesn't as clearly differentiate the wiki mail - // from "directly" sent mails. - // - // Another is that some mailers (like sSMTP) will use the From - // address as the envelope sender as well. For open sites this - // can cause mails to be flunked for SPF violations (since the - // wiki server isn't an authorized sender for various users' - // domains) as well as creating a privacy issue as bounces - // containing the recipient's e-mail address may get sent to - // the sending user. - $mailFrom = $from; - $replyTo = null; - } - - $mailResult = UserMailer::send( $to, $mailFrom, $subject, $this->text, $replyTo ); - - if( WikiError::isError( $mailResult ) ) { - return $mailResult; - - } else { - - // if the user requested a copy of this mail, do this now, - // unless they are emailing themselves, in which case one copy of the message is sufficient. - if ($this->cc_me && $to != $from) { - $cc_subject = wfMsg('emailccsubject', $this->target->getName(), $subject); - if( wfRunHooks( 'EmailUser', array( &$from, &$from, &$cc_subject, &$this->text ) ) ) { - $ccResult = UserMailer::send( $from, $from, $cc_subject, $this->text ); - if( WikiError::isError( $ccResult ) ) { - // At this stage, the user's CC mail has failed, but their - // original mail has succeeded. It's unlikely, but still, what to do? - // We can either show them an error, or we can say everything was fine, - // or we can say we sort of failed AND sort of succeeded. Of these options, - // simply saying there was an error is probably best. - return $ccResult; - } - } - } - - wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $this->text ) ); - return; - } + if( !wfRunHooks( 'EmailUserForm', array( &$form ) ) ){ + return false; } - } - - function showSuccess( &$user = null ) { - global $wgOut; - if ( is_null($user) ) - $user = $this->target; - - $wgOut->setPagetitle( wfMsg( "emailsent" ) ); - $wgOut->addWikiMsg( 'emailsenttext' ); - - $wgOut->returnToMain( false, $user->getUserPage() ); - } - - function getTarget() { - return $this->target; - } - - static function userEmailEnabled() { - global $wgEnableEmail, $wgEnableUserEmail; - return $wgEnableEmail && $wgEnableUserEmail; + $wgOut->setPagetitle( wfMsg( 'emailpage' ) ); + $result = $form->show(); + if( $result === true || ( $result instanceof Status && $result->isGood() ) ){ + $wgOut->setPagetitle( wfMsg( 'emailsent' ) ); + $wgOut->addWikiMsg( 'emailsenttext' ); + $wgOut->returnToMain( false, $this->mTargetObj->getUserPage() ); + } } - static function validateEmailTarget ( $target ) { - if ( $target == "" ) { + + /** + * Validate target User + * + * @param $target String: target user name + * @return User object on success or a string on error + */ + public static function getTarget( $target ) { + if ( $target == '' ) { wfDebug( "Target is empty.\n" ); - return "notarget"; - } - - $nt = Title::newFromURL( $target ); - if ( is_null( $nt ) ) { - wfDebug( "Target is invalid title.\n" ); - return "notarget"; + return 'notarget'; } - - $nu = User::newFromName( $nt->getText() ); + + $nu = User::newFromName( $target ); if( !$nu instanceof User || !$nu->getId() ) { wfDebug( "Target is invalid user.\n" ); - return "notarget"; + return 'notarget'; } else if ( !$nu->isEmailConfirmed() ) { wfDebug( "User has no valid email.\n" ); - return "noemail"; + return 'noemail'; } else if ( !$nu->canReceiveEmail() ) { wfDebug( "User does not allow user emails.\n" ); - return "nowikiemail"; + return 'nowikiemail'; } - + return $nu; } - 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"; + + /** + * Check whether a user is allowed to send email + * + * @param $user User object + * @param $editToken String: edit token + * @return null on success or string on error + */ + public static function getPermissionsError( $user, $editToken ) { + global $wgEnableEmail, $wgEnableUserEmail; + if( !$wgEnableEmail || !$wgEnableUserEmail ){ + return 'usermaildisabled'; + } + + if( !$user->isAllowed( 'sendemail' ) ) { + return 'badaccess'; } + if( !$user->isEmailConfirmed() ){ + return 'mailnologin'; + } + if( $user->isBlockedFromEmailuser() ) { wfDebug( "User is blocked from sending e-mail.\n" ); return "blockedemailuser"; } - + if( $user->pingLimiter( 'emailuser' ) ) { - wfDebug( "Ping limiter triggered.\n" ); + wfDebug( "Ping limiter triggered.\n" ); return 'actionthrottledtext'; } - - $hookErr = null; + + $hookErr = false; + wfRunHooks( 'UserCanSendEmail', array( &$user, &$hookErr ) ); wfRunHooks( 'EmailUserPermissionsErrors', array( $user, $editToken, &$hookErr ) ); - - if ($hookErr) { + if ( $hookErr ) { return $hookErr; } + + return null; + } + + /** + * Really send a mail. Permissions should have been checked using + * getPermissionsError(). It is probably also a good + * idea to check the edit token and ping limiter in advance. + * + * @return Mixed: Status object, or potentially a String on error + * or maybe even true on success if anything uses the EmailUser hook. + */ + public static function submit( $data ) { + global $wgUser, $wgUserEmailUseReplyTo; + + $target = self::getTarget( $data['Target'] ); + if( !$target instanceof User ){ + return wfMsgExt( $target . 'text', 'parse' ); + } + $to = new MailAddress( $target ); + $from = new MailAddress( $wgUser ); + $subject = $data['Subject']; + $text = $data['Text']; + + // Add a standard footer and trim up trailing newlines + $text = rtrim( $text ) . "\n\n-- \n"; + $text .= wfMsgExt( + 'emailuserfooter', + array( 'content', 'parsemag' ), + array( $from->name, $to->name ) + ); + + $error = ''; + if( !wfRunHooks( 'EmailUser', array( &$to, &$from, &$subject, &$text, &$error ) ) ) { + return $error; + } - if( !$user->matchEditToken( $editToken ) ) { - wfDebug( "Matching edit token failed.\n" ); - return 'sessionfailure'; + if( $wgUserEmailUseReplyTo ) { + // Put the generic wiki autogenerated address in the From: + // header and reserve the user for Reply-To. + // + // This is a bit ugly, but will serve to differentiate + // wiki-borne mails from direct mails and protects against + // SPF and bounce problems with some mailers (see below). + global $wgPasswordSender, $wgPasswordSenderName; + $mailFrom = new MailAddress( $wgPasswordSender, $wgPasswordSenderName ); + $replyTo = $from; + } else { + // Put the sending user's e-mail address in the From: header. + // + // This is clean-looking and convenient, but has issues. + // One is that it doesn't as clearly differentiate the wiki mail + // from "directly" sent mails. + // + // Another is that some mailers (like sSMTP) will use the From + // address as the envelope sender as well. For open sites this + // can cause mails to be flunked for SPF violations (since the + // wiki server isn't an authorized sender for various users' + // domains) as well as creating a privacy issue as bounces + // containing the recipient's e-mail address may get sent to + // the sending user. + $mailFrom = $from; + $replyTo = null; + } + + $status = UserMailer::send( $to, $mailFrom, $subject, $text, $replyTo ); + + if( !$status->isGood() ) { + return $status; + } else { + // if the user requested a copy of this mail, do this now, + // unless they are emailing themselves, in which case one + // copy of the message is sufficient. + if ( $data['CCMe'] && $to != $from ) { + $cc_subject = wfMsg( + 'emailccsubject', + $target->getName(), + $subject + ); + wfRunHooks( 'EmailUserCC', array( &$from, &$from, &$cc_subject, &$text ) ); + $ccStatus = UserMailer::send( $from, $from, $cc_subject, $text ); + $status->merge( $ccStatus ); + } + + wfRunHooks( 'EmailUserComplete', array( $to, $from, $subject, $text ) ); + return $status; } - } - - static function newFromURL( $target, $text, $subject, $cc_me ) - { - $nt = Title::newFromURL( $target ); - $nu = User::newFromName( $nt->getText() ); - return new EmailUserForm( $nu, $text, $subject, $cc_me ); } } diff --git a/includes/specials/SpecialExport.php b/includes/specials/SpecialExport.php index b9a44d48..eaed2393 100644 --- a/includes/specials/SpecialExport.php +++ b/includes/specials/SpecialExport.php @@ -1,56 +1,64 @@ <?php -# Copyright (C) 2003-2008 Brion Vibber <brion@pobox.com> -# 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 /** + * Implements Special:Export + * + * Copyright © 2003-2008 Brion Vibber <brion@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ +/** + * A special page that allows users to export pages in a XML file + * + * @ingroup SpecialPage + */ class SpecialExport extends SpecialPage { - + private $curonly, $doExport, $pageLinkDepth, $templates; private $images; - + public function __construct() { parent::__construct( 'Export' ); } - + public function execute( $par ) { global $wgOut, $wgRequest, $wgSitename, $wgExportAllowListContributors; global $wgExportAllowHistory, $wgExportMaxHistory, $wgExportMaxLinkDepth; - global $wgExportFromNamespaces; - + global $wgExportFromNamespaces, $wgUser; + $this->setHeaders(); $this->outputHeader(); - + // Set some variables $this->curonly = true; $this->doExport = false; $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 ) { $t = Title::makeTitleSafe( NS_MAIN, $catname ); if ( $t ) { @@ -67,7 +75,7 @@ class SpecialExport extends SpecialPage { else if( $wgRequest->getCheck( 'addns' ) && $wgExportFromNamespaces ) { $page = $wgRequest->getText( 'pages' ); $nsindex = $wgRequest->getText( 'nsindex', '' ); - + if ( strval( $nsindex ) !== '' ) { /** * Same implementation as above, so same @todo @@ -80,11 +88,13 @@ class SpecialExport extends SpecialPage { $page = $wgRequest->getText( 'pages' ); $this->curonly = $wgRequest->getCheck( 'curonly' ); $rawOffset = $wgRequest->getVal( 'offset' ); + if( $rawOffset ) { $offset = wfTimestamp( TS_MW, $rawOffset ); } else { $offset = null; } + $limit = $wgRequest->getInt( 'limit' ); $dir = $wgRequest->getVal( 'dir' ); $history = array( @@ -93,6 +103,7 @@ class SpecialExport extends SpecialPage { 'limit' => $wgExportMaxHistory, ); $historyCheck = $wgRequest->getCheck( 'history' ); + if ( $this->curonly ) { $history = WikiExporter::CURRENT; } elseif ( !$historyCheck ) { @@ -106,93 +117,101 @@ class SpecialExport extends SpecialPage { $history['dir'] = 'desc'; } } - + if( $page != '' ) $this->doExport = true; } else { - // Default to current-only for GET requests + // Default to current-only for GET requests. $page = $wgRequest->getText( 'pages', $par ); $historyCheck = $wgRequest->getCheck( 'history' ); + if( $historyCheck ) { $history = WikiExporter::FULL; } else { $history = WikiExporter::CURRENT; } - + if( $page != '' ) $this->doExport = true; } - + if( !$wgExportAllowHistory ) { // Override $history = WikiExporter::CURRENT; } - + $list_authors = $wgRequest->getCheck( 'listauthors' ); if ( !$this->curonly || !$wgExportAllowListContributors ) $list_authors = false ; - + if ( $this->doExport ) { $wgOut->disable(); + // Cancel output buffering and gzipping if set // This should provide safer streaming for pages with history wfResetOutputBuffers(); - header( "Content-type: application/xml; charset=utf-8" ); + $wgRequest->response()->header( "Content-type: application/xml; charset=utf-8" ); + if( $wgRequest->getCheck( 'wpDownload' ) ) { // Provide a sane filename suggestion $filename = urlencode( $wgSitename . '-' . wfTimestampNow() . '.xml' ); $wgRequest->response()->header( "Content-disposition: attachment;filename={$filename}" ); } + $this->doExport( $page, $history, $list_authors ); + return; } - + $wgOut->addWikiMsg( 'exporttext' ); - + $form = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalUrl( 'action=submit' ) ) ); - $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . ' '; + $form .= Xml::inputLabel( wfMsg( 'export-addcattext' ) , 'catname', 'catname', 40 ) . ' '; $form .= Xml::submitButton( wfMsg( 'export-addcat' ), array( 'name' => 'addcat' ) ) . '<br />'; - + if ( $wgExportFromNamespaces ) { - $form .= Xml::namespaceSelector( $nsindex, null, 'nsindex', wfMsg( 'export-addnstext' ) ) . ' '; + $form .= Xml::namespaceSelector( $nsindex, null, 'nsindex', wfMsg( 'export-addnstext' ) ) . ' '; $form .= Xml::submitButton( wfMsg( 'export-addns' ), array( 'name' => 'addns' ) ) . '<br />'; } - + $form .= Xml::element( 'textarea', array( 'name' => 'pages', 'cols' => 40, 'rows' => 10 ), $page, false ); $form .= '<br />'; - + if( $wgExportAllowHistory ) { $form .= Xml::checkLabel( wfMsg( 'exportcuronly' ), 'curonly', 'curonly', true ) . '<br />'; } else { $wgOut->addHTML( wfMsgExt( 'exportnohistory', 'parse' ) ); } + $form .= Xml::checkLabel( wfMsg( 'export-templates' ), 'templates', 'wpExportTemplates', false ) . '<br />'; + if( $wgExportMaxLinkDepth || $this->userCanOverrideExportDepth() ) { $form .= Xml::inputLabel( wfMsg( 'export-pagelinks' ), 'pagelink-depth', 'pagelink-depth', 20, 0 ) . '<br />'; } // Enable this when we can do something useful exporting/importing image information. :) //$form .= Xml::checkLabel( wfMsg( 'export-images' ), 'images', 'wpExportImages', false ) . '<br />'; $form .= Xml::checkLabel( wfMsg( 'export-download' ), 'wpDownload', 'wpDownload', true ) . '<br />'; - - $form .= Xml::submitButton( wfMsg( 'export-submit' ), array( 'accesskey' => 's' ) ); + + $form .= Xml::submitButton( wfMsg( 'export-submit' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'export' ) ); $form .= Xml::closeElement( 'form' ); + $wgOut->addHTML( $form ); } - - private function userCanOverrideExportDepth() { - global $wgUser; + private function userCanOverrideExportDepth() { + global $wgUser; return $wgUser->isAllowed( 'override-export-depth' ); } - + /** * Do the actual page exporting - * @param string $page User input on what page(s) to export - * @param mixed $history one of the WikiExporter history export constants + * + * @param $page String: user input on what page(s) to export + * @param $history Mixed: one of the WikiExporter history export constants + * @param $list_authors Boolean: Whether to add distinct author list (when + * not returning full history) */ private function doExport( $page, $history, $list_authors ) { - global $wgExportMaxHistory; - $pageSet = array(); // Inverted index of all pages to look up - + // Split up and normalize input foreach( explode( "\n", $page ) as $pageName ) { $pageName = trim( $pageName ); @@ -202,32 +221,33 @@ class SpecialExport extends SpecialPage { $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 ); } - - if( $linkDepth = $this->pageLinkDepth ) { + $linkDepth = $this->pageLinkDepth; + if( $linkDepth ) { $pageSet = $this->getPageLinks( $inputPages, $pageSet, $linkDepth ); } - + /* // Enable this when we can do something useful exporting/importing image information. :) if( $this->images ) ) { $pageSet = $this->getImages( $inputPages, $pageSet ); } */ - + $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... */ @@ -240,15 +260,17 @@ class SpecialExport extends SpecialPage { $lb = wfGetLBFactory()->newMainLB(); $db = $lb->getConnection( DB_SLAVE ); $buffer = WikiExporter::STREAM; - + // This might take a while... :D wfSuppressWarnings(); set_time_limit(0); wfRestoreWarnings(); } + $exporter = new WikiExporter( $db, $history, $buffer ); $exporter->list_authors = $list_authors; $exporter->openStream(); + foreach( $pages as $page ) { /* if( $wgExportMaxHistory && !$this->curonly ) { @@ -266,11 +288,12 @@ class SpecialExport extends SpecialPage { $title = Title::newFromText( $page ); if( is_null( $title ) ) continue; #TODO: perhaps output an <error> tag or something. if( !$title->userCanRead() ) continue; #TODO: perhaps output an <error> tag or something. - + $exporter->pageByTitle( $title ); } - + $exporter->closeStream(); + if( $lb ) { $lb->closeAll(); } @@ -278,52 +301,59 @@ class SpecialExport extends SpecialPage { private function getPagesFromCategory( $title ) { global $wgContLang; - + $name = $title->getDBkey(); - + $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( array('page', 'categorylinks' ), - array( 'page_namespace', 'page_title' ), - array('cl_from=page_id', 'cl_to' => $name ), - __METHOD__, array('LIMIT' => '5000')); - + $res = $dbr->select( + array( 'page', 'categorylinks' ), + array( 'page_namespace', 'page_title' ), + array( 'cl_from=page_id', 'cl_to' => $name ), + __METHOD__, + array( 'LIMIT' => '5000' ) + ); + $pages = array(); - while ( $row = $dbr->fetchObject( $res ) ) { + + foreach ( $res as $row ) { $n = $row->page_title; if ($row->page_namespace) { $ns = $wgContLang->getNsText( $row->page_namespace ); $n = $ns . ':' . $n; } - + $pages[] = $n; } - $dbr->freeResult($res); - return $pages; } - + private function getPagesFromNamespace( $nsindex ) { global $wgContLang; - + $dbr = wfGetDB( DB_SLAVE ); - $res = $dbr->select( 'page', array('page_namespace', 'page_title'), - array('page_namespace' => $nsindex), - __METHOD__, array('LIMIT' => '5000') ); - + $res = $dbr->select( + 'page', + array( 'page_namespace', 'page_title' ), + array( 'page_namespace' => $nsindex ), + __METHOD__, + array( 'LIMIT' => '5000' ) + ); + $pages = array(); - while ( $row = $dbr->fetchObject( $res ) ) { + + foreach ( $res as $row ) { $n = $row->page_title; - if ($row->page_namespace) { + + if ( $row->page_namespace ) { $ns = $wgContLang->getNsText( $row->page_namespace ); $n = $ns . ':' . $n; } - + $pages[] = $n; } - $dbr->freeResult($res); - return $pages; } + /** * Expand a list of pages to include templates used in those pages. * @param $inputPages array, list of titles to look up @@ -332,24 +362,28 @@ class SpecialExport extends SpecialPage { */ private function getTemplates( $inputPages, $pageSet ) { return $this->getLinks( $inputPages, $pageSet, - 'templatelinks', - array( 'tl_namespace AS namespace', 'tl_title AS title' ), - array( 'page_id=tl_from' ) ); + 'templatelinks', + array( 'tl_namespace AS namespace', 'tl_title AS title' ), + array( 'page_id=tl_from' ) + ); } - + /** * Validate link depth setting, if available. */ private function validateLinkDepth( $depth ) { - global $wgExportMaxLinkDepth, $wgExportMaxLinkDepthLimit; + global $wgExportMaxLinkDepth; + if( $depth < 0 ) { return 0; } + if ( !$this->userCanOverrideExportDepth() ) { if( $depth > $wgExportMaxLinkDepth ) { return $wgExportMaxLinkDepth; } } + /* * There's a HARD CODED limit of 5 levels of recursion here to prevent a * crazy-big export from being done by someone setting the depth @@ -357,58 +391,73 @@ class SpecialExport extends SpecialPage { */ return intval( min( $depth, 5 ) ); } - + /** Expand a list of pages to include pages linked to from that page. */ private function getPageLinks( $inputPages, $pageSet, $depth ) { - for( $depth=$depth; $depth>0; --$depth ) { - $pageSet = $this->getLinks( $inputPages, $pageSet, 'pagelinks', - array( 'pl_namespace AS namespace', 'pl_title AS title' ), - array( 'page_id=pl_from' ) ); + for(; $depth > 0; --$depth ) { + $pageSet = $this->getLinks( + $inputPages, $pageSet, 'pagelinks', + array( 'pl_namespace AS namespace', 'pl_title AS title' ), + array( 'page_id=pl_from' ) + ); $inputPages = array_keys( $pageSet ); } + return $pageSet; } - + /** * Expand a list of pages to include images used in those pages. + * * @param $inputPages array, list of titles to look up * @param $pageSet array, associative array indexed by titles for output + * * @return array associative array index by titles */ private function getImages( $inputPages, $pageSet ) { - return $this->getLinks( $inputPages, $pageSet, - 'imagelinks', - array( NS_FILE . ' AS namespace', 'il_to AS title' ), - array( 'page_id=il_from' ) ); + return $this->getLinks( + $inputPages, + $pageSet, + 'imagelinks', + array( NS_FILE . ' AS namespace', 'il_to AS title' ), + array( 'page_id=il_from' ) + ); } - + /** * Expand a list of pages to include items used in those pages. - * @private */ private function getLinks( $inputPages, $pageSet, $table, $fields, $join ) { $dbr = wfGetDB( DB_SLAVE ); + foreach( $inputPages as $page ) { $title = Title::newFromText( $page ); + if( $title ) { $pageSet[$title->getPrefixedText()] = true; /// @todo Fixme: May or may not be more efficient to batch these /// by namespace when given multiple input pages. $result = $dbr->select( - array( 'page', $table ), - $fields, - array_merge( $join, - array( - 'page_namespace' => $title->getNamespace(), - 'page_title' => $title->getDBkey() ) ), - __METHOD__ ); + array( 'page', $table ), + $fields, + array_merge( + $join, + array( + 'page_namespace' => $title->getNamespace(), + 'page_title' => $title->getDBkey() + ) + ), + __METHOD__ + ); + foreach( $result as $row ) { $template = Title::makeTitle( $row->namespace, $row->title ); $pageSet[$template->getPrefixedText()] = true; } } } + return $pageSet; } -} - + +}
\ No newline at end of file diff --git a/includes/specials/SpecialFewestrevisions.php b/includes/specials/SpecialFewestrevisions.php index 65d76a65..c265ed38 100644 --- a/includes/specials/SpecialFewestrevisions.php +++ b/includes/specials/SpecialFewestrevisions.php @@ -1,5 +1,22 @@ <?php /** + * Implements Special:Fewestrevisions + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ diff --git a/includes/specials/SpecialFileDuplicateSearch.php b/includes/specials/SpecialFileDuplicateSearch.php index 0ed7020a..172e92ad 100644 --- a/includes/specials/SpecialFileDuplicateSearch.php +++ b/includes/specials/SpecialFileDuplicateSearch.php @@ -1,24 +1,37 @@ <?php /** - * A special page to search for files by hash value as defined in the - * img_sha1 field in the image table + * Implements Special:FileDuplicateSearch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage - * * @author Raimond Spekking, based on Special:MIMESearch by Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ /** * Searches the database for files of the requested hash, comparing this with the * 'img_sha1' field in the image table. + * * @ingroup SpecialPage */ class FileDuplicateSearchPage extends QueryPage { var $hash, $filename; - function FileDuplicateSearchPage( $hash, $filename ) { + function __construct( $hash, $filename ) { $this->hash = $hash; $this->filename = $filename; } @@ -72,7 +85,7 @@ function wfSpecialFileDuplicateSearch( $par = null ) { $hash = ''; $filename = isset( $par ) ? $par : $wgRequest->getText( 'filename' ); - $title = Title::newFromText( $filename ); + $title = Title::makeTitleSafe( NS_FILE, $filename ); if( $title && $title->getText() != '' ) { $dbr = wfGetDB( DB_SLAVE ); $image = $dbr->tableName( 'image' ); @@ -83,13 +96,12 @@ function wfSpecialFileDuplicateSearch( $par = null ) { if( $row !== false ) { $hash = $row[0]; } - $dbr->freeResult( $res ); } # Create the input form $wgOut->addHTML( Xml::openElement( 'form', array( 'id' => 'fileduplicatesearch', 'method' => 'get', 'action' => $wgScript ) ) . - Xml::hidden( 'title', SpecialPage::getTitleFor( 'FileDuplicateSearch' )->getPrefixedDbKey() ) . + Html::hidden( 'title', SpecialPage::getTitleFor( 'FileDuplicateSearch' )->getPrefixedDbKey() ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'fileduplicatesearch-legend' ) ) . Xml::inputLabel( wfMsg( 'fileduplicatesearch-filename' ), 'filename', 'filename', 50, $filename ) . ' ' . diff --git a/includes/specials/SpecialFilepath.php b/includes/specials/SpecialFilepath.php index 8bc1c68b..8bb0890c 100644 --- a/includes/specials/SpecialFilepath.php +++ b/includes/specials/SpecialFilepath.php @@ -1,53 +1,81 @@ <?php /** + * Implements Special:Filepath + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ -function wfSpecialFilepath( $par ) { - global $wgRequest, $wgOut; +/** + * A special page that redirects to the URL of a given file + * + * @ingroup SpecialPage + */ +class SpecialFilepath extends SpecialPage { - $file = isset( $par ) ? $par : $wgRequest->getText( 'file' ); + function __construct() { + parent::__construct( 'Filepath' ); + } - $title = Title::makeTitleSafe( NS_FILE, $file ); + function execute( $par ) { + global $wgRequest, $wgOut; - if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) { - $cform = new FilepathForm( $title ); - $cform->execute(); - } else { - $file = wfFindFile( $title ); - if ( $file && $file->exists() ) { - $wgOut->redirect( $file->getURL() ); - } else { - $wgOut->setStatusCode( 404 ); - $cform = new FilepathForm( $title ); - $cform->execute(); - } - } -} + $this->setHeaders(); + $this->outputHeader(); -/** - * @ingroup SpecialPage - */ -class FilepathForm { - var $mTitle; + $file = !is_null( $par ) ? $par : $wgRequest->getText( 'file' ); - function FilepathForm( &$title ) { - $this->mTitle =& $title; + $title = Title::makeTitleSafe( NS_FILE, $file ); + + if ( ! $title instanceof Title || $title->getNamespace() != NS_FILE ) { + $this->showForm( $title ); + } else { + $file = wfFindFile( $title ); + if ( $file && $file->exists() ) { + $url = $file->getURL(); + $width = $wgRequest->getInt( 'width', -1 ); + $height = $wgRequest->getInt( 'height', -1 ); + if ( $width != -1 ) { + $mto = $file->transform( array( 'width' => $width, 'height' => $height ) ); + if ( $mto && !$mto->isError() ) { + $url = $mto->getURL(); + } + } + $wgOut->redirect( $url ); + } else { + $wgOut->setStatusCode( 404 ); + $this->showForm( $title ); + } + } } - function execute() { + function showForm( $title ) { 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', SpecialPage::getTitleFor( 'Filepath' )->getPrefixedText() ) . - Xml::inputLabel( wfMsg( 'filepath-page' ), 'file', 'file', 25, is_object( $this->mTitle ) ? $this->mTitle->getText() : '' ) . ' ' . + Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'specialfilepath' ) ) . + Html::openElement( 'fieldset' ) . + Html::element( 'legend', null, wfMsg( 'filepath' ) ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + Xml::inputLabel( wfMsg( 'filepath-page' ), 'file', 'file', 25, is_object( $title ) ? $title->getText() : '' ) . ' ' . Xml::submitButton( wfMsg( 'filepath-submit' ) ) . "\n" . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) + Html::closeElement( 'fieldset' ) . + Html::closeElement( 'form' ) ); } } diff --git a/includes/specials/SpecialImport.php b/includes/specials/SpecialImport.php index 248709a8..7d1cf0dd 100644 --- a/includes/specials/SpecialImport.php +++ b/includes/specials/SpecialImport.php @@ -1,7 +1,8 @@ <?php /** - * MediaWiki page data importer - * Copyright (C) 2003,2005 Brion Vibber <brion@pobox.com> + * Implements Special:Import + * + * Copyright © 2003,2005 Brion Vibber <brion@pobox.com> * http://www.mediawiki.org/ * * This program is free software; you can redistribute it and/or modify @@ -23,6 +24,11 @@ * @ingroup SpecialPage */ +/** + * MediaWiki page data importer + * + * @ingroup SpecialPage + */ class SpecialImport extends SpecialPage { private $interwiki = false; @@ -51,7 +57,6 @@ class SpecialImport extends SpecialPage { $this->outputHeader(); if ( wfReadOnly() ) { - global $wgOut; $wgOut->readOnlyPage(); return; } @@ -97,7 +102,7 @@ class SpecialImport extends SpecialPage { $this->pageLinkDepth = $wgExportMaxLinkDepth == 0 ? 0 : $wgRequest->getIntOrNull( 'pagelink-depth' ); if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'editToken' ) ) ) { - $source = new WikiErrorMsg( 'import-token-mismatch' ); + $source = Status::newFatal( 'import-token-mismatch' ); } elseif ( $sourceName == 'upload' ) { $isUpload = true; if( $wgUser->isAllowed( 'importupload' ) ) { @@ -111,7 +116,7 @@ class SpecialImport extends SpecialPage { } $this->interwiki = $wgRequest->getVal( 'interwiki' ); if ( !in_array( $this->interwiki, $wgImportSources ) ) { - $source = new WikiErrorMsg( "import-invalid-interwiki" ); + $source = Status::newFatal( "import-invalid-interwiki" ); } else { $this->history = $wgRequest->getCheck( 'interwikiHistory' ); $this->frompage = $wgRequest->getText( "frompage" ); @@ -124,30 +129,35 @@ class SpecialImport extends SpecialPage { $this->pageLinkDepth ); } } else { - $source = new WikiErrorMsg( "importunknownsource" ); + $source = Status::newFatal( "importunknownsource" ); } - if( WikiError::isError( $source ) ) { - $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $source->getMessage() ) ); + if( !$source->isGood() ) { + $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $source->getWikiText() ) ); } else { $wgOut->addWikiMsg( "importstart" ); - $importer = new WikiImporter( $source ); + $importer = new WikiImporter( $source->value ); if( !is_null( $this->namespace ) ) { $importer->setTargetNamespace( $this->namespace ); } $reporter = new ImportReporter( $importer, $isUpload, $this->interwiki , $this->logcomment); + $exception = false; $reporter->open(); - $result = $importer->doImport(); - $resultCount = $reporter->close(); + try { + $importer->doImport(); + } catch ( MWException $e ) { + $exception = $e; + } + $result = $reporter->close(); - if( WikiError::isError( $result ) ) { + if ( $exception ) { # No source or XML parse error - $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $result->getMessage() ) ); - } elseif( WikiError::isError( $resultCount ) ) { + $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $exception->getMessage() ) ); + } elseif( !$result->isGood() ) { # Zero revisions - $wgOut->wrapWikiMsg( '<p class="error">$1</p>', array( 'importfailed', $resultCount->getMessage() ) ); + $wgOut->wrapWikiMsg( "<p class=\"error\">\n$1\n</p>", array( 'importfailed', $result->getWikiText() ) ); } else { # Success! $wgOut->addWikiMsg( 'importsuccess' ); @@ -157,7 +167,7 @@ class SpecialImport extends SpecialPage { } private function showForm() { - global $wgUser, $wgOut, $wgRequest, $wgImportSources, $wgExportMaxLinkDepth; + global $wgUser, $wgOut, $wgImportSources, $wgExportMaxLinkDepth; $action = $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ); @@ -167,8 +177,8 @@ class SpecialImport extends SpecialPage { Xml::fieldset( wfMsg( 'import-upload' ) ). Xml::openElement( 'form', array( 'enctype' => 'multipart/form-data', 'method' => 'post', 'action' => $action, 'id' => 'mw-import-upload-form' ) ) . - Xml::hidden( 'action', 'submit' ) . - Xml::hidden( 'source', 'upload' ) . + Html::hidden( 'action', 'submit' ) . + Html::hidden( 'source', 'upload' ) . Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . "<tr> @@ -195,7 +205,7 @@ class SpecialImport extends SpecialPage { "</td> </tr>" . Xml::closeElement( 'table' ). - Xml::hidden( 'editToken', $wgUser->editToken() ) . + Html::hidden( 'editToken', $wgUser->editToken() ) . Xml::closeElement( 'form' ) . Xml::closeElement( 'fieldset' ) ); @@ -223,9 +233,9 @@ class SpecialImport extends SpecialPage { Xml::fieldset( wfMsg( 'importinterwiki' ) ) . Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'mw-import-interwiki-form' ) ) . wfMsgExt( 'import-interwiki-text', array( 'parse' ) ) . - Xml::hidden( 'action', 'submit' ) . - Xml::hidden( 'source', 'interwiki' ) . - Xml::hidden( 'editToken', $wgUser->editToken() ) . + Html::hidden( 'action', 'submit' ) . + Html::hidden( 'source', 'interwiki' ) . + Html::hidden( 'editToken', $wgUser->editToken() ) . Xml::openElement( 'table', array( 'id' => 'mw-import-table' ) ) . "<tr> <td class='mw-label'>" . @@ -280,7 +290,7 @@ class SpecialImport extends SpecialPage { <td> </td> <td class='mw-submit'>" . - Xml::submitButton( wfMsg( 'import-interwiki-submit' ), array( 'accesskey' => 's' ) ) . + Xml::submitButton( wfMsg( 'import-interwiki-submit' ), $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'import' ) ) . "</td> </tr>" . Xml::closeElement( 'table' ). @@ -297,9 +307,15 @@ class SpecialImport extends SpecialPage { */ class ImportReporter { private $reason=false; + private $mOriginalLogCallback = null; + private $mOriginalPageOutCallback = null; + private $mLogItemCount = 0; function __construct( $importer, $upload, $interwiki , $reason=false ) { - $importer->setPageOutCallback( array( $this, 'reportPage' ) ); + $this->mOriginalPageOutCallback = + $importer->setPageOutCallback( array( $this, 'reportPage' ) ); + $this->mOriginalLogCallback = + $importer->setLogItemCallback( array( $this, 'reportLogItem' ) ); $this->mPageCount = 0; $this->mIsUpload = $upload; $this->mInterwiki = $interwiki; @@ -310,9 +326,19 @@ class ImportReporter { global $wgOut; $wgOut->addHTML( "<ul>\n" ); } + + function reportLogItem( /* ... */ ) { + $this->mLogItemCount++; + if ( is_callable( $this->mOriginalLogCallback ) ) { + call_user_func_array( $this->mOriginalLogCallback, func_get_args() ); + } + } - function reportPage( $title, $origTitle, $revisionCount, $successCount ) { + function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) { global $wgOut, $wgUser, $wgLang, $wgContLang; + + $args = func_get_args(); + call_user_func_array( $this->mOriginalPageOutCallback, $args ); $skin = $wgUser->getSkin(); @@ -362,13 +388,18 @@ class ImportReporter { } function close() { - global $wgOut; - if( $this->mPageCount == 0 ) { + global $wgOut, $wgLang; + + if ( $this->mLogItemCount > 0 ) { + $msg = wfMsgExt( 'imported-log-entries', 'parseinline', + $wgLang->formatNum( $this->mLogItemCount ) ); + $wgOut->addHTML( Xml::tags( 'li', null, $msg ) ); + } elseif( $this->mPageCount == 0 && $this->mLogItemCount == 0 ) { $wgOut->addHTML( "</ul>\n" ); - return new WikiErrorMsg( "importnopages" ); + return Status::newFatal( 'importnopages' ); } $wgOut->addHTML( "</ul>\n" ); - return $this->mPageCount; + return Status::newGood( $this->mPageCount ); } } diff --git a/includes/specials/SpecialIpblocklist.php b/includes/specials/SpecialIpblocklist.php index dfdcf1a7..24d7f008 100644 --- a/includes/specials/SpecialIpblocklist.php +++ b/includes/specials/SpecialIpblocklist.php @@ -1,103 +1,134 @@ <?php /** + * Implements Special:ipblocklist + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * @param $ip part of title: Special:Ipblocklist/<ip>. - * @todo document + * A special page that lists existing blocks and allows users with the 'block' + * permission to remove blocks + * + * @ingroup SpecialPage */ -function wfSpecialIpblocklist( $ip = '' ) { - global $wgUser, $wgOut, $wgRequest; - $ip = $wgRequest->getVal( 'ip', $ip ); - $ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $ip ) ); - $id = $wgRequest->getVal( 'id' ); - $reason = $wgRequest->getText( 'wpUnblockReason' ); - $action = $wgRequest->getText( 'action' ); - $successip = $wgRequest->getVal( 'successip' ); - - $ipu = new IPUnblockForm( $ip, $id, $reason ); - - if( $action == 'unblock' ) { - # Check permissions - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - # Check for database lock - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Show unblock form - $ipu->showForm( '' ); - } elseif( $action == 'submit' && $wgRequest->wasPosted() - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - # Check permissions - if( !$wgUser->isAllowed( 'block' ) ) { - $wgOut->permissionRequired( 'block' ); - return; - } - # Check for database lock - if( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; - } - # Remove blocks and redirect user to success page - $ipu->doSubmit(); - } elseif( $action == 'success' ) { - # Inform the user of a successful unblock - # (No need to check permissions or locks here, - # if something was done, then it's too late!) - if ( substr( $successip, 0, 1) == '#' ) { - // A block ID was unblocked - $ipu->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) ); - } else { - // A username/IP was unblocked - $ipu->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) ); - } - } else { - # Just show the block list - $ipu->showList( '' ); +class IPUnblockForm extends SpecialPage { + var $ip, $reason, $id; + var $hideuserblocks, $hidetempblocks, $hideaddressblocks; + + function __construct() { + parent::__construct( 'Ipblocklist' ); } -} + /** + * Main execution point + * + * @param $ip part of title: Special:Ipblocklist/<ip>. + */ + function execute( $ip ) { + global $wgUser, $wgOut, $wgRequest; -/** - * implements Special:ipblocklist GUI - * @ingroup SpecialPage - */ -class IPUnblockForm { - var $ip, $reason, $id; + $this->setHeaders(); + $this->outputHeader(); - function IPUnblockForm( $ip, $id, $reason ) { - global $wgRequest; - $this->ip = strtr( $ip, '_', ' ' ); - $this->id = $id; - $this->reason = $reason; + $ip = $wgRequest->getVal( 'ip', $ip ); + $this->ip = trim( $wgRequest->getVal( 'wpUnblockAddress', $ip ) ); + $this->id = $wgRequest->getVal( 'id' ); + $this->reason = $wgRequest->getText( 'wpUnblockReason' ); $this->hideuserblocks = $wgRequest->getBool( 'hideuserblocks' ); $this->hidetempblocks = $wgRequest->getBool( 'hidetempblocks' ); $this->hideaddressblocks = $wgRequest->getBool( 'hideaddressblocks' ); + + $action = $wgRequest->getText( 'action' ); + $successip = $wgRequest->getVal( 'successip' ); + + if( $action == 'unblock' || $action == 'submit' && $wgRequest->wasPosted() ) { + # Check permissions + if( !$wgUser->isAllowed( 'block' ) ) { + $wgOut->permissionRequired( 'block' ); + return; + } + # Check for database lock + if( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + + # bug 15810: blocked admins should have limited access here + if ( $wgUser->isBlocked() ) { + if ( $this->id ) { + # This doesn't pick up on autoblocks, but admins + # should have the ipblock-exempt permission anyway + $block = Block::newFromID( $this->id ); + $user = User::newFromName( $block->mAddress ); + } else { + $user = User::newFromName( $this->ip ); + } + $status = IPBlockForm::checkUnblockSelf( $user ); + if ( $status !== true ) { + throw new ErrorPageError( 'badaccess', $status ); + } + } + + if( $action == 'unblock' ){ + # Show unblock form + $this->showForm( '' ); + } elseif( $action == 'submit' + && $wgRequest->wasPosted() + && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) + { + # Remove blocks and redirect user to success page + $this->doSubmit(); + } + + } elseif( $action == 'success' ) { + # Inform the user of a successful unblock + # (No need to check permissions or locks here, + # if something was done, then it's too late!) + if ( substr( $successip, 0, 1) == '#' ) { + // A block ID was unblocked + $this->showList( $wgOut->parse( wfMsg( 'unblocked-id', $successip ) ) ); + } else { + // A username/IP was unblocked + $this->showList( $wgOut->parse( wfMsg( 'unblocked', $successip ) ) ); + } + } else { + # Just show the block list + $this->showList( '' ); + } } /** * Generates the unblock form + * * @param $err string: error message * @return $out string: HTML form */ function showForm( $err ) { global $wgOut, $wgUser, $wgSysopUserBans; - $wgOut->setPagetitle( wfMsg( 'unblockip' ) ); $wgOut->addWikiMsg( 'unblockiptext' ); - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $action = $titleObj->getLocalURL( "action=submit" ); + $action = $this->getTitle()->getLocalURL( 'action=submit' ); - if ( $err != "" ) { - $wgOut->setSubtitle( wfMsg( "formerror" ) ); - $wgOut->addWikiText( Xml::tags( 'span', array( 'class' => 'error' ), $err ) . "\n" ); + if ( $err != '' ) { + $wgOut->setSubtitle( wfMsg( 'formerror' ) ); + $wgOut->addWikiText( Html::rawElement( 'span', array( 'class' => 'error' ), $err ) . "\n" ); } $addressPart = false; @@ -106,7 +137,7 @@ class IPUnblockForm { if ( $block ) { $encName = htmlspecialchars( $block->getRedactedName() ); $encId = $this->id; - $addressPart = $encName . Xml::hidden( 'id', $encId ); + $addressPart = $encName . Html::hidden( 'id', $encId ); $ipa = wfMsgHtml( $wgSysopUserBans ? 'ipadressorusername' : 'ipaddress' ); } } @@ -116,10 +147,10 @@ class IPUnblockForm { } $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'ipb-unblock' ) ) . - Xml::openElement( 'table', array( 'id' => 'mw-unblock-table' ) ). + Html::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'unblockip' ) ) . + Html::openElement( 'fieldset' ) . + Html::element( 'legend', null, wfMsg( 'ipb-unblock' ) ) . + Html::openElement( 'table', array( 'id' => 'mw-unblock-table' ) ). "<tr> <td class='mw-label'> {$ipa} @@ -137,15 +168,15 @@ class IPUnblockForm { "</td> </tr> <tr> - <td> </td> + <td> </td> <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'ipusubmit' ), array( 'name' => 'wpBlock', 'tabindex' => '3' ) ) . "</td> </tr>" . - Xml::closeElement( 'table' ) . - Xml::closeElement( 'fieldset' ) . - Xml::hidden( 'wpEditToken', $wgUser->editToken() ) . - Xml::closeElement( 'form' ) . "\n" + Html::closeElement( 'table' ) . + Html::closeElement( 'fieldset' ) . + Html::hidden( 'wpEditToken', $wgUser->editToken() ) . + Html::closeElement( 'form' ) . "\n" ); } @@ -162,34 +193,35 @@ class IPUnblockForm { * case it contains the range $ip is part of. * @return array array(message key, parameters) on failure, empty array on success */ - - static function doUnblock(&$id, &$ip, &$reason, &$range = null, $blocker=null) { + public static function doUnblock( &$id, &$ip, &$reason, &$range = null, $blocker = null ) { if ( $id ) { $block = Block::newFromID( $id ); if ( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); + return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); } $ip = $block->getRedactedName(); } else { - $block = new Block(); $ip = trim( $ip ); if ( substr( $ip, 0, 1 ) == "#" ) { $id = substr( $ip, 1 ); $block = Block::newFromID( $id ); if( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); + return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); } $ip = $block->getRedactedName(); } else { + # FIXME: do proper sanitisation/cleanup here + $ip = str_replace( '_', ' ', $ip ); + $block = Block::newFromDB( $ip ); if ( !$block ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); + return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); } if( $block->mRangeStart != $block->mRangeEnd && !strstr( $ip, "/" ) ) { /* If the specified IP is a single address, and the block is * a range block, don't unblock the range. */ $range = $block->mAddress; - return array('ipb_blocked_as_range', $ip, $range); + return array( 'ipb_blocked_as_range', $ip, $range ); } } } @@ -198,13 +230,13 @@ class IPUnblockForm { # If the name was hidden and the blocking user cannot hide # names, then don't allow any block removals... - if( $blocker && $block->mHideName && !$blocker->isAllowed('hideuser') ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); + if( $blocker && $block->mHideName && !$blocker->isAllowed( 'hideuser' ) ) { + return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); } # Delete block if ( !$block->delete() ) { - return array('ipb_cant_unblock', htmlspecialchars($id)); + return array( 'ipb_cant_unblock', htmlspecialchars( $id ) ); } # Unset _deleted fields as needed @@ -220,23 +252,23 @@ class IPUnblockForm { function doSubmit() { global $wgOut, $wgUser; - $retval = self::doUnblock($this->id, $this->ip, $this->reason, $range, $wgUser); - if( !empty($retval) ) { - $key = array_shift($retval); - $this->showForm(wfMsgReal($key, $retval)); + + $retval = self::doUnblock( $this->id, $this->ip, $this->reason, $range, $wgUser ); + if( !empty( $retval ) ) { + $key = array_shift( $retval ); + $this->showForm( wfMsgReal( $key, $retval ) ); return; } + # Report to the user - $titleObj = SpecialPage::getTitleFor( "Ipblocklist" ); - $success = $titleObj->getFullURL( "action=success&successip=" . urlencode( $this->ip ) ); + $success = $this->getTitle()->getFullURL( 'action=success&successip=' . urlencode( $this->ip ) ); $wgOut->redirect( $success ); } function showList( $msg ) { global $wgOut, $wgUser; - $wgOut->setPagetitle( wfMsg( "ipblocklist" ) ); - if ( $msg != "" ) { + if ( $msg != '' ) { $wgOut->setSubtitle( $msg ); } @@ -246,7 +278,6 @@ class IPUnblockForm { } $conds = array(); - $matches = array(); // Is user allowed to see all the blocks? if ( !$wgUser->isAllowed( 'hideuser' ) ) $conds['ipb_deleted'] = 0; @@ -255,25 +286,26 @@ class IPUnblockForm { } elseif ( substr( $this->ip, 0, 1 ) == '#' ) { $conds['ipb_id'] = substr( $this->ip, 1 ); // Single IPs - } elseif ( IP::isIPAddress($this->ip) && strpos($this->ip,'/') === false ) { - if( $iaddr = IP::toHex($this->ip) ) { + } elseif ( IP::isIPAddress( $this->ip ) && strpos( $this->ip, '/' ) === false ) { + $iaddr = IP::toHex( $this->ip ); + if( $iaddr ) { # Only scan ranges which start in this /16, this improves search speed # Blocks should not cross a /16 boundary. $range = substr( $iaddr, 0, 4 ); // Fixme -- encapsulate this sort of query-building. $dbr = wfGetDB( DB_SLAVE ); - $encIp = $dbr->addQuotes( IP::sanitizeIP($this->ip) ); + $encIp = $dbr->addQuotes( IP::sanitizeIP( $this->ip ) ); $encAddr = $dbr->addQuotes( $iaddr ); $conds[] = "(ipb_address = $encIp) OR (ipb_range_start" . $dbr->buildLike( $range, $dbr->anyString() ) . " AND ipb_range_start <= $encAddr AND ipb_range_end >= $encAddr)"; } else { - $conds['ipb_address'] = IP::sanitizeIP($this->ip); + $conds['ipb_address'] = IP::sanitizeIP( $this->ip ); } $conds['ipb_auto'] = 0; // IP range - } elseif ( IP::isIPAddress($this->ip) ) { + } elseif ( IP::isIPAddress( $this->ip ) ) { $conds['ipb_address'] = Block::normaliseRange( $this->ip ); $conds['ipb_auto'] = 0; } else { @@ -315,7 +347,7 @@ class IPUnblockForm { if ( $pager->getNumRows() ) { $wgOut->addHTML( $pager->getNavigationBar() . - Xml::tags( 'ul', null, $pager->getBody() ) . + Html::rawElement( 'ul', null, $pager->getBody() ) . $pager->getNavigationBar() ); } elseif ( $this->ip != '') { @@ -338,7 +370,7 @@ class IPUnblockForm { } function searchForm() { - global $wgScript, $wgRequest, $wgLang; + global $wgScript, $wgLang; $showhide = array( wfMsg( 'show' ), wfMsg( 'hide' ) ); $nondefaults = array(); @@ -365,30 +397,32 @@ class IPUnblockForm { $hl = $wgLang->pipeList( $links ); return - Xml::tags( 'form', array( 'action' => $wgScript ), - Xml::hidden( 'title', SpecialPage::getTitleFor( 'Ipblocklist' )->getPrefixedDbKey() ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) . + Html::rawElement( 'form', array( 'action' => $wgScript ), + Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . + Html::openElement( 'fieldset' ) . + Html::element( 'legend', null, wfMsg( 'ipblocklist-legend' ) ) . Xml::inputLabel( wfMsg( 'ipblocklist-username' ), 'ip', 'ip', /* size */ false, $this->ip ) . - ' ' . + ' ' . Xml::submitButton( wfMsg( 'ipblocklist-submit' ) ) . '<br />' . $hl . - Xml::closeElement( 'fieldset' ) + Html::closeElement( 'fieldset' ) ); } /** * Makes change an option link which carries all the other options + * * @param $title see Title - * @param $override - * @param $options + * @param $override Array: special query string options, will override the + * ones in $options + * @param $options Array: query string options + * @param $active Boolean: whether to display the link in bold */ function makeOptionsLink( $title, $override, $options, $active = false ) { global $wgUser; $sk = $wgUser->getSkin(); $params = $override + $options; - $ipblocklist = SpecialPage::getTitleFor( 'Ipblocklist' ); - return $sk->link( $ipblocklist, htmlspecialchars( $title ), + return $sk->link( $this->getTitle(), htmlspecialchars( $title ), ( $active ? array( 'style'=>'font-weight: bold;' ) : array() ), $params, array( 'known' ) ); } @@ -453,11 +487,10 @@ class IPUnblockForm { $line = wfMsgReplaceArgs( $msg['blocklistline'], array( $formattedTime, $blocker, $target, $properties ) ); - $unblocklink = ''; $changeblocklink = ''; $toolLinks = ''; if ( $wgUser->isAllowed( 'block' ) ) { - $unblocklink = $sk->link( SpecialPage::getTitleFor( 'Ipblocklist' ), + $unblocklink = $sk->link( $this->getTitle(), $msg['unblocklink'], array(), array( 'action' => 'unblock', 'id' => $block->mId ), @@ -513,7 +546,7 @@ class IPBlocklistPager extends ReverseChronologicalPager { # 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. - while ( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { $name = str_replace( ' ', '_', $row->ipb_by_text ); $lb->add( NS_USER, $name ); $lb->add( NS_USER_TALK, $name ); diff --git a/includes/specials/SpecialLinkSearch.php b/includes/specials/SpecialLinkSearch.php index 70b2257a..9dee9d5a 100644 --- a/includes/specials/SpecialLinkSearch.php +++ b/includes/specials/SpecialLinkSearch.php @@ -1,11 +1,27 @@ <?php /** + * Implements Special:LinkSearch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage - * * @author Brion Vibber - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ + /** * Special:LinkSearch to search the external-links table. @@ -13,7 +29,7 @@ function wfSpecialLinkSearch( $par ) { list( $limit, $offset ) = wfCheckLimits(); - global $wgOut, $wgRequest, $wgUrlProtocols, $wgMiserMode, $wgLang; + global $wgOut, $wgUrlProtocols, $wgMiserMode, $wgLang; $target = $GLOBALS['wgRequest']->getVal( 'target', $par ); $namespace = $GLOBALS['wgRequest']->getIntorNull( 'namespace', null ); @@ -44,19 +60,18 @@ function wfSpecialLinkSearch( $par ) { $protocol = ''; } - $wgOut->allowClickjacking(); - $self = Title::makeTitle( NS_SPECIAL, 'Linksearch' ); - + + $wgOut->allowClickjacking(); $wgOut->addWikiMsg( 'linksearch-text', '<nowiki>' . $wgLang->commaList( $wgUrlProtocols ) . '</nowiki>' ); - $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) . - Xml::hidden( 'title', $self->getPrefixedDbKey() ) . + $s = Xml::openElement( 'form', array( 'id' => 'mw-linksearch-form', 'method' => 'get', 'action' => $GLOBALS['wgScript'] ) ) . + Html::hidden( 'title', $self->getPrefixedDbKey() ) . '<fieldset>' . Xml::element( 'legend', array(), wfMsg( 'linksearch' ) ) . Xml::inputLabel( wfMsg( 'linksearch-pat' ), 'target', 'target', 50, $target ) . ' '; if ( !$wgMiserMode ) { $s .= Xml::label( wfMsg( 'linksearch-ns' ), 'namespace' ) . ' ' . - XML::namespaceSelector( $namespace, '' ); + Xml::namespaceSelector( $namespace, '' ); } $s .= Xml::submitButton( wfMsg( 'linksearch-ok' ) ) . '</fieldset>' . @@ -73,6 +88,9 @@ function wfSpecialLinkSearch( $par ) { } } +/** + * @ingroup SpecialPage + */ class LinkSearchPage extends QueryPage { function setParams( $params ) { $this->mQuery = $params['query']; @@ -98,8 +116,9 @@ class LinkSearchPage extends QueryPage { $field = 'el_index'; $rv = LinkFilter::makeLikeArray( $query , $prot ); if ($rv === false) { - //makeLike doesn't handle wildcard in IP, so we'll have to munge here. + // LinkFilter doesn't handle wildcard in IP, so we'll have to munge here. if (preg_match('/^(:?[0-9]{1,3}\.)+\*\s*$|^(:?[0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]*\*\s*$/', $query)) { + $dbr = wfGetDB( DB_SLAVE ); $rv = array( $prot . rtrim($query, " \t*"), $dbr->anyString() ); $field = 'el_to'; } @@ -162,7 +181,7 @@ class LinkSearchPage extends QueryPage { */ function doQuery( $offset, $limit, $shownavigation=true ) { global $wgOut; - list( $this->mMungedQuery, $clause ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt ); + list( $this->mMungedQuery, ) = LinkSearchPage::mungeQuery( $this->mQuery, $this->mProt ); if( $this->mMungedQuery === false ) { $wgOut->addWikiMsg( 'linksearch-error' ); } else { diff --git a/includes/specials/SpecialListfiles.php b/includes/specials/SpecialListfiles.php index b9332422..350e833b 100644 --- a/includes/specials/SpecialListfiles.php +++ b/includes/specials/SpecialListfiles.php @@ -1,16 +1,30 @@ <?php /** + * Implements Special:Listfiles + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ - -/** - * - */ -function wfSpecialListfiles() { + +function wfSpecialListfiles( $par = null ) { global $wgOut; - $pager = new ImageListPager; + $pager = new ImageListPager( $par ); $limit = $pager->getForm(); $body = $pager->getBody(); @@ -24,21 +38,32 @@ function wfSpecialListfiles() { class ImageListPager extends TablePager { var $mFieldNames = null; var $mQueryConds = array(); - - function __construct() { + var $mUserName = null; + + function __construct( $par = null ) { global $wgRequest, $wgMiserMode; if ( $wgRequest->getText( 'sort', 'img_date' ) == 'img_date' ) { $this->mDefaultDirection = true; } else { $this->mDefaultDirection = false; } + + $userName = $wgRequest->getText( 'user', $par ); + if ( $userName ) { + $nt = Title::newFromText( $userName, NS_USER ); + if ( !is_null( $nt ) ) { + $this->mUserName = $nt->getText(); + $this->mQueryConds['img_user_text'] = $this->mUserName; + } + } + $search = $wgRequest->getText( 'ilsearch' ); if ( $search != '' && !$wgMiserMode ) { $nt = Title::newFromURL( $search ); - if( $nt ) { + if ( $nt ) { $dbr = wfGetDB( DB_SLAVE ); - $this->mQueryConds = array( 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(), - strtolower( $nt->getDBkey() ), $dbr->anyString() ) ); + $this->mQueryConds[] = 'LOWER(img_name)' . $dbr->buildLike( $dbr->anyString(), + strtolower( $nt->getDBkey() ), $dbr->anyString() ); } } @@ -49,6 +74,7 @@ class ImageListPager extends TablePager { if ( !$this->mFieldNames ) { global $wgMiserMode; $this->mFieldNames = array( + 'thumb' => wfMsg( 'listfiles_thumb' ), 'img_timestamp' => wfMsg( 'listfiles_date' ), 'img_name' => wfMsg( 'listfiles_name' ), 'img_user_text' => wfMsg( 'listfiles_user' ), @@ -63,7 +89,11 @@ class ImageListPager extends TablePager { } function isFieldSortable( $field ) { - static $sortable = array( 'img_timestamp', 'img_name', 'img_size' ); + static $sortable = array( 'img_timestamp', 'img_name' ); + if ( $field == 'img_size' ) { + # No index for both img_size and img_user_text + return !isset( $this->mQueryConds['img_user_text'] ); + } return in_array( $field, $sortable ); } @@ -71,6 +101,7 @@ class ImageListPager extends TablePager { $tables = array( 'image' ); $fields = array_keys( $this->getFieldNames() ); $fields[] = 'img_user'; + $fields[array_search('thumb', $fields)] = 'img_name as thumb'; $options = $join_conds = array(); # Depends on $wgMiserMode @@ -78,9 +109,11 @@ class ImageListPager extends TablePager { $tables[] = 'oldimage'; # Need to rewrite this one - foreach ( $fields as &$field ) - if ( $field == 'count' ) + foreach ( $fields as &$field ) { + if ( $field == 'count' ) { $field = 'COUNT(oi_archive_name) as count'; + } + } unset( $field ); $dbr = wfGetDB( DB_SLAVE ); @@ -110,7 +143,7 @@ class ImageListPager extends TablePager { if ( $this->mResult->numRows() ) { $lb = new LinkBatch; $this->mResult->seek( 0 ); - while ( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { if ( $row->img_user ) { $lb->add( NS_USER, str_replace( ' ', '_', $row->img_user_text ) ); } @@ -124,14 +157,18 @@ class ImageListPager extends TablePager { function formatValue( $field, $value ) { global $wgLang; switch ( $field ) { + case 'thumb': + $file = wfLocalFile( $value ); + $thumb = $file->transform( array( 'width' => 180 ) ); + return $thumb->toHtml( array( 'desc-link' => true ) ); case 'img_timestamp': 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()->linkKnown( Title::makeTitle( NS_FILE, $name ), $value ); + $filePage = Title::makeTitle( NS_FILE, $value ); + $link = $this->getSkin()->linkKnown( $filePage, htmlspecialchars( $filePage->getText() ) ); $image = wfLocalFile( $value ); $url = $image->getURL(); $download = Xml::element('a', array( 'href' => $url ), $imgfile ); @@ -158,21 +195,26 @@ class ImageListPager extends TablePager { function getForm() { global $wgRequest, $wgScript, $wgMiserMode; $search = $wgRequest->getText( 'ilsearch' ); - - $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() ) ); - + $inputForm = array(); + $inputForm['table_pager_limit_label'] = $this->getLimitSelect(); if ( !$wgMiserMode ) { - $s .= "<br />\n" . - Xml::inputLabel( wfMsg( 'listfiles_search_for' ), 'ilsearch', 'mw-ilsearch', 20, $search ); + $inputForm['listfiles_search_for'] = Html::input( 'ilsearch', $search, 'text', array( + 'size' => '40', + 'maxlength' => '255', + 'id' => 'mw-ilsearch', + ) ); } - $s .= ' ' . - Xml::submitButton( wfMsg( 'table_pager_limit_submit' ) ) ."\n" . - $this->getHiddenFields( array( 'limit', 'ilsearch' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n"; + $inputForm['username'] = Html::input( 'user', $this->mUserName, 'text', array( + 'size' => '40', + 'maxlength' => '255', + 'id' => 'mw-listfiles-user', + ) ); + $s = Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listfiles-form' ) ) . + Xml::fieldset( wfMsg( 'listfiles' ) ) . + Xml::buildForm( $inputForm, 'table_pager_limit_submit' ) . + $this->getHiddenFields( array( 'limit', 'ilsearch', 'user' ) ) . + Html::closeElement( 'fieldset' ) . + Html::closeElement( 'form' ) . "\n"; return $s; } @@ -187,4 +229,25 @@ class ImageListPager extends TablePager { function getSortHeaderClass() { return 'listfiles_sort ' . parent::getSortHeaderClass(); } + + function getPagingQueries() { + $queries = parent::getPagingQueries(); + if ( !is_null( $this->mUserName ) ) { + # Append the username to the query string + foreach ( $queries as &$query ) { + $query['user'] = $this->mUserName; + } + } + return $queries; + } + + function getDefaultQuery() { + $queries = parent::getDefaultQuery(); + if ( !isset( $queries['user'] ) + && !is_null( $this->mUserName ) ) + { + $queries['user'] = $this->mUserName; + } + return $queries; + } } diff --git a/includes/specials/SpecialListgrouprights.php b/includes/specials/SpecialListgrouprights.php index 83724a4f..910ffd08 100644 --- a/includes/specials/SpecialListgrouprights.php +++ b/includes/specials/SpecialListgrouprights.php @@ -1,4 +1,25 @@ <?php +/** + * Implements Special:Listgrouprights + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** * This special page lists all defined user groups and the associated rights. @@ -24,10 +45,9 @@ class SpecialListGroupRights extends SpecialPage { * Show the special page */ public function execute( $par ) { - global $wgOut, $wgImplicitGroups, $wgMessageCache; + global $wgOut, $wgImplicitGroups; global $wgGroupPermissions, $wgRevokePermissions, $wgAddGroups, $wgRemoveGroups; global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - $wgMessageCache->loadAllMessages(); $this->setHeaders(); $this->outputHeader(); @@ -40,8 +60,23 @@ class SpecialListGroupRights extends SpecialPage { '</tr>' ); - foreach( $wgGroupPermissions as $group => $permissions ) { - $groupname = ( $group == '*' ) ? 'all' : $group; // Replace * with a more descriptive groupname + $allGroups = array_unique( array_merge( + array_keys( $wgGroupPermissions ), + array_keys( $wgRevokePermissions ), + array_keys( $wgAddGroups ), + array_keys( $wgRemoveGroups ), + array_keys( $wgGroupsAddToSelf ), + array_keys( $wgGroupsRemoveFromSelf ) + ) ); + asort( $allGroups ); + + foreach ( $allGroups as $group ) { + $permissions = isset( $wgGroupPermissions[$group] ) + ? $wgGroupPermissions[$group] + : array(); + $groupname = ( $group == '*' ) // Replace * with a more descriptive groupname + ? 'all' + : $group; $msg = wfMsg( 'group-' . $groupname ); if ( wfEmptyMsg( 'group-' . $groupname, $msg ) || $msg == '' ) { @@ -59,11 +94,11 @@ class SpecialListGroupRights extends SpecialPage { if( $group == '*' ) { // Do not make a link for the generic * group - $grouppage = htmlspecialchars($groupnameLocalized); + $grouppage = htmlspecialchars( $groupnameLocalized ); } else { $grouppage = $this->skin->link( Title::newFromText( $grouppageLocalized ), - htmlspecialchars($groupnameLocalized) + htmlspecialchars( $groupnameLocalized ) ); } @@ -95,16 +130,15 @@ class SpecialListGroupRights extends SpecialPage { $addgroupsSelf = isset( $wgGroupsAddToSelf[$group] ) ? $wgGroupsAddToSelf[$group] : array(); $removegroupsSelf = isset( $wgGroupsRemoveFromSelf[$group] ) ? $wgGroupsRemoveFromSelf[$group] : array(); - $wgOut->addHTML( - '<tr> - <td>' . - $grouppage . $grouplink . - '</td> - <td>' . + $id = $group == '*' ? false : Sanitizer::escapeId( $group ); + $wgOut->addHTML( Html::rawElement( 'tr', array( 'id' => $id ), + " + <td>$grouppage$grouplink</td> + <td>" . self::formatPermissions( $permissions, $revoke, $addgroups, $removegroups, $addgroupsSelf, $removegroupsSelf ) . '</td> - </tr>' - ); + ' + ) ); } $wgOut->addHTML( Xml::closeElement( 'table' ) . "\n<br /><hr />\n" diff --git a/includes/specials/SpecialListredirects.php b/includes/specials/SpecialListredirects.php index bf594070..315047da 100644 --- a/includes/specials/SpecialListredirects.php +++ b/includes/specials/SpecialListredirects.php @@ -1,11 +1,27 @@ <?php /** + * Implements Special:Listredirects + * + * Copyright © 2006 Rob Church + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage - * * @author Rob Church <robchur@gmail.com> - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ /** @@ -49,10 +65,10 @@ class ListredirectsPage extends QueryPage { $targetLink = $skin->link( $target ); return "$rd_link $arr $targetLink"; } else { - return "<s>$rd_link</s>"; + return "<del>$rd_link</del>"; } } else { - return "<s>$rd_link</s>"; + return "<del>$rd_link</del>"; } } } diff --git a/includes/specials/SpecialListusers.php b/includes/specials/SpecialListusers.php index bdb59980..abc0363a 100644 --- a/includes/specials/SpecialListusers.php +++ b/includes/specials/SpecialListusers.php @@ -1,27 +1,26 @@ <?php - -# Copyright (C) 2004 Brion Vibber, lcrocker, Tim Starling, -# Domas Mituzas, Ashar Voultoiz, Jens Frank, Zhengzhu. -# -# © 2006 Rob Church <robchur@gmail.com> -# -# 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 /** + * Implements Special:Listusers + * + * Copyright © 2004 Brion Vibber, lcrocker, Tim Starling, + * Domas Mituzas, Ashar Voultoiz, Jens Frank, Zhengzhu, + * 2006 Rob Church <robchur@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ @@ -120,16 +119,18 @@ class UsersPager extends AlphabeticPager { function formatRow( $row ) { global $wgLang; + if ($row->user_id == 0) #Bug 16487 + return ''; + $userPage = Title::makeTitle( NS_USER, $row->user_name ); $name = $this->getSkin()->link( $userPage, htmlspecialchars( $userPage->getText() ) ); - if( $row->numgroups > 1 || ( $this->requestedGroup && $row->numgroups == 1 ) ) { + $groups_list = self::getGroups( $row->user_id ); + if( count( $groups_list ) > 0 ) { $list = array(); - foreach( self::getGroups( $row->user_id ) as $group ) + foreach( $groups_list as $group ) $list[] = self::buildGroupLink( $group ); $groups = $wgLang->commaList( $list ); - } elseif( $row->numgroups == 1 ) { - $groups = self::buildGroupLink( $row->singlegroup ); } else { $groups = ''; } @@ -166,7 +167,7 @@ class UsersPager extends AlphabeticPager { } $this->mResult->rewind(); $batch = new LinkBatch; - while ( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->user_name ) ); } $batch->execute(); @@ -175,13 +176,13 @@ class UsersPager extends AlphabeticPager { } function getPageHeader( ) { - global $wgScript, $wgRequest; + global $wgScript; $self = $this->getTitle(); # Form tag $out = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'id' => 'mw-listusers-form' ) ) . Xml::fieldset( wfMsg( 'listusers' ) ) . - Xml::hidden( 'title', $self->getPrefixedDbKey() ); + Html::hidden( 'title', $self->getPrefixedDbKey() ); # Username field $out .= Xml::label( wfMsg( 'listusersfrom' ), 'offset' ) . ' ' . @@ -195,14 +196,14 @@ class UsersPager extends AlphabeticPager { $out .= Xml::option( $groupText, $group, $group == $this->requestedGroup ); $out .= Xml::closeElement( 'select' ) . '<br />'; $out .= Xml::checkLabel( wfMsg('listusers-editsonly'), 'editsOnly', 'editsOnly', $this->editsOnly ); - $out .= ' '; + $out .= ' '; $out .= Xml::checkLabel( wfMsg('listusers-creationsort'), 'creationSort', 'creationSort', $this->creationSort ); $out .= '<br />'; wfRunHooks( 'SpecialListusersHeaderForm', array( $this, &$out ) ); # Submit button and form bottom - $out .= Xml::hidden( 'limit', $this->mLimit ); + $out .= Html::hidden( 'limit', $this->mLimit ); $out .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ); wfRunHooks( 'SpecialListusersHeader', array( $this, &$out ) ); $out .= Xml::closeElement( 'fieldset' ) . @@ -269,13 +270,13 @@ class UsersPager extends AlphabeticPager { * $par string (optional) A group to list users from */ function wfSpecialListusers( $par = null ) { - global $wgRequest, $wgOut; + global $wgOut; $up = new UsersPager($par); # getBody() first to check, if empty $usersbody = $up->getBody(); - $s = XML::openElement( 'div', array('class' => 'mw-spcontent') ); + $s = Xml::openElement( 'div', array('class' => 'mw-spcontent') ); $s .= $up->getPageHeader(); if( $usersbody ) { $s .= $up->getNavigationBar(); @@ -284,6 +285,6 @@ function wfSpecialListusers( $par = null ) { } else { $s .= '<p>' . wfMsgHTML('listusers-noresult') . '</p>'; }; - $s .= XML::closeElement( 'div' ); + $s .= Xml::closeElement( 'div' ); $wgOut->addHTML( $s ); } diff --git a/includes/specials/SpecialLockdb.php b/includes/specials/SpecialLockdb.php index 8c701dd6..aad3cea4 100644 --- a/includes/specials/SpecialLockdb.php +++ b/includes/specials/SpecialLockdb.php @@ -1,97 +1,108 @@ <?php /** + * Implements Special:Lockdb + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * Constructor + * A form to make the database readonly (eg for maintenance purposes). + * + * @ingroup SpecialPage */ -function wfSpecialLockdb() { - global $wgUser, $wgOut, $wgRequest; +class SpecialLockdb extends SpecialPage { + var $reason = ''; - if( !$wgUser->isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); - return; + public function __construct() { + parent::__construct( 'Lockdb', 'siteadmin' ); } - # If the lock file isn't writable, we can do sweet bugger all - global $wgReadOnlyFile; - if( !is_writable( dirname( $wgReadOnlyFile ) ) ) { - DBLockForm::notWritable(); - return; - } + public function execute( $par ) { + global $wgUser, $wgOut, $wgRequest; - $action = $wgRequest->getVal( 'action' ); - $f = new DBLockForm(); + $this->setHeaders(); - if ( 'success' == $action ) { - $f->showSuccess(); - } else if ( 'submit' == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( '' ); - } -} + if( !$wgUser->isAllowed( 'siteadmin' ) ) { + $wgOut->permissionRequired( 'siteadmin' ); + return; + } -/** - * A form to make the database readonly (eg for maintenance purposes). - * @ingroup SpecialPage - */ -class DBLockForm { - var $reason = ''; + $this->outputHeader(); - function DBLockForm() { - global $wgRequest; - $this->reason = $wgRequest->getText( 'wpLockReason' ); + # If the lock file isn't writable, we can do sweet bugger all + global $wgReadOnlyFile; + if( !is_writable( dirname( $wgReadOnlyFile ) ) ) { + self::notWritable(); + return; + } + + $action = $wgRequest->getVal( 'action' ); + $this->reason = $wgRequest->getVal( 'wpLockReason', '' ); + + if ( $action == 'success' ) { + $this->showSuccess(); + } else if ( $action == 'submit' && $wgRequest->wasPosted() && + $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $this->doSubmit(); + } else { + $this->showForm(); + } } - function showForm( $err ) { + private function showForm( $err = '' ) { global $wgOut, $wgUser; - $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); $wgOut->addWikiMsg( 'lockdbtext' ); - if ( $err != "" ) { + if ( $err != '' ) { $wgOut->setSubtitle( wfMsg( 'formerror' ) ); $wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" ); } - $lc = htmlspecialchars( wfMsg( 'lockconfirm' ) ); - $lb = htmlspecialchars( wfMsg( 'lockbtn' ) ); - $elr = htmlspecialchars( wfMsg( 'enterlockreason' ) ); - $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); - $action = $titleObj->escapeLocalURL( 'action=submit' ); - $reason = htmlspecialchars( $this->reason ); - $token = htmlspecialchars( $wgUser->editToken() ); - - $wgOut->addHTML( <<<HTML -<form id="lockdb" method="post" action="{$action}"> -{$elr}: -<textarea name="wpLockReason" rows="10" cols="60" wrap="virtual">{$reason}</textarea> -<table border="0"> + + $wgOut->addHTML( + Html::openElement( 'form', array( 'id' => 'lockdb', 'method' => 'POST', + 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) ). "\n" . + wfMsgHtml( 'enterlockreason' ) . ":\n" . + Html::textarea( 'wpLockReason', $this->reason, array( 'rows' => 4 ) ). " +<table> <tr> - <td align="right"> - <input type="checkbox" name="wpLockConfirm" /> + " . Html::openElement( 'td', array( 'style' => 'text-align:right' ) ) . " + " . Html::input( 'wpLockConfirm', null, 'checkbox' ) . " </td> - <td align="left">{$lc}</td> + " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . + wfMsgHtml( 'lockconfirm' ) . "</td> </tr> <tr> - <td> </td> - <td align="left"> - <input type="submit" name="wpLock" value="{$lb}" /> + <td> </td> + " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . " + " . Html::input( 'wpLock', wfMsg( 'lockbtn' ), 'submit' ) . " </td> </tr> -</table> -<input type="hidden" name="wpEditToken" value="{$token}" /> -</form> -HTML -); +</table>\n" . + Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n" . + Html::closeElement( 'form' ) + ); } - function doSubmit() { - global $wgOut, $wgUser, $wgLang, $wgRequest; + private function doSubmit() { + global $wgOut, $wgUser, $wgContLang, $wgRequest; global $wgReadOnlyFile; if ( ! $wgRequest->getCheck( 'wpLockConfirm' ) ) { @@ -109,14 +120,13 @@ HTML } fwrite( $fp, $this->reason ); fwrite( $fp, "\n<p>(by " . $wgUser->getName() . " at " . - $wgLang->timeanddate( wfTimestampNow() ) . ")</p>\n" ); + $wgContLang->timeanddate( wfTimestampNow() ) . ")</p>\n" ); fclose( $fp ); - $titleObj = SpecialPage::getTitleFor( 'Lockdb' ); - $wgOut->redirect( $titleObj->getFullURL( 'action=success' ) ); + $wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) ); } - function showSuccess() { + private function showSuccess() { global $wgOut; $wgOut->setPagetitle( wfMsg( 'lockdb' ) ); diff --git a/includes/specials/SpecialLog.php b/includes/specials/SpecialLog.php index d1ccc8c4..a2af8de5 100644 --- a/includes/specials/SpecialLog.php +++ b/includes/specials/SpecialLog.php @@ -1,86 +1,127 @@ <?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 - /** + * Implements Special:Log + * + * Copyright © 2008 Aaron Schulz + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * constructor + * A special page that lists log entries + * + * @ingroup SpecialPage */ -function wfSpecialLog( $par = '' ) { - global $wgRequest, $wgOut, $wgUser, $wgLogTypes; - - # Get parameters - $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) ); - $symsForAll = array( '*', 'all' ); - if ( $parms[0] != '' && ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) ) { - $type = $par; - $user = $wgRequest->getText( 'user' ); - } else if ( count( $parms ) == 2 ) { - $type = $parms[0]; - $user = $parms[1]; - } else { - $type = $wgRequest->getVal( 'type' ); - $user = ( $par != '' ) ? $par : $wgRequest->getText( 'user' ); +class SpecialLog extends SpecialPage { + + public function __construct() { + parent::__construct( 'Log' ); } - $title = $wgRequest->getText( 'page' ); - $pattern = $wgRequest->getBool( 'pattern' ); - $y = $wgRequest->getIntOrNull( 'year' ); - $m = $wgRequest->getIntOrNull( 'month' ); - $tagFilter = $wgRequest->getVal( 'tagfilter' ); - # Don't let the user get stuck with a certain date - $skip = $wgRequest->getText( 'offset' ) || $wgRequest->getText( 'dir' ) == 'prev'; - if( $skip ) { - $y = ''; - $m = ''; + + public function execute( $par ) { + global $wgRequest; + + $this->setHeaders(); + $this->outputHeader(); + + $opts = new FormOptions; + $opts->add( 'type', '' ); + $opts->add( 'user', '' ); + $opts->add( 'page', '' ); + $opts->add( 'pattern', false ); + $opts->add( 'year', null, FormOptions::INTNULL ); + $opts->add( 'month', null, FormOptions::INTNULL ); + $opts->add( 'tagfilter', '' ); + $opts->add( 'offset', '' ); + $opts->add( 'dir', '' ); + $opts->add( 'offender', '' ); + + // Set values + $opts->fetchValuesFromRequest( $wgRequest ); + if ( $par ) { + $this->parseParams( $opts, (string)$par ); + } + + # Don't let the user get stuck with a certain date + if ( $opts->getValue( 'offset' ) || $opts->getValue( 'dir' ) == 'prev' ) { + $opts->setValue( 'year', '' ); + $opts->setValue( 'month', '' ); + } + + # Handle type-specific inputs + $qc = array(); + if ( $opts->getValue( 'type' ) == 'suppress' ) { + $offender = User::newFromName( $opts->getValue( 'offender' ), false ); + if ( $offender && $offender->getId() > 0 ) { + $qc = array( 'ls_field' => 'target_author_id', 'ls_value' => $offender->getId() ); + } elseif ( $offender && IP::isIPAddress( $offender->getName() ) ) { + $qc = array( 'ls_field' => 'target_author_ip', 'ls_value' => $offender->getName() ); + } + } + + $this->show( $opts, $qc ); } - # 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() ); + + private function parseParams( FormOptions $opts, $par ) { + global $wgLogTypes; + + # Get parameters + $parms = explode( '/', ($par = ( $par !== null ) ? $par : '' ) ); + $symsForAll = array( '*', 'all' ); + if ( $parms[0] != '' && ( in_array( $par, $wgLogTypes ) || in_array( $par, $symsForAll ) ) ) { + $opts->setValue( 'type', $par ); + } elseif ( count( $parms ) == 2 ) { + $opts->setValue( 'type', $parms[0] ); + $opts->setValue( 'user', $parms[1] ); + } elseif ( $par != '' ) { + $opts->setValue( 'user', $par ); } } - # 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, $qc, $y, $m, $tagFilter ); - # Set title and add header - $loglist->showHeader( $pager->getType() ); - # Show form options - $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(), - $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $tagFilter ); - # Insert list - $logBody = $pager->getBody(); - if( $logBody ) { - $wgOut->addHTML( - $pager->getNavigationBar() . - $loglist->beginLogEventsList() . - $logBody . - $loglist->endLogEventsList() . - $pager->getNavigationBar() - ); - } else { - $wgOut->addWikiMsg( 'logempty' ); + + private function show( FormOptions $opts, array $extraConds ) { + global $wgOut, $wgUser; + + # 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, $opts->getValue( 'type' ), $opts->getValue( 'user' ), + $opts->getValue( 'page' ), $opts->getValue( 'pattern' ), $extraConds, $opts->getValue( 'year' ), + $opts->getValue( 'month' ), $opts->getValue( 'tagfilter' ) ); + + # Set title and add header + $loglist->showHeader( $pager->getType() ); + + # Show form options + $loglist->showOptions( $pager->getType(), $pager->getUser(), $pager->getPage(), $pager->getPattern(), + $pager->getYear(), $pager->getMonth(), $pager->getFilterParams(), $opts->getValue( 'tagfilter' ) ); + + # Insert list + $logBody = $pager->getBody(); + if ( $logBody ) { + $wgOut->addHTML( + $pager->getNavigationBar() . + $loglist->beginLogEventsList() . + $logBody . + $loglist->endLogEventsList() . + $pager->getNavigationBar() + ); + } else { + $wgOut->addWikiMsg( 'logempty' ); + } } } diff --git a/includes/specials/SpecialLonelypages.php b/includes/specials/SpecialLonelypages.php index 90da25fd..0788037f 100644 --- a/includes/specials/SpecialLonelypages.php +++ b/includes/specials/SpecialLonelypages.php @@ -1,5 +1,22 @@ <?php /** + * Implements Special:Lonelypaages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ @@ -7,6 +24,7 @@ /** * A special page looking for articles with no article linking to them, * thus being lonely. + * * @ingroup SpecialPage */ class LonelyPagesPage extends PageQueryPage { diff --git a/includes/specials/SpecialLongpages.php b/includes/specials/SpecialLongpages.php index be16a029..cd0f3090 100644 --- a/includes/specials/SpecialLongpages.php +++ b/includes/specials/SpecialLongpages.php @@ -1,5 +1,22 @@ <?php /** + * Implements Special:Longpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ diff --git a/includes/specials/SpecialMIMEsearch.php b/includes/specials/SpecialMIMEsearch.php index dafe003e..79683a35 100644 --- a/includes/specials/SpecialMIMEsearch.php +++ b/includes/specials/SpecialMIMEsearch.php @@ -1,13 +1,25 @@ <?php /** - * A special page to search for files by MIME type as defined in the - * img_major_mime and img_minor_mime fields in the image table + * Implements Special:MIMESearch + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage - * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ /** @@ -18,7 +30,7 @@ class MIMEsearchPage extends QueryPage { var $major, $minor; - function MIMEsearchPage( $major, $minor ) { + function __construct( $major, $minor ) { $this->major = $major; $this->minor = $minor; } @@ -95,7 +107,7 @@ function wfSpecialMIMEsearch( $par = null ) { $wgOut->addHTML( Xml::openElement( 'form', array( 'id' => 'specialmimesearch', 'method' => 'get', 'action' => SpecialPage::getTitleFor( 'MIMEsearch' )->getLocalUrl() ) ) . Xml::openElement( 'fieldset' ) . - Xml::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) . + Html::hidden( 'title', SpecialPage::getTitleFor( 'MIMEsearch' )->getPrefixedText() ) . Xml::element( 'legend', null, wfMsg( 'mimesearch' ) ) . Xml::inputLabel( wfMsg( 'mimetype' ), 'mime', 'mime', 20, $mime ) . ' ' . Xml::submitButton( wfMsg( 'ilsubmit' ) ) . diff --git a/includes/specials/SpecialMergeHistory.php b/includes/specials/SpecialMergeHistory.php index 1b4ef30c..43b4ef6a 100644 --- a/includes/specials/SpecialMergeHistory.php +++ b/includes/specials/SpecialMergeHistory.php @@ -1,34 +1,42 @@ <?php /** - * Special page allowing users with the appropriate permissions to - * merge article histories, with some restrictions + * Implements Special:MergeHistory + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage */ /** - * Constructor - */ -function wfSpecialMergehistory( $par ) { - global $wgRequest; - - $form = new MergehistoryForm( $wgRequest, $par ); - $form->execute(); -} - -/** - * The HTML form for Special:MergeHistory, which allows users with the appropriate - * permissions to view and restore deleted content. + * Special page allowing users with the appropriate permissions to + * merge article histories, with some restrictions + * * @ingroup SpecialPage */ -class MergehistoryForm { +class SpecialMergeHistory extends SpecialPage { var $mAction, $mTarget, $mDest, $mTimestamp, $mTargetID, $mDestID, $mComment; var $mTargetObj, $mDestObj; - function MergehistoryForm( $request, $par = "" ) { - global $wgUser; + public function __construct() { + parent::__construct( 'MergeHistory', 'mergehistory' ); + } + private function loadRequestParams( $request ) { + global $wgUser; $this->mAction = $request->getVal( 'action' ); $this->mTarget = $request->getVal( 'target' ); $this->mDest = $request->getVal( 'dest' ); @@ -51,7 +59,6 @@ class MergehistoryForm { $this->mTargetObj = null; $this->mDestObj = null; } - $this->preCacheMessages(); } @@ -62,14 +69,27 @@ class MergehistoryForm { function preCacheMessages() { // Precache various messages if( !isset( $this->message ) ) { - $this->message['last'] = wfMsgExt( 'last', array( 'escape') ); + $this->message['last'] = wfMsgExt( 'last', array( 'escape' ) ); } } - function execute() { - global $wgOut; + function execute( $par ) { + global $wgOut, $wgRequest, $wgUser; + + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + + if( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); + return; + } + + $this->loadRequestParams( $wgRequest ); - $wgOut->setPagetitle( wfMsgHtml( "mergehistory" ) ); + $this->setHeaders(); + $this->outputHeader(); if( $this->mTargetID && $this->mDestID && $this->mAction=="submit" && $this->mMerge ) { return $this->merge(); @@ -122,10 +142,9 @@ class MergehistoryForm { '<fieldset>' . Xml::element( 'legend', array(), wfMsg( 'mergehistory-box' ) ) . - Xml::hidden( 'title', - SpecialPage::getTitleFor( 'Mergehistory' )->getPrefixedDbKey() ) . - Xml::hidden( 'submitted', '1' ) . - Xml::hidden( 'mergepoint', $this->mTimestamp ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) . + Html::hidden( 'submitted', '1' ) . + Html::hidden( 'mergepoint', $this->mTimestamp ) . Xml::openElement( 'table' ) . "<tr> <td>".Xml::label( wfMsg( 'mergehistory-from' ), 'target' )."</td> @@ -142,7 +161,7 @@ class MergehistoryForm { } private function showHistory() { - global $wgLang, $wgUser, $wgOut; + global $wgUser, $wgOut; $this->sk = $wgUser->getSkin(); @@ -154,7 +173,7 @@ class MergehistoryForm { $revisions = new MergeHistoryPager( $this, array(), $this->mTargetObj, $this->mDestObj ); $haveRevisions = $revisions && $revisions->getNumRows() > 0; - $titleObj = SpecialPage::getTitleFor( "Mergehistory" ); + $titleObj = $this->getTitle(); $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) ); # Start the form here $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'merge' ) ); @@ -177,7 +196,7 @@ class MergehistoryForm { "</td> </tr> <tr> - <td> </td> + <td> </td> <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'mergehistory-submit' ), array( 'name' => 'merge', 'id' => 'mw-merge-submit' ) ) . "</td> @@ -206,11 +225,11 @@ class MergehistoryForm { # When we submit, go by page ID to avoid some nasty but unlikely collisions. # Such would happen if a page was renamed after the form loaded, but before submit - $misc = Xml::hidden( 'targetID', $this->mTargetObj->getArticleID() ); - $misc .= Xml::hidden( 'destID', $this->mDestObj->getArticleID() ); - $misc .= Xml::hidden( 'target', $this->mTarget ); - $misc .= Xml::hidden( 'dest', $this->mDest ); - $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); + $misc = Html::hidden( 'targetID', $this->mTargetObj->getArticleID() ); + $misc .= Html::hidden( 'destID', $this->mDestObj->getArticleID() ); + $misc .= Html::hidden( 'target', $this->mTarget ); + $misc .= Html::hidden( 'dest', $this->mDest ); + $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() ); $misc .= Xml::closeElement( 'form' ); $wgOut->addHTML( $misc ); @@ -419,7 +438,7 @@ class MergeHistoryPager extends ReverseChronologicalPager { $batch = new LinkBatch(); # Give some pointers to make (last) links $this->mForm->prevId = array(); - while( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->rev_user_text ) ); $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->rev_user_text ) ); @@ -440,7 +459,6 @@ class MergeHistoryPager extends ReverseChronologicalPager { } function formatRow( $row ) { - $block = new Block; return $this->mForm->formatRevisionRow( $row ); } diff --git a/includes/specials/SpecialMostcategories.php b/includes/specials/SpecialMostcategories.php index 1ba05626..124f0bd5 100644 --- a/includes/specials/SpecialMostcategories.php +++ b/includes/specials/SpecialMostcategories.php @@ -1,15 +1,32 @@ <?php /** + * Implements Special:Mostcategories + * + * Copyright © 2005 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage - * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ /** - * implements Special:Mostcategories + * A special page that list pages that have highest category count + * * @ingroup SpecialPage */ class MostcategoriesPage extends QueryPage { diff --git a/includes/specials/SpecialMostimages.php b/includes/specials/SpecialMostimages.php index 5cc100ba..411a281b 100644 --- a/includes/specials/SpecialMostimages.php +++ b/includes/specials/SpecialMostimages.php @@ -1,15 +1,32 @@ <?php /** + * Implements Special:Mostimages + * + * Copyright © 2005 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage - * * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ /** - * implements Special:Mostimages + * A special page page that list most used images + * * @ingroup SpecialPage */ class MostimagesPage extends ImageQueryPage { @@ -36,7 +53,7 @@ class MostimagesPage extends ImageQueryPage { function getCellHtml( $row ) { global $wgLang; - return wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), + return wfMsgExt( 'nimagelinks', array( 'parsemag', 'escape' ), $wgLang->formatNum( $row->value ) ) . '<br />'; } diff --git a/includes/specials/SpecialMostlinked.php b/includes/specials/SpecialMostlinked.php index f112ae17..c731588a 100644 --- a/includes/specials/SpecialMostlinked.php +++ b/includes/specials/SpecialMostlinked.php @@ -1,20 +1,34 @@ <?php /** + * Implements Special:Mostlinked + * + * Copyright © 2005 Ævar Arnfjörð Bjarmason, 2006 Rob Church + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> + * @author Rob Church <robchur@gmail.com> */ /** * A special page to show pages ordered by the number of pages linking to them. - * Implements Special:Mostlinked * * @ingroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @author Rob Church <robchur@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ class MostlinkedPage extends QueryPage { @@ -59,8 +73,9 @@ class MostlinkedPage extends QueryPage { function preprocessResults( $db, $res ) { if( $db->numRows( $res ) > 0 ) { $linkBatch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) + foreach ( $res as $row ) { $linkBatch->add( $row->namespace, $row->title ); + } $db->dataSeek( $res, 0 ); $linkBatch->execute(); } diff --git a/includes/specials/SpecialMostlinkedcategories.php b/includes/specials/SpecialMostlinkedcategories.php index 20a35c97..e1fc1d95 100644 --- a/includes/specials/SpecialMostlinkedcategories.php +++ b/includes/specials/SpecialMostlinkedcategories.php @@ -1,17 +1,33 @@ <?php /** + * Implements Special:Mostlinkedcategories + * + * Copyright © 2005, Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> */ /** * A querypage to show categories ordered in descending order by the pages in them * * @ingroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ class MostlinkedCategoriesPage extends QueryPage { @@ -42,8 +58,9 @@ class MostlinkedCategoriesPage extends QueryPage { */ function preprocessResults( $db, $res ) { $batch = new LinkBatch; - while ( $row = $db->fetchObject( $res ) ) + foreach ( $res as $row ) { $batch->add( $row->namespace, $row->title ); + } $batch->execute(); // Back to start for display diff --git a/includes/specials/SpecialMostlinkedtemplates.php b/includes/specials/SpecialMostlinkedtemplates.php index 71a6b539..822d6bc9 100644 --- a/includes/specials/SpecialMostlinkedtemplates.php +++ b/includes/specials/SpecialMostlinkedtemplates.php @@ -1,7 +1,25 @@ <?php /** + * Implements Special:Mostlinkedtemplates + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Rob Church <robchur@gmail.com> */ /** @@ -9,7 +27,6 @@ * transclusion links, i.e. "most used" templates * * @ingroup SpecialPage - * @author Rob Church <robchur@gmail.com> */ class SpecialMostlinkedtemplates extends QueryPage { @@ -75,7 +92,7 @@ class SpecialMostlinkedtemplates extends QueryPage { */ public function preprocessResults( $db, $res ) { $batch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) { + foreach ( $res as $row ) { $batch->add( $row->namespace, $row->title ); } $batch->execute(); @@ -110,8 +127,8 @@ class SpecialMostlinkedtemplates extends QueryPage { private function makeWlhLink( $title, $skin, $result ) { global $wgLang; $wlh = SpecialPage::getTitleFor( 'Whatlinkshere' ); - $label = wfMsgExt( 'nlinks', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->value ) ); + $label = wfMsgExt( 'ntransclusions', array( 'parsemag', 'escape' ), + $wgLang->formatNum( $result->value ) ); return $skin->link( $wlh, $label, array(), array( 'target' => $title->getPrefixedText() ) ); } } diff --git a/includes/specials/SpecialMostrevisions.php b/includes/specials/SpecialMostrevisions.php index 414e8d97..f9bafabc 100644 --- a/includes/specials/SpecialMostrevisions.php +++ b/includes/specials/SpecialMostrevisions.php @@ -1,15 +1,32 @@ <?php /** - * A special page to show pages in the + * Implements Special:Mostrevisions * - * @ingroup SpecialPage + * Copyright © 2005 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ /** + * A special page to show pages with highest revision count + * * @ingroup SpecialPage */ class MostrevisionsPage extends QueryPage { diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index 02197b19..2f156c65 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -1,93 +1,104 @@ <?php /** + * Implements Special:Movepage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * Constructor + * A special page that allows users to change page titles + * + * @ingroup SpecialPage */ -function wfSpecialMovepage( $par = null ) { - global $wgUser, $wgOut, $wgRequest, $action; +class MovePageForm extends UnlistedSpecialPage { + var $oldTitle, $newTitle; # Objects + var $reason; # Text input + var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks - # Check for database lock - if ( wfReadOnly() ) { - $wgOut->readOnlyPage(); - return; + private $watch = false; + + public function __construct() { + parent::__construct( 'Movepage' ); } - $target = isset( $par ) ? $par : $wgRequest->getVal( 'target' ); + public function execute( $par ) { + global $wgUser, $wgOut, $wgRequest; - // Yes, the use of getVal() and getText() is wanted, see bug 20365 - $oldTitleText = $wgRequest->getVal( 'wpOldTitle', $target ); - $newTitleText = $wgRequest->getText( 'wpNewTitle' ); + # Check for database lock + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } - $oldTitle = Title::newFromText( $oldTitleText ); - $newTitle = Title::newFromText( $newTitleText ); + $this->setHeaders(); + $this->outputHeader(); - if( is_null( $oldTitle ) ) { - $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); - return; - } - if( !$oldTitle->exists() ) { - $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' ); - return; - } + $target = !is_null( $par ) ? $par : $wgRequest->getVal( 'target' ); - # Check rights - $permErrors = $oldTitle->getUserPermissionsErrors( 'move', $wgUser ); - if( !empty( $permErrors ) ) { - $wgOut->showPermissionsErrorPage( $permErrors ); - return; - } + // Yes, the use of getVal() and getText() is wanted, see bug 20365 + $oldTitleText = $wgRequest->getVal( 'wpOldTitle', $target ); + $newTitleText = $wgRequest->getText( 'wpNewTitle' ); - $form = new MovePageForm( $oldTitle, $newTitle ); + $this->oldTitle = Title::newFromText( $oldTitleText ); + $this->newTitle = Title::newFromText( $newTitleText ); - if ( 'submit' == $action && $wgRequest->wasPosted() - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $form->doSubmit(); - } else { - $form->showForm( '' ); - } -} + if( is_null( $this->oldTitle ) ) { + $wgOut->showErrorPage( 'notargettitle', 'notargettext' ); + return; + } + if( !$this->oldTitle->exists() ) { + $wgOut->showErrorPage( 'nopagetitle', 'nopagetext' ); + return; + } -/** - * HTML form for Special:Movepage - * @ingroup SpecialPage - */ -class MovePageForm { - var $oldTitle, $newTitle; # Objects - var $reason; # Text input - var $moveTalk, $deleteAndMove, $moveSubpages, $fixRedirects, $leaveRedirect, $moveOverShared; # Checks + # Check rights + $permErrors = $this->oldTitle->getUserPermissionsErrors( 'move', $wgUser ); + if( !empty( $permErrors ) ) { + $wgOut->showPermissionsErrorPage( $permErrors ); + return; + } - private $watch = false; + $def = !$wgRequest->wasPosted(); - function __construct( $oldTitle, $newTitle ) { - global $wgRequest, $wgUser; - $target = isset($par) ? $par : $wgRequest->getVal( 'target' ); - $this->oldTitle = $oldTitle; - $this->newTitle = $newTitle; $this->reason = $wgRequest->getText( 'wpReason' ); - if ( $wgRequest->wasPosted() ) { - $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', false ); - $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', false ); - $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', false ); - } else { - $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', true ); - $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', true ); - $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', true ); - } + $this->moveTalk = $wgRequest->getBool( 'wpMovetalk', $def ); + $this->fixRedirects = $wgRequest->getBool( 'wpFixRedirects', $def ); + $this->leaveRedirect = $wgRequest->getBool( 'wpLeaveRedirect', $def ); $this->moveSubpages = $wgRequest->getBool( 'wpMovesubpages', false ); $this->deleteAndMove = $wgRequest->getBool( 'wpDeleteAndMove' ) && $wgRequest->getBool( 'wpConfirm' ); $this->moveOverShared = $wgRequest->getBool( 'wpMoveOverSharedFile', false ); $this->watch = $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn(); + + if ( 'submit' == $wgRequest->getVal( 'action' ) && $wgRequest->wasPosted() + && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $this->doSubmit(); + } else { + $this->showForm( '' ); + } } /** * Show the form - * @param mixed $err Error message. May either be a string message name or - * array message name and parameters, like the second argument to - * OutputPage::wrapWikiMsg(). + * + * @param $err Mixed: error message. May either be a string message name or + * array message name and parameters, like the second argument to + * OutputPage::wrapWikiMsg(). */ function showForm( $err ) { global $wgOut, $wgUser, $wgContLang, $wgFixDoubleRedirects; @@ -134,7 +145,8 @@ class MovePageForm { 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' ); + $wgOut->addWikiMsg( $wgFixDoubleRedirects ? 'movepagetext' : + 'movepagetext-noredirectfixer' ); $movepagebtn = wfMsg( 'movepagebtn' ); $submitVar = 'wpMove'; $confirm = false; @@ -145,14 +157,14 @@ class MovePageForm { $submitVar = 'wpMoveOverSharedFile'; $err = ''; } - + $oldTalk = $this->oldTitle->getTalkPage(); $considerTalk = ( !$this->oldTitle->isTalkPage() && $oldTalk->exists() ); $dbr = wfGetDB( DB_SLAVE ); if ( $wgFixDoubleRedirects ) { - $hasRedirects = $dbr->selectField( 'redirect', '1', - array( + $hasRedirects = $dbr->selectField( 'redirect', '1', + array( 'rd_namespace' => $this->oldTitle->getNamespace(), 'rd_title' => $this->oldTitle->getDBkey(), ) , __METHOD__ ); @@ -164,7 +176,6 @@ class MovePageForm { $wgOut->addWikiMsg( 'movepagetalktext' ); } - $titleObj = SpecialPage::getTitleFor( 'Movepage' ); $token = htmlspecialchars( $wgUser->editToken() ); if ( !empty($err) ) { @@ -174,7 +185,7 @@ class MovePageForm { $errMsg = "<p><strong class=\"error\">$hookErr</strong></p>\n"; $wgOut->addHTML( $errMsg ); } else { - $wgOut->wrapWikiMsg( '<p><strong class="error">$1</strong></p>', $err ); + $wgOut->wrapWikiMsg( "<p><strong class=\"error\">\n$1\n</strong></p>", $err ); } } @@ -195,7 +206,7 @@ class MovePageForm { } $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $titleObj->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) . + Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL( 'action=submit' ), 'id' => 'movepage' ) ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'move-page-legend' ) ) . Xml::openElement( 'table', array( 'border' => '0', 'id' => 'mw-movepage-table' ) ) . @@ -213,7 +224,7 @@ class MovePageForm { "</td> <td class='mw-input'>" . Xml::input( 'wpNewTitle', 40, $wgContLang->recodeForEdit( $newTitle->getPrefixedText() ), array( 'type' => 'text', 'id' => 'wpNewTitle' ) ) . - Xml::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) . + Html::hidden( 'wpOldTitle', $this->oldTitle->getPrefixedText() ) . "</td> </tr> <tr> @@ -243,7 +254,7 @@ class MovePageForm { <tr> <td></td> <td class='mw-input' >" . - Xml::checkLabel( wfMsg( 'move-leave-redirect' ), 'wpLeaveRedirect', + Xml::checkLabel( wfMsg( 'move-leave-redirect' ), 'wpLeaveRedirect', 'wpLeaveRedirect', $this->leaveRedirect ) . "</td> </tr>" @@ -255,7 +266,7 @@ class MovePageForm { <tr> <td></td> <td class='mw-input' >" . - Xml::checkLabel( wfMsg( 'fix-double-redirects' ), 'wpFixRedirects', + Xml::checkLabel( wfMsg( 'fix-double-redirects' ), 'wpFixRedirects', 'wpFixRedirects', $this->fixRedirects ) . "</td> </tr>" @@ -277,7 +288,7 @@ class MovePageForm { # move and we aren't moving the talk page. $this->moveSubpages && ($this->oldTitle->hasSubpages() || $this->moveTalk), array( 'id' => 'wpMovesubpages' ) - ) . ' ' . + ) . ' ' . Xml::tags( 'label', array( 'for' => 'wpMovesubpages' ), wfMsgExt( ( $this->oldTitle->hasSubpages() @@ -294,7 +305,7 @@ class MovePageForm { ); } - $watchChecked = $wgUser->isLoggedIn() && ($this->watch || $wgUser->getBoolOption( 'watchmoves' ) + $watchChecked = $wgUser->isLoggedIn() && ($this->watch || $wgUser->getBoolOption( 'watchmoves' ) || $this->oldTitle->userIsWatching()); # Don't allow watching if user is not logged in if( $wgUser->isLoggedIn() ) { @@ -307,16 +318,16 @@ class MovePageForm { </tr>"); } - $wgOut->addHTML( " + $wgOut->addHTML( " {$confirm} <tr> - <td> </td> + <td> </td> <td class='mw-submit'>" . Xml::submitButton( $movepagebtn, array( 'name' => $submitVar ) ) . "</td> </tr>" . Xml::closeElement( 'table' ) . - Xml::hidden( 'wpEditToken', $token ) . + Html::hidden( 'wpEditToken', $token ) . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ) . "\n" @@ -328,7 +339,7 @@ class MovePageForm { } function doSubmit() { - global $wgOut, $wgUser, $wgRequest, $wgMaximumMovedPages, $wgLang; + global $wgOut, $wgUser, $wgMaximumMovedPages, $wgLang; global $wgFixDoubleRedirects; if ( $wgUser->pingLimiter( 'move' ) ) { @@ -346,7 +357,7 @@ class MovePageForm { # Disallow deletions of big articles $bigHistory = $article->isBigDeletion(); if( $bigHistory && !$nt->userCan( 'bigdelete' ) ) { - global $wgLang, $wgDeleteRevisionsLimit; + global $wgDeleteRevisionsLimit; $this->showForm( array('delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) ); return; } @@ -368,16 +379,16 @@ class MovePageForm { } # Show a warning if the target file exists on a shared repo - if ( $nt->getNamespace() == NS_FILE + if ( $nt->getNamespace() == NS_FILE && !( $this->moveOverShared && $wgUser->isAllowed( 'reupload-shared' ) ) - && !RepoGroup::singleton()->getLocalRepo()->findFile( $nt ) + && !RepoGroup::singleton()->getLocalRepo()->findFile( $nt ) && wfFindFile( $nt ) ) { $this->showForm( array('file-exists-sharedrepo') ); return; - + } - + if ( $wgUser->isAllowed( 'suppressredirect' ) ) { $createRedirect = $this->leaveRedirect; } else { @@ -433,7 +444,7 @@ class MovePageForm { # would mean that you couldn't move them back in one operation, which # is bad. FIXME: A specific error message should be given in this # case. - + // FIXME: Use Title::moveSubpages() here $dbr = wfGetDB( DB_MASTER ); if( $this->moveSubpages && ( @@ -547,8 +558,8 @@ class MovePageForm { $wgUser->removeWatch( $ot ); $wgUser->removeWatch( $nt ); } - - # Re-clear the file redirect cache, which may have been polluted by + + # 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 ) { diff --git a/includes/specials/SpecialNewimages.php b/includes/specials/SpecialNewimages.php index a39b56ee..cecd7dfd 100644 --- a/includes/specials/SpecialNewimages.php +++ b/includes/specials/SpecialNewimages.php @@ -1,10 +1,29 @@ <?php /** + * Implements Special:Newimages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage - * FIXME: this code is crap, should use Pager and Database::select(). */ +/** + * @todo FIXME: this code is crap, should use Pager and Database::select(). + */ function wfSpecialNewimages( $par, $specialPage ) { global $wgUser, $wgOut, $wgLang, $wgRequest, $wgMiserMode; @@ -49,16 +68,14 @@ function wfSpecialNewimages( $par, $specialPage ) { } else { $ts = false; } - $dbr->freeResult( $res ); - $sql = ''; # If we were clever, we'd use this to cache. $latestTimestamp = wfTimestamp( TS_MW, $ts ); # Hardcode this for now. $limit = 48; - - if ( $parval = intval( $par ) ) { + $parval = intval( $par ); + if ( $parval ) { if ( $parval <= $limit && $parval > 0 ) { $limit = $parval; } @@ -75,14 +92,16 @@ function wfSpecialNewimages( $par, $specialPage ) { } $invertSort = false; - if( $until = $wgRequest->getVal( 'until' ) ) { + $until = $wgRequest->getVal( 'until' ); + if( $until ) { $where[] = "img_timestamp < '" . $dbr->timestamp( $until ) . "'"; } - if( $from = $wgRequest->getVal( 'from' ) ) { + $from = $wgRequest->getVal( 'from' ); + if( $from ) { $where[] = "img_timestamp >= '" . $dbr->timestamp( $from ) . "'"; $invertSort = true; } - $sql='SELECT img_size, img_name, img_user, img_user_text,'. + $sql = 'SELECT img_size, img_name, img_user, img_user_text,'. "img_description,img_timestamp FROM $image"; if( $hidebotsql ) { @@ -100,14 +119,13 @@ function wfSpecialNewimages( $par, $specialPage ) { * We have to flip things around to get the last N after a certain date */ $images = array(); - while ( $s = $dbr->fetchObject( $res ) ) { + foreach ( $res as $s ) { if( $invertSort ) { array_unshift( $images, $s ); } else { array_push( $images, $s ); } } - $dbr->freeResult( $res ); $gallery = new ImageGallery(); $firstTimestamp = null; @@ -214,9 +232,9 @@ function wfSpecialNewimages( $par, $specialPage ) { } $nextLink = wfMsgExt( 'pager-older-n', $opts, $wgLang->formatNum( $limit ) ); - if( $shownImages > $limit && $lastTimestamp ) { + if( $invertSort || ( $shownImages > $limit && $lastTimestamp ) ) { $query = array_merge( - array( 'until' => $lastTimestamp ), + array( 'until' => ( $lastTimestamp ? $lastTimestamp : "" ) ), $botpar, $searchpar ); diff --git a/includes/specials/SpecialNewpages.php b/includes/specials/SpecialNewpages.php index 903ddab0..3235436a 100644 --- a/includes/specials/SpecialNewpages.php +++ b/includes/specials/SpecialNewpages.php @@ -1,10 +1,32 @@ <?php +/** + * Implements Special:Newpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** - * implements Special:Newpages + * A special page that list newly created pages + * * @ingroup SpecialPage */ -class SpecialNewpages extends SpecialPage { +class SpecialNewpages extends IncludableSpecialPage { // Stored objects protected $opts, $skin; @@ -14,7 +36,6 @@ class SpecialNewpages extends SpecialPage { public function __construct() { parent::__construct( 'Newpages' ); - $this->includable( true ); } protected function setup( $par ) { @@ -85,11 +106,11 @@ class SpecialNewpages extends SpecialPage { /** * Show a form for filtering namespace and username * - * @param string $par - * @return string + * @param $par String + * @return String */ public function execute( $par ) { - global $wgLang, $wgOut; + global $wgOut; $this->setHeaders(); $this->outputHeader(); @@ -136,6 +157,7 @@ class SpecialNewpages extends SpecialPage { ); // Disable some if needed + # FIXME: throws E_NOTICEs if not set; and doesn't obey hooks etc if ( $wgGroupPermissions['*']['createpage'] !== true ) unset($filters['hideliu']); @@ -174,7 +196,7 @@ class SpecialNewpages extends SpecialPage { // Store query values in hidden fields so that form submission doesn't lose them $hidden = array(); foreach ( $this->opts->getUnconsumedValues() as $key => $value ) { - $hidden[] = Xml::hidden( $key, $value ); + $hidden[] = Html::hidden( $key, $value ); } $hidden = implode( "\n", $hidden ); @@ -183,7 +205,7 @@ class SpecialNewpages extends SpecialPage { list( $tagFilterLabel, $tagFilterSelector ) = $tagFilter; $form = Xml::openElement( 'form', array( 'action' => $wgScript ) ) . - Xml::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . Xml::fieldset( wfMsg( 'newpages' ) ) . Xml::openElement( 'table', array( 'id' => 'mw-newpages-table' ) ) . "<tr> @@ -239,9 +261,8 @@ class SpecialNewpages extends SpecialPage { /** * Format a row, providing the timestamp, links to the page/history, size, user links, and a comment * - * @param $skin Skin to use * @param $result Result row - * @return string + * @return String */ public function formatRow( $result ) { global $wgLang, $wgContLang; @@ -251,7 +272,9 @@ class SpecialNewpages extends SpecialPage { $dm = $wgContLang->getDirMark(); $title = Title::makeTitleSafe( $result->rc_namespace, $result->rc_title ); - $time = htmlspecialchars( $wgLang->timeAndDate( $result->rc_timestamp, true ) ); + $time = Html::element( 'span', array( 'class' => 'mw-newpages-time' ), + $wgLang->timeAndDate( $result->rc_timestamp, true ) + ); $query = array( 'redirect' => 'no' ); @@ -261,38 +284,53 @@ class SpecialNewpages extends SpecialPage { $plink = $this->skin->linkKnown( $title, null, - array(), - $query + array( 'class' => 'mw-newpages-pagename' ), + $query, + array( 'known' ) // Set explicitly to avoid the default of 'known','noclasses'. This breaks the colouration for stubs ); - $hist = $this->skin->linkKnown( + $histLink = $this->skin->linkKnown( $title, wfMsgHtml( 'hist' ), array(), array( 'action' => 'history' ) ); - $length = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), - $wgLang->formatNum( $result->length ) ); + $hist = Html::rawElement( 'span', array( 'class' => 'mw-newpages-history' ), wfMsg( 'parentheses', $histLink ) ); + + $length = Html::rawElement( 'span', array( 'class' => 'mw-newpages-length' ), + '[' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( $result->length ) ) . + ']' + ); $ulink = $this->skin->userLink( $result->rc_user, $result->rc_user_text ) . ' ' . $this->skin->userToolLinks( $result->rc_user, $result->rc_user_text ); $comment = $this->skin->commentBlock( $result->rc_comment ); - if ( $this->patrollable( $result ) ) + if ( $this->patrollable( $result ) ) { $classes[] = 'not-patrolled'; + } - # Tags, if any. - list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' ); - $classes = array_merge( $classes, $newClasses ); + # Add a class for zero byte pages + if ( $result->length == 0 ) { + $classes[] = 'mw-newpages-zero-byte-page'; + } + + # Tags, if any. check for including due to bug 23293 + if ( !$this->including() ) { + list( $tagDisplay, $newClasses ) = ChangeTags::formatSummaryRow( $result->ts_tags, 'newpages' ); + $classes = array_merge( $classes, $newClasses ); + } else { + $tagDisplay = ''; + } $css = count($classes) ? ' class="'.implode( " ", $classes).'"' : ''; - return "<li{$css}>{$time} {$dm}{$plink} ({$hist}) {$dm}[{$length}] {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n"; + return "<li{$css}>{$time} {$dm}{$plink} {$hist} {$dm}{$length} {$dm}{$ulink} {$comment} {$tagDisplay}</li>\n"; } /** * Should a specific result row provide "patrollable" links? * * @param $result Result row - * @return bool + * @return Boolean */ protected function patrollable( $result ) { global $wgUser; @@ -301,19 +339,18 @@ class SpecialNewpages extends SpecialPage { /** * Output a subscription feed listing recent edits to this page. - * @param string $type + * + * @param $type String */ protected function feed( $type ) { - global $wgFeed, $wgFeedClasses, $wgFeedLimit; + global $wgFeed, $wgFeedClasses, $wgFeedLimit, $wgOut; if ( !$wgFeed ) { - global $wgOut; $wgOut->addWikiMsg( 'feed-unavailable' ); return; } if( !isset( $wgFeedClasses[$type] ) ) { - global $wgOut; $wgOut->addWikiMsg( 'feed-invalid' ); return; } @@ -329,7 +366,7 @@ class SpecialNewpages extends SpecialPage { $feed->outHeader(); if( $pager->getNumRows() > 0 ) { - while( $row = $pager->mResult->fetchObject() ) { + foreach ( $pager->mResult as $row ) { $feed->outItem( $this->feedItem( $row ) ); } } @@ -337,10 +374,10 @@ class SpecialNewpages extends SpecialPage { } protected function feedTitle() { - global $wgContLanguageCode, $wgSitename; + global $wgLanguageCode, $wgSitename; $page = SpecialPage::getPage( 'Newpages' ); $desc = $page->getDescription(); - return "$wgSitename - $desc [$wgContLanguageCode]"; + return "$wgSitename - $desc [$wgLanguageCode]"; } protected function feedItem( $row ) { @@ -434,6 +471,9 @@ class NewPagesPager extends ReverseChronologicalPager { if ( $this->opts->getValue( 'hideredirs' ) ) { $conds['page_is_redirect'] = 0; } + + // Allow changes to the New Pages query + wfRunHooks('SpecialNewpagesConditions', array(&$this, $this->opts, &$conds)); $info = array( 'tables' => array( 'recentchanges', 'page' ), @@ -471,7 +511,7 @@ class NewPagesPager extends ReverseChronologicalPager { function getStartBody() { # Do a batch existence check on pages $linkBatch = new LinkBatch(); - while( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { $linkBatch->add( NS_USER, $row->rc_user_text ); $linkBatch->add( NS_USER_TALK, $row->rc_user_text ); $linkBatch->add( $row->rc_namespace, $row->rc_title ); diff --git a/includes/specials/SpecialPopularpages.php b/includes/specials/SpecialPopularpages.php index 88b90bc3..375cefdf 100644 --- a/includes/specials/SpecialPopularpages.php +++ b/includes/specials/SpecialPopularpages.php @@ -1,11 +1,29 @@ <?php /** + * Implements Special:PopularPages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * implements Special:Popularpages + * A special page that list most viewed pages + * * @ingroup SpecialPage */ class PopularPagesPage extends QueryPage { diff --git a/includes/specials/SpecialPreferences.php b/includes/specials/SpecialPreferences.php index 4c8bbb09..e63aeee6 100644 --- a/includes/specials/SpecialPreferences.php +++ b/includes/specials/SpecialPreferences.php @@ -1,5 +1,31 @@ <?php - +/** + * Implements Special:Preferences + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ + +/** + * A special page that allows users to change their preferences + * + * @ingroup SpecialPage + */ class SpecialPreferences extends SpecialPage { function __construct() { parent::__construct( 'Preferences' ); @@ -25,18 +51,19 @@ class SpecialPreferences extends SpecialPage { $this->showResetForm(); return; } - - $wgOut->addScriptFile( 'prefs.js' ); + + $wgOut->addModules( 'mediawiki.legacy.prefs' ); + $wgOut->addModules( 'mediawiki.special.preferences' ); if ( $wgRequest->getCheck( 'success' ) ) { $wgOut->wrapWikiMsg( - '<div class="successbox"><strong>$1</strong></div><div id="mw-pref-clear"></div>', + "<div class=\"successbox\"><strong>\n$1\n</strong></div><div id=\"mw-pref-clear\"></div>", 'savedprefs' ); } if ( $wgRequest->getCheck( 'eauth' ) ) { - $wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1</div>", + $wgOut->wrapWikiMsg( "<div class='error' style='clear: both;'>\n$1\n</div>", 'eauthentsent', $wgUser->getName() ); } diff --git a/includes/specials/SpecialPrefixindex.php b/includes/specials/SpecialPrefixindex.php index 8b5f0c93..09e7734c 100644 --- a/includes/specials/SpecialPrefixindex.php +++ b/includes/specials/SpecialPrefixindex.php @@ -1,7 +1,29 @@ <?php +/** + * Implements Special:Prefixindex + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** - * implements Special:Prefixindex + * Implements Special:Prefixindex + * * @ingroup SpecialPage */ class SpecialPrefixindex extends SpecialAllpages { @@ -22,7 +44,7 @@ class SpecialPrefixindex extends SpecialAllpages { $this->outputHeader(); # GET values - $from = $wgRequest->getVal( 'from' ); + $from = $wgRequest->getVal( 'from', '' ); $prefix = $wgRequest->getVal( 'prefix', '' ); $namespace = $wgRequest->getInt( 'namespace' ); $namespaces = $wgContLang->getNamespaces(); @@ -32,12 +54,17 @@ class SpecialPrefixindex extends SpecialAllpages { : wfMsg( 'prefixindex' ) ); + $showme = ''; if( isset( $par ) ){ - $this->showPrefixChunk( $namespace, $par, $from ); - } elseif( isset( $prefix ) ){ - $this->showPrefixChunk( $namespace, $prefix, $from ); - } elseif( isset( $from ) ){ - $this->showPrefixChunk( $namespace, $from, $from ); + $showme = $par; + } elseif( $prefix != '' ){ + $showme = $prefix; + } elseif( $from != '' ){ + // For back-compat with Special:Allpages + $showme = $from; + } + if ($showme != '' || $namespace) { + $this->showPrefixChunk( $namespace, $showme, $from ); } else { $wgOut->addHTML( $this->namespacePrefixForm( $namespace, null ) ); } @@ -45,8 +72,8 @@ class SpecialPrefixindex extends SpecialAllpages { /** * HTML for the top form - * @param integer $namespace A namespace constant (default NS_MAIN). - * @param string $from dbKey we are starting listing at. + * @param $namespace Integer: a namespace constant (default NS_MAIN). + * @param $from String: dbKey we are starting listing at. */ function namespacePrefixForm( $namespace = NS_MAIN, $from = '' ) { global $wgScript; @@ -54,7 +81,7 @@ class SpecialPrefixindex extends SpecialAllpages { $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); - $out .= Xml::hidden( 'title', $t->getPrefixedText() ); + $out .= Html::hidden( 'title', $t->getPrefixedText() ); $out .= Xml::openElement( 'fieldset' ); $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) ); $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); @@ -83,8 +110,9 @@ class SpecialPrefixindex extends SpecialAllpages { } /** - * @param integer $namespace (Default NS_MAIN) - * @param string $from list all pages from this name (default FALSE) + * @param $namespace Integer, default NS_MAIN + * @param $prefix String + * @param $from String: list all pages from this name (default FALSE) */ function showPrefixChunk( $namespace = NS_MAIN, $prefix, $from = null ) { global $wgOut, $wgUser, $wgContLang, $wgLang; @@ -105,7 +133,7 @@ class SpecialPrefixindex extends SpecialAllpages { $namespace = NS_MAIN; } else { list( $namespace, $prefixKey, $prefix ) = $prefixList; - list( /* $fromNs */, $fromKey, $from ) = $fromList; + list( /* $fromNS */, $fromKey, ) = $fromList; ### FIXME: should complain if $fromNs != $namespace diff --git a/includes/specials/SpecialProtectedpages.php b/includes/specials/SpecialProtectedpages.php index 8229770c..c676aa00 100644 --- a/includes/specials/SpecialProtectedpages.php +++ b/includes/specials/SpecialProtectedpages.php @@ -1,24 +1,45 @@ <?php /** + * Implements Special:Protectedpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * @todo document + * A special page that lists protected pages + * * @ingroup SpecialPage */ -class ProtectedPagesForm { +class SpecialProtectedpages extends SpecialPage { protected $IdLevel = 'level'; protected $IdType = 'type'; - public function showList( $msg = '' ) { + public function __construct() { + parent::__construct( 'Protectedpages' ); + } + + public function execute( $par ) { global $wgOut, $wgRequest; - if( $msg != "" ) { - $wgOut->setSubtitle( $msg ); - } + $this->setHeaders(); + $this->outputHeader(); // Purge expired entries on one in every 10 queries if( !mt_rand( 0, 10 ) ) { @@ -77,7 +98,6 @@ class ProtectedPagesForm { $description_items[] = wfMsg( 'protect-summary-cascade' ); } - $expiry_description = ''; $stxt = ''; if( $row->pr_expiry != 'infinity' && strlen($row->pr_expiry) ) { @@ -123,14 +143,14 @@ class ProtectedPagesForm { } /** - * @param $namespace int - * @param $type string - * @param $level string - * @param $minsize int - * @param $indefOnly bool - * @param $cascadeOnly bool - * @return string Input form - * @private + * @param $namespace Integer + * @param $type String: restriction type + * @param $level String: restriction level + * @param $sizetype String: "min" or "max" + * @param $size Integer + * @param $indefOnly Boolean: only indefinie protection + * @param $cascadeOnly Boolean: only cascading protection + * @return String: input form */ protected function showOptions( $namespace, $type='edit', $level, $sizetype, $size, $indefOnly, $cascadeOnly ) { global $wgScript; @@ -138,17 +158,17 @@ class ProtectedPagesForm { return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', array(), wfMsg( 'protectedpages' ) ) . - Xml::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" . - $this->getNamespaceMenu( $namespace ) . " \n" . - $this->getTypeMenu( $type ) . " \n" . - $this->getLevelMenu( $level ) . " \n" . + Html::hidden( 'title', $title->getPrefixedDBkey() ) . "\n" . + $this->getNamespaceMenu( $namespace ) . " \n" . + $this->getTypeMenu( $type ) . " \n" . + $this->getLevelMenu( $level ) . " \n" . "<br /><span style='white-space: nowrap'>" . - $this->getExpiryCheck( $indefOnly ) . " \n" . - $this->getCascadeCheck( $cascadeOnly ) . " \n" . + $this->getExpiryCheck( $indefOnly ) . " \n" . + $this->getCascadeCheck( $cascadeOnly ) . " \n" . "</span><br /><span style='white-space: nowrap'>" . - $this->getSizeLimit( $sizetype, $size ) . " \n" . + $this->getSizeLimit( $sizetype, $size ) . " \n" . "</span>" . - " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . + " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . Xml::closeElement( 'fieldset' ) . Xml::closeElement( 'form' ); } @@ -157,12 +177,12 @@ class ProtectedPagesForm { * Prepare the namespace filter drop-down; standard namespace * selector, sans the MediaWiki namespace * - * @param mixed $namespace Pre-select namespace - * @return string + * @param $namespace Mixed: pre-select namespace + * @return String */ protected function getNamespaceMenu( $namespace = null ) { return "<span style='white-space: nowrap'>" . - Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' + Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . Xml::namespaceSelector( $namespace, '' ) . "</span>"; } @@ -190,11 +210,11 @@ class ProtectedPagesForm { return Xml::radioLabel( wfMsg('minimum-size'), 'sizetype', 'min', 'wpmin', !$max ) . - ' ' . + ' ' . Xml::radioLabel( wfMsg('maximum-size'), 'sizetype', 'max', 'wpmax', $max ) . - ' ' . + ' ' . Xml::input( 'size', 9, $size, array( 'id' => 'wpsize' ) ) . - ' ' . + ' ' . Xml::label( wfMsg('pagesize'), 'wpsize' ); } @@ -204,13 +224,11 @@ class ProtectedPagesForm { * @return string Formatted HTML */ protected function getTypeMenu( $pr_type ) { - global $wgRestrictionTypes; - $m = array(); // Temporary array $options = array(); // First pass to load the log names - foreach( $wgRestrictionTypes as $type ) { + foreach( Title::getFilteredRestrictionTypes( true ) as $type ) { $text = wfMsg("restriction-$type"); $m[$text] = $type; } @@ -222,7 +240,7 @@ class ProtectedPagesForm { } return "<span style='white-space: nowrap'>" . - Xml::label( wfMsg('restriction-type') , $this->IdType ) . ' ' . + Xml::label( wfMsg('restriction-type') , $this->IdType ) . ' ' . Xml::tags( 'select', array( 'id' => $this->IdType, 'name' => $this->IdType ), implode( "\n", $options ) ) . "</span>"; @@ -288,7 +306,7 @@ class ProtectedPagesPager extends AlphabeticPager { function getStartBody() { # Do a link batch query $lb = new LinkBatch; - while( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { $lb->add( $row->page_namespace, $row->page_title ); } $lb->execute(); @@ -334,11 +352,3 @@ class ProtectedPagesPager extends AlphabeticPager { return 'pr_id'; } } - -/** - * Constructor - */ -function wfSpecialProtectedpages() { - $ppForm = new ProtectedPagesForm(); - $ppForm->showList(); -} diff --git a/includes/specials/SpecialProtectedtitles.php b/includes/specials/SpecialProtectedtitles.php index d65b3f79..5b18d87f 100644 --- a/includes/specials/SpecialProtectedtitles.php +++ b/includes/specials/SpecialProtectedtitles.php @@ -1,24 +1,45 @@ <?php /** + * Implements Special:Protectedtitles + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * @todo document + * A special page that list protected titles from creation + * * @ingroup SpecialPage */ -class ProtectedTitlesForm { +class SpecialProtectedtitles extends SpecialPage { protected $IdLevel = 'level'; protected $IdType = 'type'; - function showList( $msg = '' ) { + public function __construct() { + parent::__construct( 'Protectedtitles' ); + } + + function execute( $par ) { global $wgOut, $wgRequest; - if ( $msg != "" ) { - $wgOut->setSubtitle( $msg ); - } + $this->setHeaders(); + $this->outputHeader(); // Purge expired entries on one in every 10 queries if ( !mt_rand( 0, 10 ) ) { @@ -33,7 +54,7 @@ class ProtectedTitlesForm { $pager = new ProtectedTitlesPager( $this, array(), $type, $level, $NS, $sizetype, $size ); - $wgOut->addHTML( $this->showOptions( $NS, $type, $level, $sizetype, $size ) ); + $wgOut->addHTML( $this->showOptions( $NS, $type, $level ) ); if ( $pager->getNumRows() ) { $s = $pager->getNavigationBar(); @@ -51,7 +72,7 @@ class ProtectedTitlesForm { * Callback function to output a restriction */ function formatRow( $row ) { - global $wgUser, $wgLang, $wgContLang; + global $wgUser, $wgLang; wfProfileIn( __METHOD__ ); @@ -69,7 +90,7 @@ class ProtectedTitlesForm { $description_items[] = $protType; - $expiry_description = ''; $stxt = ''; + $stxt = ''; if ( $row->pt_expiry != 'infinity' && strlen($row->pt_expiry) ) { $expiry = Block::decodeExpiry( $row->pt_expiry ); @@ -85,13 +106,12 @@ class ProtectedTitlesForm { } /** - * @param $namespace int + * @param $namespace Integer: * @param $type string * @param $level string - * @param $minsize int * @private */ - function showOptions( $namespace, $type='edit', $level, $sizetype, $size ) { + function showOptions( $namespace, $type='edit', $level ) { global $wgScript; $action = htmlspecialchars( $wgScript ); $title = SpecialPage::getTitleFor( 'Protectedtitles' ); @@ -99,10 +119,10 @@ class ProtectedTitlesForm { return "<form action=\"$action\" method=\"get\">\n" . '<fieldset>' . Xml::element( 'legend', array(), wfMsg( 'protectedtitles' ) ) . - Xml::hidden( 'title', $special ) . " \n" . - $this->getNamespaceMenu( $namespace ) . " \n" . - $this->getLevelMenu( $level ) . " \n" . - " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . + Html::hidden( 'title', $special ) . " \n" . + $this->getNamespaceMenu( $namespace ) . " \n" . + $this->getLevelMenu( $level ) . " \n" . + " " . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" . "</fieldset></form>"; } @@ -110,12 +130,12 @@ class ProtectedTitlesForm { * Prepare the namespace filter drop-down; standard namespace * selector, sans the MediaWiki namespace * - * @param mixed $namespace Pre-select namespace + * @param $namespace Mixed: pre-select namespace * @return string */ function getNamespaceMenu( $namespace = null ) { return Xml::label( wfMsg( 'namespace' ), 'namespace' ) - . ' ' + . ' ' . Xml::namespaceSelector( $namespace, '' ); } @@ -147,7 +167,7 @@ class ProtectedTitlesForm { } return - Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . + Xml::label( wfMsg('restriction-level') , $this->IdLevel ) . ' ' . Xml::tags( 'select', array( 'id' => $this->IdLevel, 'name' => $this->IdLevel ), implode( "\n", $options ) ); @@ -158,7 +178,7 @@ class ProtectedTitlesForm { * @todo document * @ingroup Pager */ -class ProtectedtitlesPager extends AlphabeticPager { +class ProtectedTitlesPager extends AlphabeticPager { public $mForm, $mConds; function __construct( $form, $conds = array(), $type, $level, $namespace, $sizetype='', $size=0 ) { @@ -176,7 +196,7 @@ class ProtectedtitlesPager extends AlphabeticPager { $this->mResult->seek( 0 ); $lb = new LinkBatch; - while ( $row = $this->mResult->fetchObject() ) { + foreach ( $this->mResult as $row ) { $lb->add( $row->pt_namespace, $row->pt_title ); } @@ -208,12 +228,3 @@ class ProtectedtitlesPager extends AlphabeticPager { } } -/** - * Constructor - */ -function wfSpecialProtectedtitles() { - - $ppForm = new ProtectedTitlesForm(); - - $ppForm->showList(); -} diff --git a/includes/specials/SpecialRandompage.php b/includes/specials/SpecialRandompage.php index fd3f17f2..6299f384 100644 --- a/includes/specials/SpecialRandompage.php +++ b/includes/specials/SpecialRandompage.php @@ -1,11 +1,31 @@ <?php +/** + * Implements Special:Randompage + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + * @author Rob Church <robchur@gmail.com>, Ilmari Karonen + */ /** * Special page to direct the user to a random page * * @ingroup SpecialPage - * @author Rob Church <robchur@gmail.com>, Ilmari Karonen - * @license GNU General Public Licence 2.0 or later */ class RandomPage extends SpecialPage { private $namespaces; // namespaces to select pages from @@ -33,7 +53,7 @@ class RandomPage extends SpecialPage { } public function execute( $par ) { - global $wgOut, $wgContLang; + global $wgOut, $wgContLang, $wgRequest; if ($par) { $this->setNamespace( $wgContLang->getNsIndex( $par ) ); @@ -48,7 +68,9 @@ class RandomPage extends SpecialPage { return; } - $query = $this->isRedirect() ? 'redirect=no' : ''; + $redirectParam = $this->isRedirect() ? array( 'redirect' => 'no' ) : array(); + $query = array_merge( $wgRequest->getValues(), $redirectParam ); + unset( $query['title'] ); $wgOut->redirect( $title->getFullUrl( $query ) ); } diff --git a/includes/specials/SpecialRandomredirect.php b/includes/specials/SpecialRandomredirect.php index 28cb2aae..88c81b31 100644 --- a/includes/specials/SpecialRandomredirect.php +++ b/includes/specials/SpecialRandomredirect.php @@ -1,11 +1,31 @@ <?php +/** + * Implements Special:Randomredirect + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + * @author Rob Church <robchur@gmail.com>, Ilmari Karonen + */ /** * Special page to direct the user to a random redirect page (minus the second redirect) * * @ingroup SpecialPage - * @author Rob Church <robchur@gmail.com>, Ilmari Karonen - * @license GNU General Public Licence 2.0 or later */ class SpecialRandomredirect extends RandomPage { function __construct(){ diff --git a/includes/specials/SpecialRecentchanges.php b/includes/specials/SpecialRecentchanges.php index 283eeaf4..c012beca 100644 --- a/includes/specials/SpecialRecentchanges.php +++ b/includes/specials/SpecialRecentchanges.php @@ -1,15 +1,36 @@ <?php - /** * Implements Special:Recentchanges + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file * @ingroup SpecialPage */ -class SpecialRecentChanges extends SpecialPage { + +/** + * A special page that lists last changes made to the wiki + * + * @ingroup SpecialPage + */ +class SpecialRecentChanges extends IncludableSpecialPage { var $rcOptions, $rcSubpage; - public function __construct() { - parent::__construct( 'Recentchanges' ); - $this->includable( true ); + public function __construct( $name = 'Recentchanges' ) { + parent::__construct( $name ); } /** @@ -91,7 +112,7 @@ class SpecialRecentChanges extends SpecialPage { /** * Main execution point * - * @param $subpage string + * @param $subpage String */ public function execute( $subpage ) { global $wgRequest, $wgOut; @@ -142,7 +163,7 @@ class SpecialRecentChanges extends SpecialPage { /** * Return an array with a ChangesFeed object and ChannelFeed object * - * @return array + * @return Array */ public function getFeedObject( $feedFormat ){ $changesFeed = new ChangesFeed( $feedFormat, 'rcfeed' ); @@ -186,7 +207,7 @@ class SpecialRecentChanges extends SpecialPage { * update the timestamp * * @param $feedFormat String - * @return string or false + * @return String or false */ public function checkLastModified( $feedFormat ) { global $wgUseRCPatrol, $wgOut; @@ -275,7 +296,7 @@ class SpecialRecentChanges extends SpecialPage { /** * Process the query * - * @param $conds array + * @param $conds Array * @param $opts FormOptions * @return database result or false (for Recentchangeslinked only) */ @@ -290,6 +311,7 @@ class SpecialRecentChanges extends SpecialPage { $dbr = wfGetDB( DB_SLAVE ); $limit = $opts['limit']; $namespace = $opts['namespace']; + $select = '*'; $invert = $opts['invert']; // JOIN on watchlist for users @@ -302,14 +324,17 @@ class SpecialRecentChanges extends SpecialPage { $tables[] = 'page'; $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); } - // 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'] - ); + if ( !$this->including() ) { + // Tag stuff. + // Doesn't work when transcluding. See bug 23293 + $fields = array(); + // Fields are * in this case, so let the function modify an empty array to keep it happy. + ChangeTags::modifyDisplayQuery( + $tables, $fields, $conds, $join_conds, $query_options, $opts['tagfilter'] + ); + } - if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options ) ) ) + if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) ) return false; // Don't use the new_namespace_time timestamp index if: @@ -317,8 +342,8 @@ class SpecialRecentChanges extends SpecialPage { // (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 + if( is_null( $namespace ) + || ( $invert && !is_null( $namespace ) ) || $opts['tagfilter'] != '' || !$dbr->unionSupportsOrderAndLimit() ) { @@ -329,7 +354,7 @@ class SpecialRecentChanges extends SpecialPage { // We have a new_namespace_time index! UNION over new=(0,1) and sort result set! } else { // New pages - $sqlNew = $dbr->selectSQLText( $tables, '*', + $sqlNew = $dbr->selectSQLText( $tables, $select, array( 'rc_new' => 1 ) + $conds, __METHOD__, array( 'ORDER BY' => 'rc_timestamp DESC', 'LIMIT' => $limit, @@ -354,7 +379,7 @@ class SpecialRecentChanges extends SpecialPage { /** * Send output to $wgOut, only called if not used feeds * - * @param $rows array of database rows + * @param $rows Array of database rows * @param $opts FormOptions */ public function webOutput( $rows, $opts ) { @@ -437,7 +462,8 @@ class SpecialRecentChanges extends SpecialPage { $defaults = $opts->getAllValues(); $nondefaults = $opts->getChangedValues(); - $opts->consumeValues( array( 'namespace', 'invert', 'tagfilter' ) ); + $opts->consumeValues( array( 'namespace', 'invert', 'tagfilter', + 'categories', 'categories_any' ) ); $panel = array(); $panel[] = $this->optionsPanel( $defaults, $nondefaults ); @@ -467,11 +493,11 @@ class SpecialRecentChanges extends SpecialPage { $unconsumed = $opts->getUnconsumedValues(); foreach( $unconsumed as $key => $value ) { - $out .= Xml::hidden( $key, $value ); + $out .= Html::hidden( $key, $value ); } $t = $this->getTitle(); - $out .= Xml::hidden( 'title', $t->getPrefixedText() ); + $out .= Html::hidden( 'title', $t->getPrefixedText() ); $form = Xml::tags( 'form', array( 'action' => $wgScript ), $out ); $panel[] = $form; $panelString = implode( "\n", $panel ); @@ -480,8 +506,6 @@ class SpecialRecentChanges extends SpecialPage { Xml::fieldset( wfMsg( 'recentchanges-legend' ), $panelString, array( 'class' => 'rcoptions' ) ) ); - $wgOut->addHTML( ChangesList::flagLegend() ); - $this->setBottomText( $wgOut, $opts ); } @@ -489,7 +513,7 @@ class SpecialRecentChanges extends SpecialPage { * Get options to be displayed in a form * * @param $opts FormOptions - * @return array + * @return Array */ function getExtraOptions( $opts ){ $extraOpts = array(); @@ -531,7 +555,7 @@ class SpecialRecentChanges extends SpecialPage { * Creates the choose namespace selection * * @param $opts FormOptions - * @return string + * @return String */ protected function namespaceFilterForm( FormOptions $opts ) { $nsSelect = Xml::namespaceSelector( $opts['namespace'], '' ); @@ -544,7 +568,7 @@ class SpecialRecentChanges extends SpecialPage { * Create a input to filter changes by categories * * @param $opts FormOptions - * @return array + * @return Array */ protected function categoryFilterForm( FormOptions $opts ) { list( $label, $input ) = Xml::inputLabelSep( wfMsg('rc_categories'), @@ -559,13 +583,13 @@ class SpecialRecentChanges extends SpecialPage { /** * Filter $rows by categories set in $opts * - * @param $rows array of database rows + * @param $rows Array of database rows * @param $opts FormOptions */ function filterByCategories( &$rows, FormOptions $opts ) { - $categories = array_map( 'trim', explode( "|" , $opts['categories'] ) ); + $categories = array_map( 'trim', explode( '|' , $opts['categories'] ) ); - if( empty($categories) ) { + if( !count( $categories ) ) { return; } @@ -573,32 +597,34 @@ class SpecialRecentChanges extends SpecialPage { $cats = array(); foreach( $categories as $cat ) { $cat = trim( $cat ); - if( $cat == "" ) continue; + if( $cat == '' ) continue; $cats[] = $cat; } # Filter articles $articles = array(); $a2r = array(); + $rowsarr = array(); foreach( $rows AS $k => $r ) { $nt = Title::makeTitle( $r->rc_namespace, $r->rc_title ); $id = $nt->getArticleID(); if( $id == 0 ) continue; # Page might have been deleted... - if( !in_array($id, $articles) ) { + if( !in_array( $id, $articles ) ) { $articles[] = $id; } - if( !isset($a2r[$id]) ) { + if( !isset( $a2r[$id] ) ) { $a2r[$id] = array(); } $a2r[$id][] = $k; + $rowsarr[$k] = $r; } # Shortcut? - if( !count($articles) || !count($cats) ) + if( !count( $articles ) || !count( $cats ) ) return ; # Look up - $c = new Categoryfinder ; + $c = new Categoryfinder; $c->seed( $articles, $cats, $opts['categories_any'] ? "OR" : "AND" ) ; $match = $c->run(); @@ -607,7 +633,7 @@ class SpecialRecentChanges extends SpecialPage { foreach( $match AS $id ) { foreach( $a2r[$id] AS $rev ) { $k = $rev; - $newrows[$k] = $rows[$k]; + $newrows[$k] = $rowsarr[$k]; } } $rows = $newrows; @@ -615,9 +641,11 @@ class SpecialRecentChanges extends SpecialPage { /** * Makes change an option link which carries all the other options - * @param $title see Title - * @param $override - * @param $options + * + * @param $title Title + * @param $override Array: options to override + * @param $options Array: current options + * @param $active Boolean: whether to show the link in bold */ function makeOptionsLink( $title, $override, $options, $active = false ) { global $wgUser; @@ -633,8 +661,9 @@ class SpecialRecentChanges extends SpecialPage { /** * Creates the options panel. - * @param $defaults array - * @param $nondefaults array + * + * @param $defaults Array + * @param $nondefaults Array */ function optionsPanel( $defaults, $nondefaults ) { global $wgLang, $wgUser, $wgRCLinkLimits, $wgRCLinkDays; diff --git a/includes/specials/SpecialRecentchangeslinked.php b/includes/specials/SpecialRecentchangeslinked.php index 3b549843..db0f554d 100644 --- a/includes/specials/SpecialRecentchangeslinked.php +++ b/includes/specials/SpecialRecentchangeslinked.php @@ -1,15 +1,36 @@ <?php +/** + * Implements Special:Recentchangeslinked + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** * This is to display changes made to all articles linked in an article. + * * @ingroup SpecialPage */ -class SpecialRecentchangeslinked extends SpecialRecentchanges { +class SpecialRecentchangeslinked extends SpecialRecentChanges { var $rclTargetTitle; function __construct(){ - SpecialPage::SpecialPage( 'Recentchangeslinked' ); - $this->includable( true ); + parent::__construct( 'Recentchangeslinked' ); } public function getDefaultOptions() { @@ -52,7 +73,7 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { } $title = Title::newFromURL( $target ); if( !$title || $title->getInterwiki() != '' ){ - $wgOut->wrapWikiMsg( "<div class=\"errorbox\">\n$1</div><br style=\"clear: both\" />", 'allpagesbadtitle' ); + $wgOut->wrapWikiMsg( "<div class=\"errorbox\">\n$1\n</div><br style=\"clear: both\" />", 'allpagesbadtitle' ); return false; } @@ -78,7 +99,8 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $query_options = array(); // left join with watchlist table to highlight watched rows - if( $uid = $wgUser->getId() ) { + $uid = $wgUser->getId(); + if( $uid ) { $tables[] = 'watchlist'; $select[] = 'wl_user'; $join_conds['watchlist'] = array( 'LEFT JOIN', "wl_user={$uid} AND wl_title=rc_title AND wl_namespace=rc_namespace" ); @@ -88,12 +110,13 @@ class SpecialRecentchangeslinked extends SpecialRecentchanges { $join_conds['page'] = array('LEFT JOIN', 'rc_cur_id=page_id'); $select[] = 'page_latest'; } + if ( !$this->including() ) { // bug 23293 + ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds, + $query_options, $opts['tagfilter'] ); + } - ChangeTags::modifyDisplayQuery( $tables, $select, $conds, $join_conds, - $query_options, $opts['tagfilter'] ); - - // XXX: parent class does this, should we too? - // wfRunHooks('SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts ) ); + if ( !wfRunHooks( 'SpecialRecentChangesQuery', array( &$conds, &$tables, &$join_conds, $opts, &$query_options, &$select ) ) ) + return false; if( $ns == NS_CATEGORY && !$showlinkedto ) { // special handling for categories diff --git a/includes/specials/SpecialRemoveRestrictions.php b/includes/specials/SpecialRemoveRestrictions.php deleted file mode 100644 index a3428a5a..00000000 --- a/includes/specials/SpecialRemoveRestrictions.php +++ /dev/null @@ -1,60 +0,0 @@ -<?php - -function wfSpecialRemoveRestrictions() { - global $wgOut, $wgRequest, $wgUser, $wgLang; - $sk = $wgUser->getSkin(); - $title = SpecialPage::getTitleFor( 'RemoveRestrictions' ); - $id = $wgRequest->getVal( 'id' ); - if( !is_numeric( $id ) ) { - $wgOut->addWikiMsg( 'removerestrictions-noid' ); - return; - } - - UserRestriction::purgeExpired(); - $r = UserRestriction::newFromId( $id, true ); - if( !$r ) { - $wgOut->addWikiMsg( 'removerestrictions-wrongid' ); - return; - } - - $form = array(); - $form['removerestrictions-user'] = $sk->userLink( $r->getSubjectId(), $r->getSubjectText() ) . - $sk->userToolLinks( $r->getSubjectId(), $r->getSubjectText() ); - $form['removerestrictions-type'] = UserRestriction::formatType( $r->getType() ); - if( $r->isPage() ) - $form['removerestrictions-page'] = $sk->link( $r->getPage() ); - if( $r->isNamespace() ) - $form['removerestrictions-namespace'] = $wgLang->getDisplayNsText( $r->getNamespace() ); - $form['removerestrictions-reason'] = Xml::input( 'reason' ); - - $result = null; - if( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'edittoken' ) ) ) - $result = wfSpecialRemoveRestrictionsProcess( $r ); - - $wgOut->addWikiMsg( 'removerestrictions-intro' ); - $wgOut->addHTML( Xml::fieldset( wfMsgHtml( 'removerestrictions-legend' ) ) ); - if( $result ) - $wgOut->addHTML( '<strong class="success">' . wfMsgExt( 'removerestrictions-success', - 'parseinline', $r->getSubjectText() ) . '</strong>' ); - $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', $title->getPrefixedDbKey() ) ); - $wgOut->addHTML( Xml::hidden( 'edittoken', $wgUser->editToken() ) ); - $wgOut->addHTML( "</form></fieldset>" ); -} - -function wfSpecialRemoveRestrictionsProcess( $r ) { - global $wgRequest; - $reason = $wgRequest->getVal( 'reason' ); - $result = $r->delete(); - $log = new LogPage( 'restrict' ); - $params = array( $r->getType() ); - if( $r->isPage() ) - $params[] = $r->getPage()->getPrefixedDbKey(); - if( $r->isNamespace() ) - $params[] = $r->getNamespace(); - $log->addEntry( 'remove', Title::makeTitle( NS_USER, $r->getSubjectText() ), $reason, $params ); - return $result; -} diff --git a/includes/specials/SpecialResetpass.php b/includes/specials/SpecialResetpass.php index 967d2119..0af6fbf0 100644 --- a/includes/specials/SpecialResetpass.php +++ b/includes/specials/SpecialResetpass.php @@ -1,11 +1,29 @@ <?php /** + * Implements Special:Resetpass + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** * Let users recover their password. + * * @ingroup SpecialPage */ class SpecialResetpass extends SpecialPage { @@ -19,6 +37,11 @@ class SpecialResetpass extends SpecialPage { function execute( $par ) { global $wgUser, $wgAuth, $wgOut, $wgRequest; + if ( wfReadOnly() ) { + $wgOut->readOnlyPage(); + return; + } + $this->mUserName = $wgRequest->getVal( 'wpName' ); $this->mOldpass = $wgRequest->getVal( 'wpPassword' ); $this->mNewpass = $wgRequest->getVal( 'wpNewPassword' ); @@ -26,6 +49,7 @@ class SpecialResetpass extends SpecialPage { $this->setHeaders(); $this->outputHeader(); + $wgOut->disallowUserJs(); if( !$wgAuth->allowPasswordChange() ) { $this->error( wfMsg( 'resetpass_forbidden' ) ); @@ -84,18 +108,18 @@ class SpecialResetpass extends SpecialPage { function showForm() { global $wgOut, $wgUser, $wgRequest; - $wgOut->disallowUserJs(); - - $self = SpecialPage::getTitleFor( 'Resetpass' ); + $self = $this->getTitle(); if ( !$this->mUserName ) { $this->mUserName = $wgUser->getName(); } $rememberMe = ''; if ( !$wgUser->isLoggedIn() ) { + global $wgCookieExpiration, $wgLang; $rememberMe = '<tr>' . '<td></td>' . '<td class="mw-input">' . - Xml::checkLabel( wfMsg( 'remembermypassword' ), + Xml::checkLabel( + wfMsgExt( 'remembermypassword', 'parsemag', $wgLang->formatNum( ceil( $wgCookieExpiration / ( 3600 * 24 ) ) ) ), 'wpRemember', 'wpRemember', $wgRequest->getCheck( 'wpRemember' ) ) . '</td>' . @@ -113,9 +137,9 @@ class SpecialResetpass extends SpecialPage { 'method' => 'post', 'action' => $self->getLocalUrl(), 'id' => 'mw-resetpass-form' ) ) . "\n" . - Xml::hidden( 'token', $wgUser->editToken() ) . "\n" . - Xml::hidden( 'wpName', $this->mUserName ) . "\n" . - Xml::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . "\n" . + Html::hidden( 'token', $wgUser->editToken() ) . "\n" . + Html::hidden( 'wpName', $this->mUserName ) . "\n" . + Html::hidden( 'returnto', $wgRequest->getVal( 'returnto' ) ) . "\n" . wfMsgExt( 'resetpass_text', array( 'parse' ) ) . "\n" . Xml::openElement( 'table', array( 'id' => 'mw-resetpass-table' ) ) . "\n" . $this->pretty( array( @@ -196,7 +220,6 @@ class SpecialResetpass extends SpecialPage { } catch( PasswordError $e ) { wfRunHooks( 'PrefsPasswordAudit', array( $user, $newpass, 'error' ) ); throw new PasswordError( $e->getMessage() ); - return; } $user->setCookies(); diff --git a/includes/specials/SpecialRevisiondelete.php b/includes/specials/SpecialRevisiondelete.php index b2db869c..f77fc347 100644 --- a/includes/specials/SpecialRevisiondelete.php +++ b/includes/specials/SpecialRevisiondelete.php @@ -1,12 +1,32 @@ <?php /** - * Special page allowing users with the appropriate permissions to view - * and hide revisions. Log items can also be hidden. + * Implements Special:Revisiondelete + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage */ +/** + * Special page allowing users with the appropriate permissions to view + * and hide revisions. Log items can also be hidden. + * + * @ingroup SpecialPage + */ class SpecialRevisionDelete extends UnlistedSpecialPage { /** Skin object */ var $skin; @@ -333,8 +353,6 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { $wgOut->addHTML( "<ul>" ); - $where = $revObjs = array(); - $numRevisions = 0; // Live revisions... $list = $this->getList(); @@ -396,10 +414,10 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { '</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 ) ) . + Html::hidden( 'wpEditToken', $wgUser->editToken() ) . + Html::hidden( 'target', $this->targetObj->getPrefixedText() ) . + Html::hidden( 'type', $this->typeName ) . + Html::hidden( 'ids', implode( ',', $this->ids ) ) . Xml::closeElement( 'fieldset' ) . "\n"; } else { $out = ''; @@ -533,7 +551,7 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { protected function success() { global $wgOut; $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); - $wgOut->wrapWikiMsg( '<span class="success">$1</span>', $this->typeInfo['success'] ); + $wgOut->wrapWikiMsg( "<span class=\"success\">\n$1\n</span>", $this->typeInfo['success'] ); $this->list->reloadFromMaster(); $this->showForm(); } @@ -598,1250 +616,3 @@ class SpecialRevisionDelete extends UnlistedSpecialPage { } } -/** - * 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' ); - } - 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) ); - - } - - // 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 $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; - } - - /** - * Get the internal type name of this list. Equal to the table name. - */ - public function getType() { - return $this->type; - } - - /** - * Get the DB field name associated with the ID list - */ - public function getIdField() { - return $this->idField; - } - - /** - * Get the DB field name storing timestamps - */ - public function getTimestampField() { - return $this->dateField; - } - - /** - * Get the DB field name storing user ids - */ - public function getAuthorIdField() { - return $this->authorIdField; - } - - /** - * 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']; - - $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 { - $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++; - } - } - - // Handle missing revisions - foreach ( $missing as $id => $unused ) { - $status->error( 'revdelete-modify-missing', $id ); - $status->failCount++; - } - - if ( $status->successCount == 0 ) { - $status->ok = false; - $dbw->rollback(); - return $status; - } - - // Save success count - $successCount = $status->successCount; - - // 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; - } - - // Log it - $this->updateLog( array( - 'title' => $this->title, - 'count' => $successCount, - 'newBits' => $newBits, - 'oldBits' => $oldBits, - 'comment' => $comment, - 'ids' => $idsForLog, - 'authorIds' => $authorIds, - 'authorIPs' => $authorIPs - ) ); - $dbw->commit(); - - // Clear caches - $status->merge( $this->doPostCommitUpdates() ); - return $status; - } - - /** - * 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 ); - } - - /** - * 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 ); - } - - /** - * Get the log action for this list type - */ - public function getLogAction() { - return 'revision'; - } - - /** - * 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']}" - ); - } - - /** - * Initialise the current iteration pointer - */ - protected function initCurrent() { - $row = $this->res->current(); - if ( $row ) { - $this->current = $this->newItem( $row ); - } else { - $this->current = false; - } - } - - /** - * Start iteration. This must be called before current() or next(). - * @return First list item - */ - public function reset() { - if ( !$this->res ) { - $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) ); - } else { - $this->res->rewind(); - } - $this->initCurrent(); - return $this->current; - } - - /** - * Get the current list item, or false if we are at the end - */ - public function current() { - return $this->current; - } - - /** - * Move the iteration pointer to the next list item, and return it. - */ - 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(); - } - } - - /** - * Clear any data structures needed for doPreCommitUpdates() and doPostCommitUpdates() - * STUB - */ - public function clearFileOps() { - } - - /** - * A hook for setVisibility(): do batch updates pre-commit. - * STUB - * @return Status - */ - public function doPreCommitUpdates() { - return Status::newGood(); - } - - /** - * A hook for setVisibility(): do any necessary updates post-commit. - * STUB - * @return Status - */ - public function doPostCommitUpdates() { - return Status::newGood(); - } - - /** - * 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(); -} - -/** - * Abstract base class for deletable items - */ -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; - } - - /** - * Get the ID, as it would appear in the ids URL parameter - */ - public function getId() { - $field = $this->list->getIdField(); - return $this->row->$field; - } - - /** - * Get the date, formatted with $wgLang - */ - public function formatDate() { - global $wgLang; - return $wgLang->date( $this->getTimestamp() ); - } - - /** - * Get the time, formatted with $wgLang - */ - public function formatTime() { - global $wgLang; - return $wgLang->time( $this->getTimestamp() ); - } - - /** - * 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 ); - } - - /** - * Returns true if the item is "current", and the operation to set the given - * bits can't be executed for that reason - * STUB - */ - public function isHideCurrentOp( $newBits ) { - return false; - } - - /** - * 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(); - - /** - * Get the current deletion bitfield value - */ - abstract public function getBits(); - - /** - * 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(); - - /** - * 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 - */ - abstract public function setBits( $newBits ); -} - -/** - * 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( - 'rev_page' => $this->title->getArticleID(), - 'rev_id' => $ids, - 'rev_page = page_id' - ), - __METHOD__, - array( 'ORDER BY' => 'rev_id DESC' ) - ); - } - - public function newItem( $row ) { - return new RevDel_RevisionItem( $this, $row ); - } - - public function getCurrent() { - if ( is_null( $this->currentRevId ) ) { - $dbw = wfGetDB( DB_MASTER ); - $this->currentRevId = $dbw->selectField( - 'page', 'page_latest', $this->title->pageCond(), __METHOD__ ); - } - return $this->currentRevId; - } - - public function getSuppressBit() { - return Revision::DELETED_RESTRICTED; - } - - public function doPreCommitUpdates() { - $this->title->invalidateCache(); - return Status::newGood(); - } - - public function doPostCommitUpdates() { - $this->title->purgeSquid(); - // Extensions that require referencing previous revisions may need this - wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) ); - return Status::newGood(); - } -} - -/** - * Item class for a revision table row - */ -class RevDel_RevisionItem extends RevDel_Item { - var $revision; - - public function __construct( $list, $row ) { - parent::__construct( $list, $row ); - $this->revision = new Revision( $row ); - } - - public function canView() { - return $this->revision->userCan( Revision::DELETED_RESTRICTED ); - } - - public function canViewContent() { - return $this->revision->userCan( Revision::DELETED_TEXT ); - } - - public function getBits() { - return $this->revision->mDeleted; - } - - 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; - } - // Update recentchanges table - $dbw->update( 'recentchanges', - array( - 'rc_deleted' => $bits, - 'rc_patrolled' => 1 - ), - array( - 'rc_this_oldid' => $this->revision->getId(), // condition - // non-unique timestamp index - 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ), - ), - __METHOD__ - ); - return true; - } - - public function isDeleted() { - return $this->revision->isDeleted( Revision::DELETED_TEXT ); - } - - public function isHideCurrentOp( $newBits ) { - return ( $newBits & Revision::DELETED_TEXT ) - && $this->list->getCurrent() == $this->getId(); - } - - /** - * Get the HTML link to the revision text. - * Overridden by RevDel_ArchiveItem. - */ - 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 { - return - $this->special->skin->link( - $this->list->title, - wfMsgHtml('diff'), - array(), - array( - 'diff' => $this->revision->getId(), - 'oldid' => 'prev', - 'unhide' => 1 - ), - array( - 'known', - 'noclasses' - ) - ); - } - } - - 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>"; - } -} - -/** - * 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' ) - ); - } - - public function newItem( $row ) { - return new RevDel_ArchiveItem( $this, $row ); - } - - public function doPreCommitUpdates() { - return Status::newGood(); - } - - public function doPostCommitUpdates() { - return Status::newGood(); - } -} - -/** - * 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() - ) ); - } - - protected function getDiffLink() { - if ( $this->isDeleted() && !$this->canViewContent() ) { - return wfMsgHtml( 'diff' ); - } - $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() - ) ); - } -} - -/** - * 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' ) - ); - } - - public function newItem( $row ) { - return new RevDel_FileItem( $this, $row ); - } - - public function clearFileOps() { - $this->deleteBatch = array(); - $this->storeBatch = array(); - $this->cleanupBatch = array(); - } - - 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; - } - - public function doPostCommitUpdates() { - $file = wfLocalFile( $this->title ); - $file->purgeCache(); - $file->purgeDescription(); - return Status::newGood(); - } - - public function getSuppressBit() { - return File::DELETED_RESTRICTED; - } -} - -/** - * 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 ); - } - - public function getId() { - $parts = explode( '!', $this->row->oi_archive_name ); - return $parts[0]; - } - - public function canView() { - return $this->file->userCan( File::DELETED_RESTRICTED ); - } - - public function canViewContent() { - return $this->file->userCan( File::DELETED_FILE ); - } - - 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 ); - } - - /** - * Get the link to the file. - * Overridden by RevDel_ArchivedFileItem. - */ - 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 ); - } - } - /** - * Generate a user tool link cluster if the current user is allowed to view it - * @return string HTML - */ - 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; - } - - /** - * Wrap and format the file's comment block, if the current - * user is allowed to view it. - * - * @return string HTML - */ - 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; - } - - 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>'; - } -} - -/** - * 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' ) - ); - } - - public function newItem( $row ) { - return new RevDel_ArchivedFileItem( $this, $row ); - } -} - -/** - * 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 ); - } - - 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(); - } - - 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; - } -} - -/** - * 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' ) - ); - } - - public function newItem( $row ) { - return new RevDel_LogItem( $this, $row ); - } - - public function getSuppressBit() { - return Revision::DELETED_RESTRICTED; - } - - public function getLogAction() { - return 'event'; - } - - public function getLogParams( $params ) { - return array( - implode( ',', $params['ids'] ), - "ofield={$params['oldBits']}", - "nfield={$params['newBits']}" - ); - } -} - -/** - * 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; - } - - 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; - - $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 ); - - // 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 { - $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 40b28236..fd6e858e 100644 --- a/includes/specials/SpecialSearch.php +++ b/includes/specials/SpecialSearch.php @@ -1,24 +1,24 @@ <?php -# Copyright (C) 2004 Brion Vibber <brion@pobox.com> -# 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 - /** - * Run text & title search and display the output + * Implements Special:Search + * + * Copyright © 2004 Brion Vibber <brion@pobox.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ @@ -29,7 +29,9 @@ * @param $par String: (default '') */ function wfSpecialSearch( $par = '' ) { - global $wgRequest, $wgUser; + global $wgRequest, $wgUser, $wgOut; + $wgOut->allowClickjacking(); + // Strip underscores from title parameter; most of the time we'll want // text form here. But don't strip underscores from actual text params! $titleParam = str_replace( '_', ' ', $par ); @@ -56,11 +58,10 @@ class SpecialSearch { * 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 + * @param $request WebRequest + * @param $user User */ - function __construct( &$request, &$user ) { + public function __construct( &$request, &$user ) { list( $this->limit, $this->offset ) = $request->getLimitOffset( 20, 'searchlimit' ); $this->mPrefix = $request->getVal('prefix', ''); # Extract requested namespaces @@ -68,7 +69,7 @@ class SpecialSearch { if( empty( $this->namespaces ) ) { $this->namespaces = SearchEngine::userNamespaces( $user ); } - $this->searchRedirects = $request->getcheck( 'redirs' ) ? true : false; + $this->searchRedirects = $request->getCheck( 'redirs' ); $this->searchAdvanced = $request->getVal( 'advanced' ); $this->active = 'advanced'; $this->sk = $user->getSkin(); @@ -78,7 +79,8 @@ class SpecialSearch { /** * If an exact title match can be found, jump straight ahead to it. - * @param string $term + * + * @param $term String */ public function goResult( $term ) { global $wgOut; @@ -91,6 +93,12 @@ class SpecialSearch { } # If there's an exact or very near match, jump right there. $t = SearchEngine::getNearMatch( $term ); + + if ( !wfRunHooks( 'SpecialSearchGo', array( &$t, &$term ) ) ) { + # Hook requested termination + return; + } + if( !is_null( $t ) ) { $wgOut->redirect( $t->getFullURL() ); return; @@ -100,6 +108,8 @@ class SpecialSearch { if( !is_null( $t ) ) { global $wgGoToEdit; wfRunHooks( 'SpecialSearchNogomatch', array( &$t ) ); + wfDebugLog( 'nogomatch', $t->getText(), false ); + # If the feature is enabled, go straight to the edit page if( $wgGoToEdit ) { $wgOut->redirect( $t->getFullURL( array( 'action' => 'edit' ) ) ); @@ -110,7 +120,7 @@ class SpecialSearch { } /** - * @param string $term + * @param $term String */ public function showResults( $term ) { global $wgOut, $wgUser, $wgDisableTextSearch, $wgContLang, $wgScript; @@ -220,7 +230,6 @@ class SpecialSearch { $filePrefix = $wgContLang->getFormattedNsText(NS_FILE).':'; 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 ) ); @@ -302,13 +311,10 @@ class SpecialSearch { $textMatches->free(); } if( $num === 0 ) { - $wgOut->addWikiMsg( 'search-nonefound', wfEscapeWikiText( $term ) ); + $wgOut->wrapWikiMsg( "<p class=\"mw-search-nonefound\">\n$1</p>", array( 'search-nonefound', wfEscapeWikiText( $term ) ) ); $this->showCreateLink( $t ); } $wgOut->addHtml( "</div>" ); - if( $num === 0 ) { - $wgOut->addHTML( $this->searchFocus() ); - } if( $num || $this->offset ) { $wgOut->addHTML( "<p class='mw-search-pager-bottom'>{$prevnext}</p>\n" ); @@ -326,10 +332,12 @@ class SpecialSearch { $messageName = 'searchmenu-exists'; } elseif( $t->userCan( 'create' ) ) { $messageName = 'searchmenu-new'; + } else { + $messageName = 'searchmenu-new-nocreate'; } } if( $messageName ) { - $wgOut->addWikiMsg( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) ); + $wgOut->wrapWikiMsg( "<p class=\"mw-search-createlink\">\n$1</p>", array( $messageName, wfEscapeWikiText( $t->getPrefixedText() ) ) ); } else { // preserve the paragraph for margins etc... $wgOut->addHtml( '<p></p>' ); @@ -342,10 +350,9 @@ class SpecialSearch { protected function setupPage( $term ) { global $wgOut; // Figure out the active search profile header - $nsAllSet = array_keys( SearchEngine::searchableNamespaces() ); - if( $this->searchAdvanced ) + if( $this->searchAdvanced ) { $this->active = 'advanced'; - else { + } else { $profiles = $this->getSearchProfiles(); foreach( $profiles as $key => $data ) { @@ -363,16 +370,16 @@ class SpecialSearch { $wgOut->setArticleRelated( false ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); // add javascript specific to special:search - $wgOut->addScriptFile( 'search.js' ); - $wgOut->allowClickjacking(); + $wgOut->addModules( 'mediawiki.legacy.search' ); + $wgOut->addModules( 'mediawiki.special.search' ); } /** * Extract "power search" namespace settings from the request object, * returning a list of index numbers to search. * - * @param WebRequest $request - * @return array + * @param $request WebRequest + * @return Array */ protected function powerSearch( &$request ) { $arr = array(); @@ -386,7 +393,8 @@ class SpecialSearch { /** * Reconstruct the 'power search' options for links - * @return array + * + * @return Array */ protected function powerSearchOptions() { $opt = array(); @@ -403,7 +411,7 @@ class SpecialSearch { /** * Show whole set of results * - * @param SearchResultSet $matches + * @param $matches SearchResultSet */ protected function showMatches( &$matches ) { global $wgContLang; @@ -416,7 +424,6 @@ class SpecialSearch { 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 ); @@ -431,11 +438,12 @@ class SpecialSearch { /** * Format a single hit result - * @param SearchResult $result - * @param array $terms terms to highlight + * + * @param $result SearchResult + * @param $terms Array: terms to highlight */ protected function showHit( $result, $terms ) { - global $wgContLang, $wgLang, $wgUser; + global $wgLang, $wgUser; wfProfileIn( __METHOD__ ); if( $result->isBrokenTitle() ) { @@ -539,6 +547,18 @@ class SpecialSearch { $this->sk->formatSize( $byteSize ), $wgLang->formatNum( $wordCount ) ); + + if( $t->getNamespace() == NS_CATEGORY ) { + $cat = Category::newFromTitle( $t ); + $size = wfMsgExt( + 'search-result-category-size', + array( 'parsemag', 'escape' ), + $wgLang->formatNum( $cat->getPageCount() ), + $wgLang->formatNum( $cat->getSubcatCount() ), + $wgLang->formatNum( $cat->getFileCount() ) + ); + } + $date = $wgLang->timeanddate( $timestamp ); // link to related articles if supported @@ -567,7 +587,7 @@ class SpecialSearch { if( $img ) { $thumb = $img->transform( array( 'width' => 120, 'height' => 120 ) ); if( $thumb ) { - $desc = $img->getShortDesc(); + $desc = wfMsg( 'parentheses', $img->getShortDesc() ); wfProfileOut( __METHOD__ ); // Float doesn't seem to interact well with the bullets. // Table messes up vertical alignment of the bullets. @@ -591,7 +611,7 @@ class SpecialSearch { } wfProfileOut( __METHOD__ ); - return "<li>{$link} {$redirect} {$section} {$extract}\n" . + return "<li><div class='mw-search-result-heading'>{$link} {$redirect} {$section}</div> {$extract}\n" . "<div class='mw-search-result-data'>{$score}{$size} - {$date}{$related}</div>" . "</li>\n"; @@ -600,7 +620,8 @@ class SpecialSearch { /** * Show results from other wikis * - * @param SearchResultSet $matches + * @param $matches SearchResultSet + * @param $query String */ protected function showInterwiki( &$matches, $query ) { global $wgContLang; @@ -609,7 +630,6 @@ class SpecialSearch { $out = "<div id='mw-search-interwiki'><div id='mw-search-interwiki-caption'>". wfMsg('search-interwiki-caption')."</div>\n"; - $off = $this->offset + 1; $out .= "<ul class='mw-search-iwresults'>\n"; // work out custom project captions @@ -638,15 +658,14 @@ class SpecialSearch { /** * Show single interwiki link * - * @param SearchResult $result - * @param string $lastInterwiki - * @param array $terms - * @param string $query - * @param array $customCaptions iw prefix -> caption + * @param $result SearchResult + * @param $lastInterwiki String + * @param $terms Array + * @param $query String + * @param $customCaptions Array: iw prefix -> caption */ protected function showInterwikiHit( $result, $lastInterwiki, $terms, $query, $customCaptions) { wfProfileIn( __METHOD__ ); - global $wgContLang, $wgLang; if( $result->isBrokenTitle() ) { wfProfileOut( __METHOD__ ); @@ -719,12 +738,11 @@ class SpecialSearch { /** * Generates the power search box at bottom of [[Special:Search]] - * @param $term string: search term - * @return $out string: HTML form + * + * @param $term String: search term + * @return String: HTML form */ protected function powerSearchBox( $term ) { - global $wgScript, $wgContLang; - // Groups namespaces into rows according to subject $rows = array(); foreach( SearchEngine::searchableNamespaces() as $namespace => $name ) { @@ -809,19 +827,11 @@ class SpecialSearch { $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' ) . + Html::hidden( 'title', SpecialPage::getTitleFor( 'Search' )->getPrefixedText() ) . + Html::hidden( 'advanced', $this->searchAdvanced ) . + Html::hidden( 'fulltext', 'Advanced search' ) . Xml::closeElement( 'fieldset' ); } - - protected function searchFocus() { - $id = $this->searchAdvanced ? 'powerSearchText' : 'searchText'; - return Html::inlineScript( - "hookEvent(\"load\", function() {" . - "document.getElementById('$id').focus();" . - "});" ); - } protected function getSearchProfiles() { // Builds list of Search Types (profiles) @@ -864,7 +874,7 @@ class SpecialSearch { wfRunHooks( 'SpecialSearchProfiles', array( &$profiles ) ); - foreach( $profiles as $key => &$data ) { + foreach( $profiles as &$data ) { sort($data['namespaces']); } @@ -872,7 +882,7 @@ class SpecialSearch { } protected function formHeader( $term, $resultsShown, $totalNum ) { - global $wgContLang, $wgLang; + global $wgLang; $out = Xml::openElement('div', array( 'class' => 'mw-search-formheader' ) ); @@ -882,7 +892,6 @@ class SpecialSearch { $bareterm = substr( $term, strpos( $term, ':' ) + 1 ); } - $profiles = $this->getSearchProfiles(); // Outputs XML for Search Types @@ -934,7 +943,7 @@ class SpecialSearch { // Adds hidden namespace fields if ( !$this->searchAdvanced ) { foreach( $this->namespaces as $ns ) { - $out .= Xml::hidden( "ns{$ns}", '1' ); + $out .= Html::hidden( "ns{$ns}", '1' ); } } @@ -943,7 +952,6 @@ class SpecialSearch { protected function shortDialog( $term ) { $searchTitle = SpecialPage::getTitleFor( 'Search' ); - $searchable = SearchEngine::searchableNamespaces(); $out = Html::hidden( 'title', $searchTitle->getPrefixedText() ) . "\n"; // Keep redirect setting $out .= Html::hidden( "redirs", (int)$this->searchRedirects ) . "\n"; @@ -958,7 +966,16 @@ class SpecialSearch { return $out . $this->didYouMeanHtml; } - /** Make a search link with some target namespaces */ + /** + * Make a search link with some target namespaces + * + * @param $term String + * @param $namespaces Array + * @param $label String: link's text + * @param $tooltip String: link's tooltip + * @param $params Array: query string parameters + * @return String: HTML fragment + */ protected function makeSearchLink( $term, $namespaces, $label, $tooltip, $params=array() ) { $opt = $params; foreach( $namespaces as $n ) { @@ -986,7 +1003,12 @@ class SpecialSearch { ); } - /** Check if query starts with image: prefix */ + /** + * Check if query starts with image: prefix + * + * @param $term String: the string to check + * @return Boolean + */ protected function startsWithImage( $term ) { global $wgContLang; @@ -997,7 +1019,12 @@ class SpecialSearch { return false; } - /** Check if query starts with all: prefix */ + /** + * Check if query starts with all: prefix + * + * @param $term String: the string to check + * @return Boolean + */ protected function startsWithAll( $term ) { $allkeyword = wfMsgForContent('searchall'); diff --git a/includes/specials/SpecialShortpages.php b/includes/specials/SpecialShortpages.php index c41b15c5..989e4c07 100644 --- a/includes/specials/SpecialShortpages.php +++ b/includes/specials/SpecialShortpages.php @@ -1,5 +1,22 @@ <?php /** + * Implements Special:Shortpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ @@ -7,6 +24,7 @@ /** * SpecialShortpages extends QueryPage. It is used to return the shortest * pages in the database. + * * @ingroup SpecialPage */ class ShortPagesPage extends QueryPage { @@ -54,11 +72,13 @@ class ShortPagesPage extends QueryPage { # the page must exist for it to have been pulled out of the table if( $this->isCached() ) { $batch = new LinkBatch(); - while( $row = $db->fetchObject( $res ) ) + foreach ( $res as $row ) { $batch->add( $row->namespace, $row->title ); + } $batch->execute(); - if( $db->numRows( $res ) > 0 ) + if ( $db->numRows( $res ) > 0 ) { $db->dataSeek( $res, 0 ); + } } } @@ -83,11 +103,11 @@ class ShortPagesPage extends QueryPage { $plink = $this->isCached() ? $skin->link( $title ) : $skin->linkKnown( $title ); - $size = wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ), $wgLang->formatNum( htmlspecialchars( $result->value ) ) ); + $size = wfMessage( 'nbytes', $wgLang->formatNum( $result->value ) )->escaped(); return $title->exists() ? "({$hlink}) {$dm}{$plink} {$dm}[{$size}]" - : "<s>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</s>"; + : "<del>({$hlink}) {$dm}{$plink} {$dm}[{$size}]</del>"; } } diff --git a/includes/specials/SpecialSpecialpages.php b/includes/specials/SpecialSpecialpages.php index 8e97f9b7..19bc6b00 100644 --- a/includes/specials/SpecialSpecialpages.php +++ b/includes/specials/SpecialSpecialpages.php @@ -1,84 +1,138 @@ <?php /** + * Implements Special:Specialpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** + * A special page that lists special pages * + * @ingroup SpecialPage */ -function wfSpecialSpecialpages() { - global $wgOut, $wgUser, $wgMessageCache, $wgSortSpecialPages; +class SpecialSpecialpages extends UnlistedSpecialPage { + + function __construct() { + parent::__construct( 'Specialpages' ); + } - $wgMessageCache->loadAllMessages(); + function execute( $par ) { + global $wgOut; + $this->setHeaders(); + $this->outputHeader(); + $wgOut->allowClickjacking(); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); # Is this really needed? - $wgOut->allowClickjacking(); - $sk = $wgUser->getSkin(); + $groups = $this->getPageGroups(); - $pages = SpecialPage::getUsablePages(); + if ( $groups === false ) { + return; + } - if( count( $pages ) == 0 ) { - # Yeah, that was pointless. Thanks for coming. - return; + $this->outputPageList( $groups ); } - /** Put them into a sortable array */ - $groups = array(); - foreach ( $pages as $page ) { - if ( $page->isListed() ) { - $group = SpecialPage::getGroup( $page ); - if( !isset($groups[$group]) ) { - $groups[$group] = array(); + private function getPageGroups() { + global $wgSortSpecialPages; + + $pages = SpecialPage::getUsablePages(); + + if( !count( $pages ) ) { + # Yeah, that was pointless. Thanks for coming. + return false; + } + + /** Put them into a sortable array */ + $groups = array(); + foreach ( $pages as $page ) { + if ( $page->isListed() ) { + $group = SpecialPage::getGroup( $page ); + if( !isset( $groups[$group] ) ) { + $groups[$group] = array(); + } + $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted() ); } - $groups[$group][$page->getDescription()] = array( $page->getTitle(), $page->isRestricted() ); } - } - /** Sort */ - if ( $wgSortSpecialPages ) { - foreach( $groups as $group => $sortedPages ) { - ksort( $groups[$group] ); + /** Sort */ + if ( $wgSortSpecialPages ) { + foreach( $groups as $group => $sortedPages ) { + ksort( $groups[$group] ); + } } - } - /** Always move "other" to end */ - if( array_key_exists('other',$groups) ) { - $other = $groups['other']; - unset( $groups['other'] ); - $groups['other'] = $other; + /** Always move "other" to end */ + if( array_key_exists( 'other', $groups ) ) { + $other = $groups['other']; + unset( $groups['other'] ); + $groups['other'] = $other; + } + + return $groups; } - $includesRestrictedPages = false; - /** Now output the HTML */ - foreach ( $groups as $group => $sortedPages ) { - $middle = ceil( count($sortedPages)/2 ); - $total = count($sortedPages); - $count = 0; - - $wgOut->wrapWikiMsg( "<h4 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h4>\n", "specialpages-group-$group" ); - $wgOut->addHTML( "<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->linkKnown( $title , htmlspecialchars( $desc ) ); - if( $restricted ) { - $includesRestrictedPages = true; - $wgOut->addHTML( "<li class='mw-specialpages-page mw-specialpagerestricted'><strong>{$link}</strong></li>\n" ); - } else { - $wgOut->addHTML( "<li>{$link}</li>\n" ); - } + private function outputPageList( $groups ) { + global $wgUser, $wgOut; - # Split up the larger groups - $count++; - if( $total > 3 && $count == $middle ) { - $wgOut->addHTML( "</ul></td><td width='10%'></td><td width='30%' valign='top'><ul>" ); + $sk = $wgUser->getSkin(); + $includesRestrictedPages = false; + + foreach ( $groups as $group => $sortedPages ) { + $middle = ceil( count( $sortedPages )/2 ); + $total = count( $sortedPages ); + $count = 0; + + $wgOut->wrapWikiMsg( "<h4 class=\"mw-specialpagesgroup\" id=\"mw-specialpagesgroup-$group\">$1</h4>\n", "specialpages-group-$group" ); + $wgOut->addHTML( + Html::openElement( 'table', array( 'style' => 'width:100%;', 'class' => 'mw-specialpages-table' ) ) ."\n" . + Html::openElement( 'tr' ) . "\n" . + Html::openElement( 'td', array( 'style' => 'width:30%;vertical-align:top' ) ) . "\n" . + Html::openElement( 'ul' ) . "\n" + ); + foreach( $sortedPages as $desc => $specialpage ) { + list( $title, $restricted ) = $specialpage; + $link = $sk->linkKnown( $title , htmlspecialchars( $desc ) ); + if( $restricted ) { + $includesRestrictedPages = true; + $wgOut->addHTML( Html::rawElement( 'li', array( 'class' => 'mw-specialpages-page mw-specialpagerestricted' ), Html::rawElement( 'strong', array(), $link ) ) . "\n" ); + } else { + $wgOut->addHTML( Html::rawElement( 'li', array(), $link ) . "\n" ); + } + + # Split up the larger groups + $count++; + if( $total > 3 && $count == $middle ) { + $wgOut->addHTML( + Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) . + Html::element( 'td', array( 'style' => 'width:10%' ), '' ) . + Html::openElement( 'td', array( 'style' => 'width:30%' ) ) . Html::openElement( 'ul' ) . "\n" + ); + } } + $wgOut->addHTML( + Html::closeElement( 'ul' ) . Html::closeElement( 'td' ) . + Html::element( 'td', array( 'style' => 'width:30%' ), '' ) . + Html::closeElement( 'tr' ) . Html::closeElement( 'table' ) . "\n" + ); } - $wgOut->addHTML( "</ul></td><td width='30%' valign='top'></td></tr></table>\n" ); - } - if ( $includesRestrictedPages ) { - $wgOut->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' ); + if ( $includesRestrictedPages ) { + $wgOut->wrapWikiMsg( "<div class=\"mw-specialpages-notes\">\n$1\n</div>", 'specialpages-note' ); + } } } diff --git a/includes/specials/SpecialStatistics.php b/includes/specials/SpecialStatistics.php index 2e785b8b..b0d0246e 100644 --- a/includes/specials/SpecialStatistics.php +++ b/includes/specials/SpecialStatistics.php @@ -1,31 +1,44 @@ <?php - /** - * Special page lists various statistics, including the contents of - * `site_stats`, plus page view details if enabled + * Implements Special:Statistics + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage */ /** - * Show the special page + * Special page lists various statistics, including the contents of + * `site_stats`, plus page view details if enabled * - * @param mixed $par (not used) + * @ingroup SpecialPage */ class SpecialStatistics extends SpecialPage { private $views, $edits, $good, $images, $total, $users, - $activeUsers, $admins, $numJobs = 0; + $activeUsers, $admins = 0; public function __construct() { parent::__construct( 'Statistics' ); } public function execute( $par ) { - global $wgOut, $wgRequest, $wgMessageCache, $wgMemc; + global $wgOut, $wgMemc; global $wgDisableCounters, $wgMiserMode; - $wgMessageCache->loadAllMessages(); $this->setHeaders(); @@ -37,7 +50,6 @@ class SpecialStatistics extends SpecialPage { $this->users = SiteStats::users(); $this->activeUsers = SiteStats::activeUsers(); $this->admins = SiteStats::numberingroup('sysop'); - $this->numJobs = SiteStats::jobs(); $this->hook = ''; # Staticic - views @@ -56,11 +68,6 @@ class SpecialStatistics extends SpecialPage { $wgMemc->set( $key, '1', 24*3600 ); // don't update for 1 day } } - - # Do raw output - if( $wgRequest->getVal( 'action' ) == 'raw' ) { - $this->doRawOutput(); - } $text = Xml::openElement( 'table', array( 'class' => 'wikitable mw-statistics-table' ) ); @@ -101,15 +108,14 @@ class SpecialStatistics extends SpecialPage { /** * Format a row - * @param string $text description of the row - * @param float $number a number - * @param array $trExtraParams - * @param string $descMsg - * @param string $descMsgParam + * @param $text String: description of the row + * @param $number Float: a statistical number + * @param $trExtraParams Array: params to table row, see Html::elememt + * @param $descMsg String: message key + * @param $descMsgParam Array: message params * @return string table row in HTML format */ private function formatRow( $text, $number, $trExtraParams = array(), $descMsg = '', $descMsgParam = '' ) { - global $wgStylePath; if( $descMsg ) { $descriptionText = wfMsgExt( $descMsg, array( 'parseinline' ), $descMsgParam ); if ( !wfEmptyMsg( $descMsg, $descriptionText ) ) { @@ -118,10 +124,11 @@ class SpecialStatistics extends SpecialPage { $descriptionText ); } } - return Xml::openElement( 'tr', $trExtraParams ) . - Xml::openElement( 'td' ) . $text . Xml::closeElement( 'td' ) . - Xml::openElement( 'td', array( 'class' => 'mw-statistics-numbers' ) ) . $number . Xml::closeElement( 'td' ) . - Xml::closeElement( 'tr' ); + return + Html::rawElement( 'tr', $trExtraParams, + Html::rawElement( 'td', array(), $text ) . + Html::rawElement( 'td', array( 'class' => 'mw-statistics-numbers' ), $number ) + ); } /** @@ -155,13 +162,11 @@ class SpecialStatistics extends SpecialPage { array( 'class' => 'mw-statistics-edits' ) ) . $this->formatRow( wfMsgExt( 'statistics-edits-average', array( 'parseinline' ) ), $wgLang->formatNum( sprintf( '%.2f', $this->total ? $this->edits / $this->total : 0 ) ), - array( 'class' => 'mw-statistics-edits-average' ) ) . - $this->formatRow( wfMsgExt( 'statistics-jobqueue', array( 'parseinline' ) ), - $wgLang->formatNum( $this->numJobs ), - array( 'class' => 'mw-statistics-jobqueue' ) ); + array( 'class' => 'mw-statistics-edits-average' ) ); } + private function getUserStats() { - global $wgLang, $wgUser, $wgRCMaxAge; + global $wgLang, $wgUser, $wgActiveUserDays; $sk = $wgUser->getSkin(); return Xml::openElement( 'tr' ) . Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-header-users', array( 'parseinline' ) ) ) . @@ -180,8 +185,9 @@ class SpecialStatistics extends SpecialPage { $wgLang->formatNum( $this->activeUsers ), array( 'class' => 'mw-statistics-users-active' ), 'statistics-users-active-desc', - $wgLang->formatNum( ceil( $wgRCMaxAge / ( 3600 * 24 ) ) ) ); + $wgLang->formatNum( $wgActiveUserDays ) ); } + private function getGroupStats() { global $wgGroupPermissions, $wgImplicitGroups, $wgLang, $wgUser; $sk = $wgUser->getSkin(); @@ -228,6 +234,7 @@ class SpecialStatistics extends SpecialPage { } return $text; } + private function getViewsStats() { global $wgLang; return Xml::openElement( 'tr' ) . @@ -235,12 +242,13 @@ class SpecialStatistics extends SpecialPage { Xml::closeElement( 'tr' ) . $this->formatRow( wfMsgExt( 'statistics-views-total', array( 'parseinline' ) ), $wgLang->formatNum( $this->views ), - array ( 'class' => 'mw-statistics-views-total' ) ) . + array ( 'class' => 'mw-statistics-views-total' ), 'statistics-views-total-desc' ) . $this->formatRow( wfMsgExt( 'statistics-views-peredit', array( 'parseinline' ) ), $wgLang->formatNum( sprintf( '%.2f', $this->edits ? $this->views / $this->edits : 0 ) ), array ( 'class' => 'mw-statistics-views-peredit' ) ); } + private function getMostViewedPages() { global $wgLang, $wgUser; $text = ''; @@ -267,7 +275,7 @@ class SpecialStatistics extends SpecialPage { $text .= Xml::openElement( 'tr' ); $text .= Xml::tags( 'th', array( 'colspan' => '2' ), wfMsgExt( 'statistics-mostpopular', array( 'parseinline' ) ) ); $text .= Xml::closeElement( 'tr' ); - while( $row = $res->fetchObject() ) { + foreach ( $res as $row ) { $title = Title::makeTitleSafe( $row->page_namespace, $row->page_title ); if( $title instanceof Title ) { $text .= $this->formatRow( $sk->link( $title ), @@ -279,7 +287,7 @@ class SpecialStatistics extends SpecialPage { } return $text; } - + private function getOtherStats( $stats ) { global $wgLang; @@ -299,20 +307,4 @@ class SpecialStatistics extends SpecialPage { return $return; } - - /** - * Do the action=raw output for this page. Legacy, but we support - * it for backwards compatibility - * http://lists.wikimedia.org/pipermail/wikitech-l/2008-August/039202.html - */ - private function doRawOutput() { - global $wgOut; - $wgOut->disable(); - header( 'Pragma: nocache' ); - echo "total=" . $this->total . ";good=" . $this->good . ";views=" . - $this->views . ";edits=" . $this->edits . ";users=" . $this->users . ";"; - echo "activeusers=" . $this->activeUsers . ";admins=" . $this->admins . - ";images=" . $this->images . ";jobs=" . $this->numJobs . "\n"; - return; - } -}
\ No newline at end of file +} diff --git a/includes/specials/SpecialTags.php b/includes/specials/SpecialTags.php index 57feeae7..c2aecf47 100644 --- a/includes/specials/SpecialTags.php +++ b/includes/specials/SpecialTags.php @@ -1,8 +1,34 @@ <?php +/** + * Implements Special:Tags + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ if (!defined('MEDIAWIKI')) die; +/** + * A special page that lists tags for edits + * + * @ingroup SpecialPage + */ class SpecialTags extends SpecialPage { function __construct() { @@ -10,16 +36,12 @@ class SpecialTags extends SpecialPage { } function execute( $par ) { - global $wgOut, $wgUser, $wgMessageCache; + global $wgOut; - $wgMessageCache->loadAllMessages(); - - $sk = $wgUser->getSkin(); $wgOut->setPageTitle( wfMsg( 'tags-title' ) ); - $wgOut->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1</div>", 'tags-intro' ); + $wgOut->wrapWikiMsg( "<div class='mw-tags-intro'>\n$1\n</div>", 'tags-intro' ); // Write the headers - $html = ''; $html = Xml::tags( 'tr', null, Xml::tags( 'th', null, wfMsgExt( 'tags-tag', 'parseinline' ) ) . Xml::tags( 'th', null, wfMsgExt( 'tags-display-header', 'parseinline' ) ) . Xml::tags( 'th', null, wfMsgExt( 'tags-description-header', 'parseinline' ) ) . @@ -28,7 +50,7 @@ class SpecialTags extends SpecialPage { $dbr = wfGetDB( DB_SLAVE ); $res = $dbr->select( 'change_tag', array( 'ct_tag', 'count(*) as hitcount' ), array(), __METHOD__, array( 'GROUP BY' => 'ct_tag', 'ORDER BY' => 'hitcount DESC' ) ); - while ( $row = $res->fetchObject() ) { + foreach ( $res as $row ) { $html .= $this->doTagRow( $row->ct_tag, $row->hitcount ); } @@ -72,4 +94,4 @@ class SpecialTags extends SpecialPage { return Xml::tags( 'tr', null, $newRow ) . "\n"; } -}
\ No newline at end of file +} diff --git a/includes/specials/SpecialUncategorizedcategories.php b/includes/specials/SpecialUncategorizedcategories.php index f23e89ce..9574af70 100644 --- a/includes/specials/SpecialUncategorizedcategories.php +++ b/includes/specials/SpecialUncategorizedcategories.php @@ -1,15 +1,33 @@ <?php /** + * Implements Special:Uncategorizedcategories + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * implements Special:Uncategorizedcategories + * A special page that lists uncategorized categories + * * @ingroup SpecialPage */ class UncategorizedCategoriesPage extends UncategorizedPagesPage { - function UncategorizedCategoriesPage() { + function __construct() { $this->requestedNamespace = NS_CATEGORY; } diff --git a/includes/specials/SpecialUncategorizedimages.php b/includes/specials/SpecialUncategorizedimages.php index 25310081..c4254039 100644 --- a/includes/specials/SpecialUncategorizedimages.php +++ b/includes/specials/SpecialUncategorizedimages.php @@ -1,6 +1,21 @@ <?php /** - * Special page lists images which haven't been categorised + * Implements Special:Uncategorizedimages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage @@ -8,6 +23,8 @@ */ /** + * Special page lists images which haven't been categorised + * * @ingroup SpecialPage */ class UncategorizedImagesPage extends ImageQueryPage { diff --git a/includes/specials/SpecialUncategorizedpages.php b/includes/specials/SpecialUncategorizedpages.php index e7f0aaca..c7fef5d2 100644 --- a/includes/specials/SpecialUncategorizedpages.php +++ b/includes/specials/SpecialUncategorizedpages.php @@ -1,11 +1,29 @@ <?php /** + * Implements Special:Uncategorizedpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** * A special page looking for page without any category. + * * @ingroup SpecialPage */ class UncategorizedPagesPage extends PageQueryPage { diff --git a/includes/specials/SpecialUncategorizedtemplates.php b/includes/specials/SpecialUncategorizedtemplates.php index 7e6fd24b..aa4e979d 100644 --- a/includes/specials/SpecialUncategorizedtemplates.php +++ b/includes/specials/SpecialUncategorizedtemplates.php @@ -1,7 +1,25 @@ <?php /** + * Implements Special:Uncategorizedtemplates + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Rob Church <robchur@gmail.com> */ /** @@ -9,7 +27,6 @@ * template namespace * * @ingroup SpecialPage - * @author Rob Church <robchur@gmail.com> */ class UncategorizedTemplatesPage extends UncategorizedPagesPage { diff --git a/includes/specials/SpecialUndelete.php b/includes/specials/SpecialUndelete.php index 4db4e633..1cf61d26 100644 --- a/includes/specials/SpecialUndelete.php +++ b/includes/specials/SpecialUndelete.php @@ -1,25 +1,29 @@ <?php - /** - * Special page allowing users with the appropriate permissions to view - * and restore deleted content + * Implements Special:Undelete + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage */ /** - * Constructor - */ -function wfSpecialUndelete( $par ) { - global $wgRequest; - - $form = new UndeleteForm( $wgRequest, $par ); - $form->execute(); -} - -/** * Used to show archived pages and eventually restore them. + * * @ingroup SpecialPage */ class PageArchive { @@ -28,7 +32,7 @@ class PageArchive { function __construct( $title ) { if( is_null( $title ) ) { - throw new MWException( 'Archiver() given a null title.'); + throw new MWException( __METHOD__ . ' given a null title.' ); } $this->title = $title; } @@ -50,6 +54,7 @@ class PageArchive { * given title prefix. * Returns result wrapper with (ar_namespace, ar_title, count) fields. * + * @param $prefix String: title prefix * @return ResultWrapper */ public static function listPagesByPrefix( $prefix ) { @@ -153,7 +158,8 @@ class PageArchive { * Fetch (and decompress if necessary) the stored text for the deleted * revision of the page with the given timestamp. * - * @return string + * @param $timestamp String + * @return String * @deprecated Use getRevision() for more flexible information */ function getRevisionText( $timestamp ) { @@ -164,7 +170,8 @@ class PageArchive { /** * Return a Revision object containing data for the deleted revision. * Note that the result *may* or *may not* have a null page ID. - * @param string $timestamp + * + * @param $timestamp String * @return Revision */ function getRevision( $timestamp ) { @@ -200,7 +207,7 @@ class PageArchive { * May produce unexpected results in case of history merges or other * unusual time issues. * - * @param string $timestamp + * @param $timestamp String * @return Revision or null */ function getPreviousRevision( $timestamp ) { @@ -248,6 +255,9 @@ class PageArchive { /** * Get the text from an archive row containing ar_text, ar_flags and ar_text_id + * + * @param $row Object: database row + * @return Revision */ function getTextFromRow( $row ) { if( is_null( $row->ar_text_id ) ) { @@ -272,7 +282,7 @@ class PageArchive { * * If there are no archived revisions for the page, returns NULL. * - * @return string + * @return String */ function getLastRevisionText() { $dbr = wfGetDB( DB_SLAVE ); @@ -280,7 +290,7 @@ class PageArchive { array( 'ar_text', 'ar_flags', 'ar_text_id' ), array( 'ar_namespace' => $this->title->getNamespace(), 'ar_title' => $this->title->getDBkey() ), - 'PageArchive::getLastRevisionText', + __METHOD__, array( 'ORDER BY' => 'ar_timestamp DESC' ) ); if( $row ) { return $this->getTextFromRow( $row ); @@ -291,7 +301,8 @@ class PageArchive { /** * Quick check if any archived revisions are present for the page. - * @return bool + * + * @return Boolean */ function isDeleted() { $dbr = wfGetDB( DB_SLAVE ); @@ -306,10 +317,10 @@ class PageArchive { * Once restored, the items will be removed from the archive tables. * The deletion log will be updated with an undeletion notice. * - * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param string $comment - * @param array $fileVersions - * @param bool $unsuppress + * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @param $comment String + * @param $fileVersions Array + * @param $unsuppress Boolean * * @return array(number of file revisions restored, number of image revisions restored, log message) * on success, false on failure @@ -369,12 +380,11 @@ class PageArchive { * to the cur/old tables. If the page currently exists, all revisions will * be stuffed into old, otherwise the most recent will go into cur. * - * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete. - * @param string $comment - * @param array $fileVersions - * @param bool $unsuppress, remove all ar_deleted/fa_deleted restrictions of seletected revs + * @param $timestamps Array: pass an empty array to restore all revisions, otherwise list the ones to undelete. + * @param $comment String + * @param $unsuppress Boolean: remove all ar_deleted/fa_deleted restrictions of seletected revs * - * @return mixed number of revisions restored or false on failure + * @return Mixed: number of revisions restored or false on failure */ private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) { if ( wfReadOnly() ) @@ -482,7 +492,7 @@ class PageArchive { $revision = null; $restored = 0; - while( $row = $ret->fetchObject() ) { + foreach ( $ret as $row ) { // Check for key dupes due to shitty archive integrity. if( $row->ar_rev_id ) { $exists = $dbw->selectField( 'revision', '1', array('rev_id' => $row->ar_rev_id), __METHOD__ ); @@ -490,12 +500,12 @@ class PageArchive { } // Insert one revision at a time...maintaining deletion status // unless we are specifically removing all restrictions... - $revision = Revision::newFromArchiveRow( $row, - array( - 'page' => $pageId, + $revision = Revision::newFromArchiveRow( $row, + array( + 'page' => $pageId, 'deleted' => $unsuppress ? 0 : $row->ar_deleted ) ); - + $revision->insertOn( $dbw ); $restored++; @@ -508,7 +518,7 @@ class PageArchive { 'ar_title' => $this->title->getDBkey(), $oldones ), __METHOD__ ); - + // Was anything restored at all? if( $restored == 0 ) return 0; @@ -535,7 +545,8 @@ class PageArchive { } } else { // Revision couldn't be created. This is very weird - return self::UNDELETE_UNKNOWNERR; + wfDebug( "Undelete: unknown error...\n" ); + return false; } return $restored; @@ -545,36 +556,45 @@ class PageArchive { } /** - * The HTML form for Special:Undelete, which allows users with the appropriate - * permissions to view and restore deleted content. + * Special page allowing users with the appropriate permissions to view + * and restore deleted content. + * * @ingroup SpecialPage */ -class UndeleteForm { +class UndeleteForm extends SpecialPage { var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj; - var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken; + var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken, $mRequest; - function UndeleteForm( $request, $par = "" ) { + function __construct( $request = null ) { + parent::__construct( 'Undelete', 'deletedhistory' ); + + if ( $request === null ) { + global $wgRequest; + $this->mRequest = $wgRequest; + } else { + $this->mRequest = $request; + } + } + + function loadRequest() { global $wgUser; - $this->mAction = $request->getVal( 'action' ); - $this->mTarget = $request->getVal( 'target' ); - $this->mSearchPrefix = $request->getText( 'prefix' ); - $time = $request->getVal( 'timestamp' ); + $this->mAction = $this->mRequest->getVal( 'action' ); + $this->mTarget = $this->mRequest->getVal( 'target' ); + $this->mSearchPrefix = $this->mRequest->getText( 'prefix' ); + $time = $this->mRequest->getVal( 'timestamp' ); $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; - $this->mFile = $request->getVal( 'file' ); - - $posted = $request->wasPosted() && - $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) ); - $this->mRestore = $request->getCheck( 'restore' ) && $posted; - $this->mInvert = $request->getCheck( 'invert' ) && $posted; - $this->mPreview = $request->getCheck( 'preview' ) && $posted; - $this->mDiff = $request->getCheck( 'diff' ); - $this->mComment = $request->getText( 'wpComment' ); - $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); - $this->mToken = $request->getVal( 'token' ); - - if( $par != "" ) { - $this->mTarget = $par; - } + $this->mFile = $this->mRequest->getVal( 'file' ); + + $posted = $this->mRequest->wasPosted() && + $wgUser->matchEditToken( $this->mRequest->getVal( 'wpEditToken' ) ); + $this->mRestore = $this->mRequest->getCheck( 'restore' ) && $posted; + $this->mInvert = $this->mRequest->getCheck( 'invert' ) && $posted; + $this->mPreview = $this->mRequest->getCheck( 'preview' ) && $posted; + $this->mDiff = $this->mRequest->getCheck( 'diff' ); + $this->mComment = $this->mRequest->getText( 'wpComment' ); + $this->mUnsuppress = $this->mRequest->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' ); + $this->mToken = $this->mRequest->getVal( 'token' ); + if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) { $this->mAllowed = true; // user can restore $this->mCanView = true; // user can view content @@ -587,11 +607,7 @@ class UndeleteForm { $this->mTimestamp = ''; $this->mRestore = false; } - if ( $this->mTarget !== "" ) { - $this->mTargetObj = Title::newFromURL( $this->mTarget ); - } else { - $this->mTargetObj = null; - } + if( $this->mRestore || $this->mInvert ) { $timestamps = array(); $this->mFileVersions = array(); @@ -610,14 +626,33 @@ class UndeleteForm { } } - function execute() { + function execute( $par ) { global $wgOut, $wgUser; + + $this->setHeaders(); + if ( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); + return; + } + $this->outputHeader(); + + $this->loadRequest(); + if ( $this->mAllowed ) { $wgOut->setPagetitle( wfMsg( "undeletepage" ) ); } else { $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) ); } + if( $par != '' ) { + $this->mTarget = $par; + } + if ( $this->mTarget !== '' ) { + $this->mTargetObj = Title::newFromURL( $this->mTarget ); + } else { + $this->mTargetObj = null; + } + if( is_null( $this->mTargetObj ) ) { # Not all users can just browse every deleted page from the list if( $wgUser->isAllowed( 'browsearchive' ) ) { @@ -659,7 +694,7 @@ 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' ) ); + $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n", array( 'filedelete-maintenance' ) ); return; } return $this->undelete(); @@ -679,8 +714,8 @@ class UndeleteForm { 'method' => 'get', 'action' => $wgScript ) ) . Xml::fieldset( wfMsg( 'undelete-search-box' ) ) . - Xml::hidden( 'title', - SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) . + Html::hidden( 'title', + $this->getTitle()->getPrefixedDbKey() ) . Xml::inputLabel( wfMsg( 'undelete-search-prefix' ), 'prefix', 'prefix', 20, $this->mSearchPrefix ) . ' ' . @@ -692,7 +727,7 @@ class UndeleteForm { // Generic list of deleted pages private function showList( $result ) { - global $wgLang, $wgContLang, $wgUser, $wgOut; + global $wgLang, $wgUser, $wgOut; if( $result->numRows() == 0 ) { $wgOut->addWikiMsg( 'undelete-no-results' ); @@ -702,9 +737,9 @@ class UndeleteForm { $wgOut->addWikiMsg( 'undeletepagetext', $wgLang->formatNum( $result->numRows() ) ); $sk = $wgUser->getSkin(); - $undelete = SpecialPage::getTitleFor( 'Undelete' ); + $undelete = $this->getTitle(); $wgOut->addHTML( "<ul>\n" ); - while( $row = $result->fetchObject() ) { + foreach ( $result as $row ) { $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); $link = $sk->linkKnown( $undelete, @@ -725,7 +760,7 @@ class UndeleteForm { private function showRevision( $timestamp ) { global $wgLang, $wgUser, $wgOut; - $self = SpecialPage::getTitleFor( 'Undelete' ); + $skin = $wgUser->getSkin(); if(!preg_match("/[0-9]{14}/",$timestamp)) return 0; @@ -740,10 +775,10 @@ class UndeleteForm { if( $rev->isDeleted(Revision::DELETED_TEXT) ) { if( !$rev->userCan(Revision::DELETED_TEXT) ) { - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' ); return; } else { - $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' ); + $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' ); $wgOut->addHTML( '<br />' ); // and we are allowed to see... } @@ -752,7 +787,7 @@ class UndeleteForm { $wgOut->setPageTitle( wfMsg( 'undeletepage' ) ); $link = $skin->linkKnown( - SpecialPage::getTitleFor( 'Undelete', $this->mTargetObj->getPrefixedDBkey() ), + $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ), htmlspecialchars( $this->mTargetObj->getPrefixedText() ) ); @@ -823,7 +858,7 @@ class UndeleteForm { Xml::openElement( 'div' ) . Xml::openElement( 'form', array( 'method' => 'post', - 'action' => $self->getLocalURL( array( 'action' => 'submit' ) ) ) ) . + 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) . Xml::element( 'input', array( 'type' => 'hidden', 'name' => 'target', @@ -851,14 +886,15 @@ class UndeleteForm { /** * Build a diff display between this and the previous either deleted * or non-deleted edit. - * @param Revision $previousRev - * @param Revision $currentRev - * @return string HTML + * + * @param $previousRev Revision + * @param $currentRev Revision + * @return String: HTML */ function showDiff( $previousRev, $currentRev ) { global $wgOut; - $diffEngine = new DifferenceEngine(); + $diffEngine = new DifferenceEngine( $previousRev->getTitle() ); $diffEngine->showDiffStyle(); $wgOut->addHTML( "<div>" . @@ -888,7 +924,7 @@ class UndeleteForm { $isDeleted = !( $rev->getId() && $rev->getTitle() ); if( $isDeleted ) { /// @todo Fixme: $rev->getTitle() is null for deleted revs...? - $targetPage = SpecialPage::getTitleFor( 'Undelete' ); + $targetPage = $this->getTitle(); $targetQuery = array( 'target' => $this->mTargetObj->getPrefixedText(), 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() ) @@ -905,7 +941,7 @@ class UndeleteForm { if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) { $del .= $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops } else { - $query = array( + $query = array( 'type' => 'archive', 'target' => $this->mTargetObj->getPrefixedDbkey(), 'ids' => $rev->getTimestamp() @@ -948,10 +984,10 @@ class UndeleteForm { $this->mTargetObj->getText(), $wgLang->date( $file->getTimestamp() ), $wgLang->time( $file->getTimestamp() ) ); - $wgOut->addHTML( - Xml::openElement( 'form', array( + $wgOut->addHTML( + Xml::openElement( 'form', array( 'method' => 'POST', - 'action' => SpecialPage::getTitleFor( 'Undelete' )->getLocalUrl( + 'action' => $this->getTitle()->getLocalUrl( 'target=' . urlencode( $this->mTarget ) . '&file=' . urlencode( $key ) . '&token=' . urlencode( $wgUser->editToken( $key ) ) ) @@ -979,13 +1015,13 @@ class UndeleteForm { global $IP; require_once( "$IP/includes/StreamFile.php" ); - $repo = RepoGroup::singleton()->getLocalRepo(); + $repo = RepoGroup::singleton()->getLocalRepo(); $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key; wfStreamFile( $path ); } private function showHistory( ) { - global $wgLang, $wgUser, $wgOut; + global $wgUser, $wgOut; $sk = $wgUser->getSkin(); if( $this->mAllowed ) { @@ -994,7 +1030,7 @@ class UndeleteForm { $wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) ); } - $wgOut->wrapWikiMsg( "<div class='mw-undelete-pagetitle'>\n$1</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) ); + $wgOut->wrapWikiMsg( "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) ); $archive = new PageArchive( $this->mTargetObj ); /* @@ -1023,7 +1059,7 @@ class UndeleteForm { # Batch existence check on user and talk pages if( $haveRevisions ) { $batch = new LinkBatch(); - while( $row = $revisions->fetchObject() ) { + foreach ( $revisions as $row ) { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) ); } @@ -1032,7 +1068,7 @@ class UndeleteForm { } if( $haveFiles ) { $batch = new LinkBatch(); - while( $row = $files->fetchObject() ) { + foreach ( $files as $row ) { $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) ); } @@ -1041,8 +1077,7 @@ class UndeleteForm { } if ( $this->mAllowed ) { - $titleObj = SpecialPage::getTitleFor( "Undelete" ); - $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) ); + $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ); # Start the form here $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) ); $wgOut->addHTML( $top ); @@ -1063,7 +1098,7 @@ class UndeleteForm { if( $wgUser->isAllowed( 'suppressrevision' ) ) { $unsuppressBox = "<tr> - <td> </td> + <td> </td> <td class='mw-input'>" . Xml::checkLabel( wfMsg('revdelete-unsuppress'), 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ). @@ -1089,7 +1124,7 @@ class UndeleteForm { "</td> </tr> <tr> - <td> </td> + <td> </td> <td class='mw-submit'>" . Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' . Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . ' ' . @@ -1108,11 +1143,10 @@ class UndeleteForm { if( $haveRevisions ) { # The page's stored (deleted) history: $wgOut->addHTML("<ul>"); - $target = urlencode( $this->mTarget ); $remaining = $revisions->numRows(); $earliestLiveTime = $this->mTargetObj->getEarliestRevTime(); - while( $row = $revisions->fetchObject() ) { + foreach ( $revisions as $row ) { $remaining--; $wgOut->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) ); } @@ -1125,7 +1159,7 @@ class UndeleteForm { if( $haveFiles ) { $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" ); $wgOut->addHTML( "<ul>" ); - while( $row = $files->fetchObject() ) { + foreach ( $files as $row ) { $wgOut->addHTML( $this->formatFileRow( $row, $sk ) ); } $files->free(); @@ -1134,8 +1168,8 @@ class UndeleteForm { if ( $this->mAllowed ) { # Slip in the hidden controls here - $misc = Xml::hidden( 'target', $this->mTarget ); - $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() ); + $misc = Html::hidden( 'target', $this->mTarget ); + $misc .= Html::hidden( 'wpEditToken', $wgUser->editToken() ); $misc .= Xml::closeElement( 'form' ); $wgOut->addHTML( $misc ); } @@ -1146,7 +1180,7 @@ class UndeleteForm { private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) { global $wgUser, $wgLang; - $rev = Revision::newFromArchiveRow( $row, + $rev = Revision::newFromArchiveRow( $row, array( 'page' => $this->mTargetObj->getArticleId() ) ); $stxt = ''; $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); @@ -1166,7 +1200,7 @@ class UndeleteForm { } // Build page & diff links... if( $this->mCanView ) { - $titleObj = SpecialPage::getTitleFor( "Undelete" ); + $titleObj = $this->getTitle(); # Last link if( !$rev->userCan( Revision::DELETED_TEXT ) ) { $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) ); @@ -1228,14 +1262,12 @@ class UndeleteForm { if( $this->mAllowed && $row->fa_storage_key ) { $checkBox = Xml::check( "fileid" . $row->fa_id ); $key = urlencode( $row->fa_storage_key ); - $target = urlencode( $this->mTarget ); - $titleObj = SpecialPage::getTitleFor( "Undelete" ); - $pageLink = $this->getFileLink( $file, $titleObj, $ts, $key, $sk ); + $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key, $sk ); } else { $checkBox = ''; $pageLink = $wgLang->timeanddate( $ts, true ); } - $userLink = $this->getFileUser( $file, $sk ); + $userLink = $this->getFileUser( $file, $sk ); $data = wfMsg( 'widthheight', $wgLang->formatNum( $row->fa_width ), @@ -1294,7 +1326,8 @@ class UndeleteForm { /** * Fetch image view link if it's available to all users - * @return string + * + * @return String: HTML fragment */ function getFileLink( $file, $titleObj, $ts, $key, $sk ) { global $wgLang, $wgUser; @@ -1320,7 +1353,8 @@ class UndeleteForm { /** * Fetch file's user id if it's available to this user - * @return string + * + * @return String: HTML fragment */ function getFileUser( $file, $sk ) { if( !$file->userCan(File::DELETED_USER) ) { @@ -1336,7 +1370,8 @@ class UndeleteForm { /** * Fetch file upload comment if it's available to this user - * @return string + * + * @return String: HTML fragment */ function getFileComment( $file, $sk ) { if( !$file->userCan(File::DELETED_COMMENT) ) { diff --git a/includes/specials/SpecialUnlockdb.php b/includes/specials/SpecialUnlockdb.php index fe38a48a..c71b554b 100644 --- a/includes/specials/SpecialUnlockdb.php +++ b/includes/specials/SpecialUnlockdb.php @@ -1,39 +1,62 @@ <?php /** + * Implements Special:Unlockdb + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** + * Implements Special:Unlockdb * + * @ingroup SpecialPage */ -function wfSpecialUnlockdb() { - global $wgUser, $wgOut, $wgRequest; +class SpecialUnlockdb extends SpecialPage { - if( !$wgUser->isAllowed( 'siteadmin' ) ) { - $wgOut->permissionRequired( 'siteadmin' ); - return; + public function __construct() { + parent::__construct( 'Unlockdb', 'siteadmin' ); } - $action = $wgRequest->getVal( 'action' ); - $f = new DBUnlockForm(); + public function execute( $par ) { + global $wgUser, $wgOut, $wgRequest; + + $this->setHeaders(); + + if( !$wgUser->isAllowed( 'siteadmin' ) ) { + $wgOut->permissionRequired( 'siteadmin' ); + return; + } + + $this->outputHeader(); + + $action = $wgRequest->getVal( 'action' ); - if ( "success" == $action ) { - $f->showSuccess(); - } else if ( "submit" == $action && $wgRequest->wasPosted() && - $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { - $f->doSubmit(); - } else { - $f->showForm( "" ); + if ( $action == 'success' ) { + $this->showSuccess(); + } else if ( $action == 'submit' && $wgRequest->wasPosted() && + $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) { + $this->doSubmit(); + } else { + $this->showForm(); + } } -} -/** - * @ingroup SpecialPage - */ -class DBUnlockForm { - function showForm( $err ) - { + private function showForm( $err = '' ) { global $wgOut, $wgUser; global $wgReadOnlyFile; @@ -42,65 +65,57 @@ class DBUnlockForm { return; } - $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); - $wgOut->addWikiMsg( "unlockdbtext" ); + $wgOut->addWikiMsg( 'unlockdbtext' ); - if ( $err != "" ) { - $wgOut->setSubtitle( wfMsg( "formerror" ) ); + if ( $err != '' ) { + $wgOut->setSubtitle( wfMsg( 'formerror' ) ); $wgOut->addHTML( '<p class="error">' . htmlspecialchars( $err ) . "</p>\n" ); } - $lc = htmlspecialchars( wfMsg( "unlockconfirm" ) ); - $lb = htmlspecialchars( wfMsg( "unlockbtn" ) ); - $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); - $action = $titleObj->escapeLocalURL( "action=submit" ); - $token = htmlspecialchars( $wgUser->editToken() ); - $wgOut->addHTML( <<<HTML - -<form id="unlockdb" method="post" action="{$action}"> -<table border="0"> + $wgOut->addHTML( + Html::openElement( 'form', array( 'id' => 'unlockdb', 'method' => 'POST', + 'action' => $this->getTitle()->getLocalURL( 'action=submit' ) ) ) . " +<table> <tr> - <td align="right"> - <input type="checkbox" name="wpLockConfirm" /> + " . Html::openElement( 'td', array( 'style' => 'text-align:right' ) ) . " + " . Html::input( 'wpLockConfirm', null, 'checkbox' ) . " </td> - <td align="left">{$lc}</td> + " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . + wfMsgHtml( 'unlockconfirm' ) . "</td> </tr> <tr> - <td> </td> - <td align="left"> - <input type="submit" name="wpLock" value="{$lb}" /> + <td> </td> + " . Html::openElement( 'td', array( 'style' => 'text-align:left' ) ) . " + " . Html::input( 'wpLock', wfMsg( 'unlockbtn' ), 'submit' ) . " </td> </tr> -</table> -<input type="hidden" name="wpEditToken" value="{$token}" /> -</form> -HTML -); +</table>\n" . + Html::hidden( 'wpEditToken', $wgUser->editToken() ) . "\n" . + Html::closeElement( 'form' ) + ); } - function doSubmit() { + private function doSubmit() { global $wgOut, $wgRequest, $wgReadOnlyFile; $wpLockConfirm = $wgRequest->getCheck( 'wpLockConfirm' ); - if ( ! $wpLockConfirm ) { - $this->showForm( wfMsg( "locknoconfirm" ) ); + if ( !$wpLockConfirm ) { + $this->showForm( wfMsg( 'locknoconfirm' ) ); return; } - if ( @! unlink( $wgReadOnlyFile ) ) { + if ( @!unlink( $wgReadOnlyFile ) ) { $wgOut->showFileDeleteError( $wgReadOnlyFile ); return; } - $titleObj = SpecialPage::getTitleFor( "Unlockdb" ); - $success = $titleObj->getFullURL( "action=success" ); - $wgOut->redirect( $success ); + + $wgOut->redirect( $this->getTitle()->getFullURL( 'action=success' ) ); } - function showSuccess() { + private function showSuccess() { global $wgOut; - $wgOut->setPagetitle( wfMsg( "unlockdb" ) ); - $wgOut->setSubtitle( wfMsg( "unlockdbsuccesssub" ) ); - $wgOut->addWikiMsg( "unlockdbsuccesstext" ); + $wgOut->setSubtitle( wfMsg( 'unlockdbsuccesssub' ) ); + $wgOut->addWikiMsg( 'unlockdbsuccesstext' ); } } diff --git a/includes/specials/SpecialUnusedcategories.php b/includes/specials/SpecialUnusedcategories.php index fe7d7a17..a20efe09 100644 --- a/includes/specials/SpecialUnusedcategories.php +++ b/includes/specials/SpecialUnusedcategories.php @@ -1,5 +1,22 @@ <?php /** + * Implements Special:Unusedcategories + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ diff --git a/includes/specials/SpecialUnusedimages.php b/includes/specials/SpecialUnusedimages.php index 9d9868f6..091ec3a3 100644 --- a/includes/specials/SpecialUnusedimages.php +++ b/includes/specials/SpecialUnusedimages.php @@ -1,11 +1,29 @@ <?php /** + * Implements Special:Unusedimages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * implements Special:Unusedimages + * A special page that lists unused images + * * @ingroup SpecialPage */ class UnusedimagesPage extends ImageQueryPage { @@ -22,22 +40,11 @@ class UnusedimagesPage extends ImageQueryPage { function isSyndicated() { return false; } function getSQL() { - global $wgCountCategorizedImagesAsUsed, $wgDBtype; + global $wgCountCategorizedImagesAsUsed; + $dbr = wfGetDB( DB_SLAVE ); - 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)'; - } + $epoch = $dbr->unixTimestamp( '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 6ddbab32..68bf95a2 100644 --- a/includes/specials/SpecialUnusedtemplates.php +++ b/includes/specials/SpecialUnusedtemplates.php @@ -1,14 +1,32 @@ <?php /** + * Implements Special:Unusedtemplates + * + * Copyright © 2006 Rob Church + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Rob Church <robchur@gmail.com> */ /** - * implements Special:Unusedtemplates - * @author Rob Church <robchur@gmail.com> - * @copyright © 2006 Rob Church - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + * A special page that lists unused templates + * * @ingroup SpecialPage */ class UnusedtemplatesPage extends QueryPage { diff --git a/includes/specials/SpecialUnwatchedpages.php b/includes/specials/SpecialUnwatchedpages.php index 483afdaa..ecd62cb7 100644 --- a/includes/specials/SpecialUnwatchedpages.php +++ b/includes/specials/SpecialUnwatchedpages.php @@ -1,17 +1,33 @@ <?php /** + * Implements Special:Unwatchedpages + * + * Copyright © 2005 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> */ /** * A special page that displays a list of pages that are not on anyones watchlist. - * Implements Special:Unwatchedpages * * @ingroup SpecialPage - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ class UnwatchedpagesPage extends QueryPage { diff --git a/includes/specials/SpecialUpload.php b/includes/specials/SpecialUpload.php index 68ee8efc..893e4be2 100644 --- a/includes/specials/SpecialUpload.php +++ b/includes/specials/SpecialUpload.php @@ -1,18 +1,38 @@ <?php /** + * Implements Special:Upload + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage * @ingroup Upload - * + */ + +/** * Form for handling uploads and special page. * + * @ingroup SpecialPage + * @ingroup Upload */ - class SpecialUpload extends SpecialPage { /** * Constructor : initialise object * Get data POSTed through the form and assign them to the object - * @param WebRequest $request Data posted. + * @param $request WebRequest : data posted. */ public function __construct( $request = null ) { global $wgRequest; @@ -50,12 +70,11 @@ class SpecialUpload extends SpecialPage { /** Text injection points for hooks not using HTMLForm **/ public $uploadFormTextTop; public $uploadFormTextAfterSummary; - /** * Initialize instance variables from request and create an Upload handler * - * @param WebRequest $request The request to extract variables from + * @param $request WebRequest: the request to extract variables from */ protected function loadRequest( $request ) { global $wgUser; @@ -63,14 +82,15 @@ class SpecialUpload extends SpecialPage { $this->mRequest = $request; $this->mSourceType = $request->getVal( 'wpSourceType', 'file' ); $this->mUpload = UploadBase::createFromRequest( $request ); - $this->mUploadClicked = $request->wasPosted() - && ( $request->getCheck( 'wpUpload' ) + $this->mUploadClicked = $request->wasPosted() + && ( $request->getCheck( 'wpUpload' ) || $request->getCheck( 'wpUploadIgnoreWarning' ) ); // Guess the desired name from the filename if not provided $this->mDesiredDestName = $request->getText( 'wpDestFile' ); - if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) + if( !$this->mDesiredDestName && $request->getFileName( 'wpUploadFile' ) !== null ) { $this->mDesiredDestName = $request->getFileName( 'wpUploadFile' ); + } $this->mComment = $request->getText( 'wpUploadDescription' ); $this->mLicense = $request->getText( 'wpLicense' ); @@ -97,7 +117,7 @@ class SpecialUpload extends SpecialPage { } else { $this->mTokenOk = $wgUser->matchEditToken( $token ); } - + $this->uploadFormTextTop = ''; $this->uploadFormTextAfterSummary = ''; } @@ -107,8 +127,8 @@ class SpecialUpload extends SpecialPage { * Handle permission checking elsewhere in order to be able to show * custom error messages. * - * @param User $user - * @return bool + * @param $user User object + * @return Boolean */ public function userCanExecute( $user ) { return UploadBase::isEnabled() && parent::userCanExecute( $user ); @@ -118,7 +138,7 @@ class SpecialUpload extends SpecialPage { * Special page entry point */ public function execute( $par ) { - global $wgUser, $wgOut, $wgRequest; + global $wgUser, $wgOut; $this->setHeaders(); $this->outputHeader(); @@ -131,13 +151,14 @@ class SpecialUpload extends SpecialPage { # Check permissions global $wgGroupPermissions; - if( !$wgUser->isAllowed( 'upload' ) ) { + $permissionRequired = UploadBase::isAllowed( $wgUser ); + if( $permissionRequired !== true ) { 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' ); + $wgOut->permissionRequired( $permissionRequired ); } return; } @@ -156,69 +177,76 @@ class SpecialUpload extends SpecialPage { # Unsave the temporary file in case this was a cancelled upload if ( $this->mCancelUpload ) { - if ( !$this->unsaveUploadedFile() ) + if ( !$this->unsaveUploadedFile() ) { # Something went wrong, so unsaveUploadedFile showed a warning return; + } } # Process upload or show a form - if ( $this->mTokenOk && !$this->mCancelUpload - && ( $this->mUpload && $this->mUploadClicked ) ) { + if ( + $this->mTokenOk && !$this->mCancelUpload && + ( $this->mUpload && $this->mUploadClicked ) + ) + { $this->processUpload(); } else { # Backwards compatibility hook - if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) - { + if( !wfRunHooks( 'UploadForm:initial', array( &$this ) ) ) { wfDebug( "Hook 'UploadForm:initial' broke output of the upload form" ); return; } + $this->showUploadForm( $this->getUploadForm() ); } # Cleanup - if ( $this->mUpload ) + if ( $this->mUpload ) { $this->mUpload->cleanupTempFile(); + } } /** - * Show the main upload form + * Show the main upload form * - * @param mixed $form An HTMLForm instance or HTML string to show + * @param $form Mixed: an HTMLForm instance or HTML string to show */ 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 ); } - + } /** * Get an UploadForm instance with title and text properly set. * - * @param string $message HTML string to add to the form - * @param string $sessionKey Session key in case this is a stashed upload + * @param $message String: HTML string to add to the form + * @param $sessionKey String: session key in case this is a stashed upload + * @param $hideIgnoreWarning Boolean: whether to hide "ignore warning" check box * @return UploadForm */ protected function getUploadForm( $message = '', $sessionKey = '', $hideIgnoreWarning = false ) { global $wgOut; - + # Initialize form $form = new UploadForm( array( - 'watch' => $this->getWatchCheck(), - 'forreupload' => $this->mForReUpload, + 'watch' => $this->getWatchCheck(), + 'forreupload' => $this->mForReUpload, 'sessionkey' => $sessionKey, 'hideignorewarning' => $hideIgnoreWarning, 'destwarningack' => (bool)$this->mDestWarningAck, - + + 'description' => $this->mComment, 'texttop' => $this->uploadFormTextTop, 'textaftersummary' => $this->uploadFormTextAfterSummary, 'destfile' => $this->mDesiredDestName, @@ -226,26 +254,44 @@ class SpecialUpload extends SpecialPage { $form->setTitle( $this->getTitle() ); # Check the token, but only if necessary - if( !$this->mTokenOk && !$this->mCancelUpload - && ( $this->mUpload && $this->mUploadClicked ) ) { + if( + !$this->mTokenOk && !$this->mCancelUpload && + ( $this->mUpload && $this->mUploadClicked ) + ) + { $form->addPreText( wfMsgExt( 'session_fail_preview', 'parseinline' ) ); } + # Give a notice if the user is uploading a file that has been deleted or moved + # Note that this is independent from the message 'filewasdeleted' that requires JS + $desiredTitleObj = Title::makeTitleSafe( NS_FILE, $this->mDesiredDestName ); + $delNotice = ''; // empty by default + if ( $desiredTitleObj instanceof Title && !$desiredTitleObj->exists() ) { + LogEventsList::showLogExtract( $delNotice, array( 'delete', 'move' ), + $desiredTitleObj->getPrefixedText(), + '', array( 'lim' => 10, + 'conds' => array( "log_action != 'revision'" ), + 'showIfEmpty' => false, + 'msgKey' => array( 'upload-recreate-warning' ) ) + ); + } + $form->addPreText( $delNotice ); + # 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; + + return $form; } @@ -287,13 +333,13 @@ class SpecialUpload extends SpecialPage { * essentially means that UploadBase::VERIFICATION_ERROR and * UploadBase::EMPTY_FILE should not be passed here. * - * @param string $message HTML message to be passed to mainUploadForm + * @param $message String: 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 ); @@ -302,13 +348,11 @@ class SpecialUpload extends SpecialPage { * 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 + * @param $warnings Array + * @return boolean true if warnings were displayed, false if there are no * warnings and the should continue processing like there was no warning */ protected function showUploadWarning( $warnings ) { - global $wgUser; - # 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 @@ -322,28 +366,26 @@ class SpecialUpload extends SpecialPage { $sessionKey = $this->mUpload->stashSession(); - $sk = $wgUser->getSkin(); - $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"; + 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 ); } - $warningHtml .= $msg; + $msg = "\t<li>" . wfMsgExt( $warning, 'parseinline', $args ) . "</li>\n"; + } + $warningHtml .= $msg; } $warningHtml .= "</ul>\n"; $warningHtml .= wfMsgExt( 'uploadwarning-text', 'parse' ); @@ -354,7 +396,7 @@ class SpecialUpload extends SpecialPage { $form->addButton( 'wpCancelUpload', wfMsg( 'reuploaddesc' ) ); $this->showUploadForm( $form ); - + # Indicate that we showed a form return true; } @@ -362,7 +404,7 @@ class SpecialUpload extends SpecialPage { /** * Show the upload form with error message, but do not stash the file. * - * @param string $message + * @param $message HTML string */ protected function showUploadError( $message ) { $message = '<h2>' . wfMsgHtml( 'uploadwarning' ) . "</h2>\n" . @@ -377,25 +419,21 @@ class SpecialUpload extends SpecialPage { protected function processUpload() { global $wgUser, $wgOut; - // Verify permissions - $permErrors = $this->mUpload->verifyPermissions( $wgUser ); - if( $permErrors !== true ) { - $wgOut->showPermissionsErrorPage( $permErrors ); - return; - } - // Fetch the file if required $status = $this->mUpload->fetchFile(); if( !$status->isOK() ) { - $this->showUploadForm( $this->getUploadForm( $wgOut->parse( $status->getWikiText() ) ) ); + $this->showUploadError( $wgOut->parse( $status->getWikiText() ) ); return; } - // Deprecated backwards compatibility hook - if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) - { + if( !wfRunHooks( 'UploadForm:BeforeProcessing', array( &$this ) ) ) { wfDebug( "Hook 'UploadForm:BeforeProcessing' broke processing the file.\n" ); - return array( 'status' => UploadBase::BEFORE_PROCESSING ); + // This code path is deprecated. If you want to break upload processing + // do so by hooking into the appropriate hooks in UploadBase::verifyUpload + // and UploadBase::verifyFile. + // If you use this hook to break uploading, the user will be returned + // an empty form with no error message whatsoever. + return; } @@ -405,6 +443,15 @@ class SpecialUpload extends SpecialPage { $this->processVerificationError( $details ); return; } + + // Verify permissions for this title + $permErrors = $this->mUpload->verifyPermissions( $wgUser ); + if( $permErrors !== true ) { + $code = array_shift( $permErrors[0] ); + $this->showRecoverableUploadError( wfMsgExt( $code, + 'parseinline', $permErrors[0] ) ); + return; + } $this->mLocalFile = $this->mUpload->getLocalFile(); @@ -433,28 +480,41 @@ class SpecialUpload extends SpecialPage { $this->mUploadSuccessful = true; wfRunHooks( 'SpecialUploadComplete', array( &$this ) ); $wgOut->redirect( $this->mLocalFile->getTitle()->getFullURL() ); - } /** * Get the initial image page text based on a comment and optional file status information */ public static function getInitialPageText( $comment = '', $license = '', $copyStatus = '', $source = '' ) { - global $wgUseCopyrightUpload; + global $wgUseCopyrightUpload, $wgForceUIMsgAsContentMsg; + $wgForceUIMsgAsContentMsg = (array) $wgForceUIMsgAsContentMsg; + + /* These messages are transcluded into the actual text of the description page. + * Thus, forcing them as content messages makes the upload to produce an int: template + * instead of hardcoding it there in the uploader language. + */ + foreach( array( 'license-header', 'filedesc', 'filestatus', 'filesource' ) as $msgName ) { + if ( in_array( $msgName, $wgForceUIMsgAsContentMsg ) ) { + $msg[$msgName] = "{{int:$msgName}}"; + } else { + $msg[$msgName] = wfMsgForContent( $msgName ); + } + } + if ( $wgUseCopyrightUpload ) { $licensetxt = ''; if ( $license != '' ) { - $licensetxt = '== ' . wfMsgForContent( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + $licensetxt = '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n"; } - $pageText = '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n" . - '== ' . wfMsgForContent ( 'filestatus' ) . " ==\n" . $copyStatus . "\n" . - "$licensetxt" . - '== ' . wfMsgForContent ( 'filesource' ) . " ==\n" . $source ; + $pageText = '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n" . + '== ' . $msg[ 'filestatus' ] . " ==\n" . $copyStatus . "\n" . + "$licensetxt" . + '== ' . $msg[ 'filesource' ] . " ==\n" . $source; } else { if ( $license != '' ) { - $filedesc = $comment == '' ? '' : '== ' . wfMsgForContent ( 'filedesc' ) . " ==\n" . $comment . "\n"; - $pageText = $filedesc . - '== ' . wfMsgForContent ( 'license-header' ) . " ==\n" . '{{' . $license . '}}' . "\n"; + $filedesc = $comment == '' ? '' : '== ' . $msg[ 'filedesc' ] . " ==\n" . $comment . "\n"; + $pageText = $filedesc . + '== ' . $msg[ 'license-header' ] . " ==\n" . '{{' . $license . '}}' . "\n"; } else { $pageText = $comment; } @@ -495,7 +555,7 @@ class SpecialUpload extends SpecialPage { /** * Provides output to the user for a result of UploadBase::verifyUpload * - * @param array $details Result of UploadBase::verifyUpload + * @param $details Array: result of UploadBase::verifyUpload */ protected function processVerificationError( $details ) { global $wgFileExtensions, $wgLang; @@ -510,10 +570,6 @@ class SpecialUpload extends SpecialPage { $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' ) ); @@ -521,7 +577,10 @@ class SpecialUpload extends SpecialPage { /** Statuses that require reuploading **/ case UploadBase::EMPTY_FILE: - $this->showUploadForm( $this->getUploadForm( wfMsgHtml( 'emptyfile' ) ) ); + $this->showUploadError( wfMsgHtml( 'emptyfile' ) ); + break; + case UploadBase::FILE_TOO_LARGE: + $this->showUploadError( wfMsgHtml( 'largefileserver' ) ); break; case UploadBase::FILETYPE_BADTYPE: $finalExt = $details['finalExt']; @@ -560,15 +619,16 @@ class SpecialUpload extends SpecialPage { /** * Remove a temporarily kept file stashed by saveTempUploadedFile(). - * @access private - * @return success + * + * @return Boolean: success */ protected function unsaveUploadedFile() { global $wgOut; - if ( !( $this->mUpload instanceof UploadFromStash ) ) + if ( !( $this->mUpload instanceof UploadFromStash ) ) { return true; + } $success = $this->mUpload->unsaveUploadedFile(); - if ( ! $success ) { + if ( !$success ) { $wgOut->showFileDeleteError( $this->mUpload->getTempPath() ); return false; } else { @@ -582,14 +642,15 @@ class SpecialUpload extends SpecialPage { * Formats a result of UploadBase::getExistsWarning as HTML * This check is static and can be done pre-upload via AJAX * - * @param array $exists The result of UploadBase::getExistsWarning - * @return string Empty string if there is no warning or an HTML fragment + * @param $exists Array: 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; + global $wgUser; - if ( !$exists ) + if ( !$exists ) { return ''; + } $file = $exists['file']; $filename = $file->getTitle()->getPrefixedText(); @@ -638,8 +699,8 @@ class SpecialUpload extends SpecialPage { /** * Get a list of warnings * - * @param string local filename, e.g. 'file exists', 'non-descriptive filename' - * @return array list of warning messages + * @param $filename String: local filename, e.g. 'file exists', 'non-descriptive filename' + * @return Array: list of warning messages */ public static function ajaxGetExistsWarning( $filename ) { $file = wfFindFile( $filename ); @@ -648,7 +709,7 @@ class SpecialUpload extends SpecialPage { // if there isn't an exact match... $file = wfLocalFile( $filename ); } - $s = ' '; + $s = ' '; if ( $file ) { $exists = UploadBase::getExistsWarning( $file ); $warning = self::getExistsWarning( $exists ); @@ -665,15 +726,15 @@ class SpecialUpload extends SpecialPage { public static function getDupeWarning( $dupes ) { if( $dupes ) { global $wgOut; - $msg = "<gallery>"; + $msg = '<gallery>'; foreach( $dupes as $file ) { $title = $file->getTitle(); $msg .= $title->getPrefixedText() . - "|" . $title->getText() . "\n"; + '|' . $title->getText() . "\n"; } - $msg .= "</gallery>"; - return "<li>" . - wfMsgExt( "file-exists-duplicate", array( "parse" ), count( $dupes ) ) . + $msg .= '</gallery>'; + return '<li>' . + wfMsgExt( 'file-exists-duplicate', array( 'parse' ), count( $dupes ) ) . $wgOut->parse( $msg ) . "</li>\n"; } else { @@ -694,25 +755,30 @@ class UploadForm extends HTMLForm { protected $mDestWarningAck; protected $mDestFile; + protected $mComment; protected $mTextTop; protected $mTextAfterSummary; - + protected $mSourceIds; public function __construct( $options = array() ) { - global $wgLang; - $this->mWatch = !empty( $options['watch'] ); $this->mForReUpload = !empty( $options['forreupload'] ); - $this->mSessionKey = isset( $options['sessionkey'] ) + $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'] : ''; + $this->mComment = isset( $options['description'] ) ? + $options['description'] : ''; + + $this->mTextTop = isset( $options['texttop'] ) + ? $options['texttop'] : ''; + + $this->mTextAfterSummary = isset( $options['textaftersummary'] ) + ? $options['textaftersummary'] : ''; + $sourceDescriptor = $this->getSourceSection(); $descriptor = $sourceDescriptor + $this->getDescriptionSection() @@ -724,34 +790,37 @@ class UploadForm extends HTMLForm { # Set some form properties $this->setSubmitText( wfMsg( 'uploadbtn' ) ); $this->setSubmitName( 'wpUpload' ); + # Used message keys: 'accesskey-upload', 'tooltip-upload' $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'] ) ) + foreach ( $sourceDescriptor as $field ) { + if ( !empty( $field['id'] ) ) { $this->mSourceIds[] = $field['id']; + } } } /** - * Get the descriptor of the fieldset that contains the file source + * Get the descriptor of the fieldset that contains the file source * selection. The section is 'source' - * - * @return array Descriptor array + * + * @return Array: descriptor array */ protected function getSourceSection() { global $wgLang, $wgUser, $wgRequest; + global $wgMaxUploadSize; if ( $this->mSessionKey ) { return array( - 'wpSessionKey' => array( + 'SessionKey' => array( 'type' => 'hidden', 'default' => $this->mSessionKey, ), - 'wpSourceType' => array( + 'SourceType' => array( 'type' => 'hidden', 'default' => 'Stash', ), @@ -771,25 +840,28 @@ class UploadForm extends HTMLForm { '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', + '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( min( + wfShorthandToInteger( + ini_get( 'upload_max_filesize' ) + ), $wgMaxUploadSize + ) ) + ) + ) . ' ' . wfMsgHtml( 'upload_source_file' ), + 'checked' => $selectedSourceType == 'file', ); if ( $canUploadByUrl ) { - global $wgMaxUploadSize; $descriptor['UploadFileURL'] = array( 'class' => 'UploadSourceField', 'section' => 'source', @@ -815,11 +887,10 @@ class UploadForm extends HTMLForm { return $descriptor; } - /** * Get the messages indicating which extensions are preferred and prohibitted. - * - * @return string HTML string containing the message + * + * @return String: HTML string containing the message */ protected function getExtensionsMessage() { # Print a list of allowed file extensions, if so configured. We ignore @@ -827,7 +898,6 @@ class UploadForm extends HTMLForm { global $wgLang, $wgCheckFileExtensions, $wgStrictFileExtensions, $wgFileExtensions, $wgFileBlacklist; - $allowedExtensions = ''; if( $wgCheckFileExtensions ) { if( $wgStrictFileExtensions ) { # Everything not permitted is banned @@ -855,16 +925,11 @@ class UploadForm extends HTMLForm { /** * Get the descriptor of the fieldset that contains the file description * input. The section is 'description' - * - * @return array Descriptor array + * + * @return Array: descriptor array */ protected function getDescriptionSection() { - global $wgUser, $wgOut; - - $cols = intval( $wgUser->getOption( 'cols' ) ); - if( $wgUser->getOption( 'editwidth' ) ) { - $wgOut->addInlineStyle( '#mw-htmlform-description { width: 100%; }' ); - } + global $wgUser; $descriptor = array( 'DestFile' => array( @@ -884,7 +949,8 @@ class UploadForm extends HTMLForm { 'label-message' => $this->mForReUpload ? 'filereuploadsummary' : 'fileuploadsummary', - 'cols' => $cols, + 'default' => $this->mComment, + 'cols' => intval( $wgUser->getOption( 'cols' ) ), 'rows' => 8, ) ); @@ -896,22 +962,25 @@ class UploadForm extends HTMLForm { 'raw' => true, ); } - + $descriptor += array( 'EditTools' => array( 'type' => 'edittools', 'section' => 'description', - ), - 'License' => array( + ) + ); + + if ( $this->mForReUpload ) { + $descriptor['DestFile']['readonly'] = true; + } else { + $descriptor['License'] = array( 'type' => 'select', 'class' => 'Licenses', 'section' => 'description', 'id' => 'wpLicense', 'label-message' => 'license', - ), - ); - if ( $this->mForReUpload ) - $descriptor['DestFile']['readonly'] = true; + ); + } global $wgUseCopyrightUpload; if ( $wgUseCopyrightUpload ) { @@ -933,15 +1002,15 @@ class UploadForm extends HTMLForm { } /** - * Get the descriptor of the fieldset that contains the upload options, + * Get the descriptor of the fieldset that contains the upload options, * such as "watch this file". The section is 'options' - * - * @return array Descriptor array + * + * @return Array: descriptor array */ protected function getOptionsSection() { - global $wgUser, $wgOut; + global $wgUser; - if( $wgUser->isLoggedIn() ) { + if ( $wgUser->isLoggedIn() ) { $descriptor = array( 'Watchthis' => array( 'type' => 'check', @@ -952,7 +1021,7 @@ class UploadForm extends HTMLForm { ) ); } - if( !$this->mHideIgnoreWarning ) { + if ( !$this->mHideIgnoreWarning ) { $descriptor['IgnoreWarning'] = array( 'type' => 'check', 'id' => 'wpIgnoreWarning', @@ -961,14 +1030,14 @@ class UploadForm extends HTMLForm { ); } - $descriptor['wpDestFileWarningAck'] = array( + $descriptor['DestFileWarningAck'] = array( 'type' => 'hidden', 'id' => 'wpDestFileWarningAck', 'default' => $this->mDestWarningAck ? '1' : '', ); if ( $this->mForReUpload ) { - $descriptor['wpForReUpload'] = array( + $descriptor['ForReUpload'] = array( 'type' => 'hidden', 'id' => 'wpForReUpload', 'default' => '1', @@ -976,7 +1045,6 @@ class UploadForm extends HTMLForm { } return $descriptor; - } /** @@ -989,12 +1057,9 @@ class UploadForm extends HTMLForm { /** * Add upload JS to $wgOut - * - * @param bool $autofill Whether or not to autofill the destination - * filename text box */ - protected function addUploadJS( ) { - global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI; + protected function addUploadJS() { + global $wgUseAjax, $wgAjaxUploadDestCheck, $wgAjaxLicensePreview, $wgEnableAPI, $wgStrictFileExtensions; global $wgOut; $useAjaxDestCheck = $wgUseAjax && $wgAjaxUploadDestCheck; @@ -1008,18 +1073,19 @@ class UploadForm extends HTMLForm { // the wpDestFile textbox $this->mDestFile === '', 'wgUploadSourceIds' => $this->mSourceIds, + 'wgStrictFileExtensions' => $wgStrictFileExtensions, + 'wgCapitalizeUploads' => MWNamespace::isCapitalized( NS_FILE ), ); $wgOut->addScript( Skin::makeVariablesScript( $scriptVars ) ); - + // For <charinsert> support - $wgOut->addScriptFile( 'edit.js' ); - $wgOut->addScriptFile( 'upload.js' ); + $wgOut->addModules( array( 'mediawiki.legacy.edit', 'mediawiki.legacy.upload' ) ); } /** * Empty function; submission is handled elsewhere. - * + * * @return bool false */ function trySubmit() { @@ -1032,9 +1098,9 @@ class UploadForm extends HTMLForm { * A form field that contains a radio box in the label */ class UploadSourceField extends HTMLTextField { - function getLabelHtml() { + function getLabelHtml( $cellAttributes = array() ) { $id = "wpSourceType{$this->mParams['upload-type']}"; - $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel ); + $label = Html::rawElement( 'label', array( 'for' => $id ), $this->mLabel ); if ( !empty( $this->mParams['radio'] ) ) { $attribs = array( @@ -1043,13 +1109,15 @@ class UploadSourceField extends HTMLTextField { 'id' => $id, 'value' => $this->mParams['upload-type'], ); - if ( !empty( $this->mParams['checked'] ) ) + if ( !empty( $this->mParams['checked'] ) ) { $attribs['checked'] = 'checked'; + } $label .= Html::element( 'input', $attribs ); } - return Html::rawElement( 'td', array( 'class' => 'mw-label' ), $label ); + return Html::rawElement( 'td', array( 'class' => 'mw-label' ) + $cellAttributes, $label ); } + function getSize() { return isset( $this->mParams['size'] ) ? $this->mParams['size'] diff --git a/includes/specials/SpecialUploadStash.php b/includes/specials/SpecialUploadStash.php new file mode 100644 index 00000000..48a41a5e --- /dev/null +++ b/includes/specials/SpecialUploadStash.php @@ -0,0 +1,394 @@ +<?php +/** + * Implements Special:UploadStash + * + * Web access for files temporarily stored by UploadStash. + * + * For example -- files that were uploaded with the UploadWizard extension are stored temporarily + * before committing them to the db. But we want to see their thumbnails and get other information + * about them. + * + * Since this is based on the user's session, in effect this creates a private temporary file area. + * However, the URLs for the files cannot be shared. + * + * @file + * @ingroup SpecialPage + * @ingroup Upload + */ + +class SpecialUploadStash extends UnlistedSpecialPage { + // UploadStash + private $stash; + + // is the edit request authorized? boolean + private $isEditAuthorized; + + // did the user request us to clear the stash? boolean + private $requestedClear; + + // Since we are directly writing the file to STDOUT, + // we should not be reading in really big files and serving them out. + // + // We also don't want people using this as a file drop, even if they + // share credentials. + // + // This service is really for thumbnails and other such previews while + // uploading. + const MAX_SERVE_BYTES = 262144; // 256K + + public function __construct( $request = null ) { + global $wgRequest; + + parent::__construct( 'UploadStash', 'upload' ); + try { + $this->stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash(); + } catch ( UploadStashNotAvailableException $e ) { + return null; + } + + $this->loadRequest( is_null( $request ) ? $wgRequest : $request ); + } + + /** + * Execute page -- can output a file directly or show a listing of them. + * + * @param $subPage String: subpage, e.g. in http://example.com/wiki/Special:UploadStash/foo.jpg, the "foo.jpg" part + * @return Boolean: success + */ + public function execute( $subPage ) { + global $wgUser; + + if ( !$this->userCanExecute( $wgUser ) ) { + $this->displayRestrictionError(); + return; + } + + if ( !isset( $subPage ) || $subPage === '' ) { + return $this->showUploads(); + } + + return $this->showUpload( $subPage ); + } + + + /** + * If file available in stash, cats it out to the client as a simple HTTP response. + * n.b. Most sanity checking done in UploadStashLocalFile, so this is straightforward. + * + * @param $key String: the key of a particular requested file + */ + public function showUpload( $key ) { + global $wgOut; + + // prevent callers from doing standard HTML output -- we'll take it from here + $wgOut->disable(); + + try { + $params = $this->parseKey( $key ); + if ( $params['type'] === 'thumb' ) { + return $this->outputThumbFromStash( $params['file'], $params['params'] ); + } else { + return $this->outputLocalFile( $params['file'] ); + } + } catch( UploadStashFileNotFoundException $e ) { + $code = 404; + $message = $e->getMessage(); + } catch( UploadStashZeroLengthFileException $e ) { + $code = 500; + $message = $e->getMessage(); + } catch( UploadStashBadPathException $e ) { + $code = 500; + $message = $e->getMessage(); + } catch( SpecialUploadStashTooLargeException $e ) { + $code = 500; + $message = 'Cannot serve a file larger than ' . self::MAX_SERVE_BYTES . ' bytes. ' . $e->getMessage(); + } catch( Exception $e ) { + $code = 500; + $message = $e->getMessage(); + } + + wfHttpError( $code, OutputPage::getStatusMessage( $code ), $message ); + return false; + } + + /** + * Parse the key passed to the SpecialPage. Returns an array containing + * the associated file object, the type ('file' or 'thumb') and if + * application the transform parameters + * + * @param string $key + * @return array + */ + private function parseKey( $key ) { + $type = strtok( $key, '/' ); + + if ( $type !== 'file' && $type !== 'thumb' ) { + throw new UploadStashBadPathException( "Unknown type '$type'" ); + } + $fileName = strtok( '/' ); + $thumbPart = strtok( '/' ); + $file = $this->stash->getFile( $fileName ); + if ( $type === 'thumb' ) { + $srcNamePos = strrpos( $thumbPart, $fileName ); + if ( $srcNamePos === false || $srcNamePos < 1 ) { + throw new UploadStashBadPathException( 'Unrecognized thumb name' ); + } + $paramString = substr( $thumbPart, 0, $srcNamePos - 1 ); + + $handler = $file->getHandler(); + $params = $handler->parseParamString( $paramString ); + return array( 'file' => $file, 'type' => $type, 'params' => $params ); + } + + return array( 'file' => $file, 'type' => $type ); + } + + + + + /** + * Get a thumbnail for file, either generated locally or remotely, and stream it out + * @param String $key: key for the file in the stash + * @param int $width: width of desired thumbnail + * @return boolean success + */ + private function outputThumbFromStash( $file, $params ) { + + // this global, if it exists, points to a "scaler", as you might find in the Wikimedia Foundation cluster. See outputRemoteScaledThumb() + // this is part of our horrible NFS-based system, we create a file on a mount point here, but fetch the scaled file from somewhere else that + // happens to share it over NFS + global $wgUploadStashScalerBaseUrl; + + $flags = 0; + if ( $wgUploadStashScalerBaseUrl ) { + $this->outputRemoteScaledThumb( $file, $params, $flags ); + } else { + $this->outputLocallyScaledThumb( $file, $params, $flags ); + } + + + } + + + /** + * Scale a file (probably with a locally installed imagemagick, or similar) and output it to STDOUT. + * @param $file: File object + * @param $params: scaling parameters ( e.g. array( width => '50' ) ); + * @param $flags: scaling flags ( see File:: constants ) + * @throws MWException + * @return boolean success + */ + private function outputLocallyScaledThumb( $file, $params, $flags ) { + + // n.b. this is stupid, we insist on re-transforming the file every time we are invoked. We rely + // on HTTP caching to ensure this doesn't happen. + + $flags |= File::RENDER_NOW; + + $thumbnailImage = $file->transform( $params, $flags ); + if ( !$thumbnailImage ) { + throw new MWException( 'Could not obtain thumbnail' ); + } + + // we should have just generated it locally + if ( ! $thumbnailImage->getPath() ) { + throw new UploadStashFileNotFoundException( "no local path for scaled item" ); + } + + // now we should construct a File, so we can get mime and other such info in a standard way + // n.b. mimetype may be different from original (ogx original -> jpeg thumb) + $thumbFile = new UnregisteredLocalFile( false, $this->stash->repo, $thumbnailImage->getPath(), false ); + if ( ! $thumbFile ) { + throw new UploadStashFileNotFoundException( "couldn't create local file object for thumbnail" ); + } + + return $this->outputLocalFile( $thumbFile ); + + } + + /** + * Scale a file with a remote "scaler", as exists on the Wikimedia Foundation cluster, and output it to STDOUT. + * Note: unlike the usual thumbnail process, the web client never sees the cluster URL; we do the whole HTTP transaction to the scaler ourselves + * and cat the results out. + * Note: We rely on NFS to have propagated the file contents to the scaler. However, we do not rely on the thumbnail being created in NFS and then + * propagated back to our filesystem. Instead we take the results of the HTTP request instead. + * Note: no caching is being done here, although we are instructing the client to cache it forever. + * @param $file: File object + * @param $params: scaling parameters ( e.g. array( width => '50' ) ); + * @param $flags: scaling flags ( see File:: constants ) + * @throws MWException + * @return boolean success + */ + private function outputRemoteScaledThumb( $file, $params, $flags ) { + + // this global probably looks something like 'http://upload.wikimedia.org/wikipedia/test/thumb/temp' + // do not use trailing slash + global $wgUploadStashScalerBaseUrl; + + $scalerThumbName = $file->getParamThumbName( $file->name, $params ); + $scalerThumbUrl = $wgUploadStashScalerBaseUrl . '/' . $file->getRel() . '/' . $scalerThumbName; + + // make a curl call to the scaler to create a thumbnail + $httpOptions = array( + 'method' => 'GET', + 'timeout' => 'default' + ); + $req = MWHttpRequest::factory( $scalerThumbUrl, $httpOptions ); + $status = $req->execute(); + if ( ! $status->isOK() ) { + $errors = $status->getErrorsArray(); + throw new MWException( "Fetching thumbnail failed: " . join( ", ", $errors ) ); + } + $contentType = $req->getResponseHeader( "content-type" ); + if ( ! $contentType ) { + throw new MWException( "Missing content-type header" ); + } + return $this->outputContents( $req->getContent(), $contentType ); + } + + /** + * Output HTTP response for file + * Side effect: writes HTTP response to STDOUT. + * XXX could use wfStreamfile (in includes/Streamfile.php), but for consistency with outputContents() doing it this way. + * XXX is mimeType really enough, or do we need encoding for full Content-Type header? + * + * @param $file File object with a local path (e.g. UnregisteredLocalFile, LocalFile. Oddly these don't share an ancestor!) + */ + private function outputLocalFile( $file ) { + if ( $file->getSize() > self::MAX_SERVE_BYTES ) { + throw new SpecialUploadStashTooLargeException(); + } + self::outputFileHeaders( $file->getMimeType(), $file->getSize() ); + readfile( $file->getPath() ); + return true; + } + + /** + * Output HTTP response of raw content + * Side effect: writes HTTP response to STDOUT. + * @param String $content: content + * @param String $mimeType: mime type + */ + private function outputContents( $content, $contentType ) { + $size = strlen( $content ); + if ( $size > self::MAX_SERVE_BYTES ) { + throw new SpecialUploadStashTooLargeException(); + } + self::outputFileHeaders( $contentType, $size ); + print $content; + return true; + } + + /** + * Output headers for streaming + * XXX unsure about encoding as binary; if we received from HTTP perhaps we should use that encoding, concatted with semicolon to mimeType as it usually is. + * Side effect: preps PHP to write headers to STDOUT. + * @param String $contentType : string suitable for content-type header + * @param String $size: length in bytes + */ + private static function outputFileHeaders( $contentType, $size ) { + header( "Content-Type: $contentType", true ); + header( 'Content-Transfer-Encoding: binary', true ); + header( 'Expires: Sun, 17-Jan-2038 19:14:07 GMT', true ); + header( "Content-Length: $size", true ); + } + + + /** + * Initialize authorization & actions to take, from the request + * @param $request: WebRequest + */ + private function loadRequest( $request ) { + global $wgUser; + if ( $request->wasPosted() ) { + + $token = $request->getVal( 'wpEditToken' ); + $this->isEditAuthorized = $wgUser->matchEditToken( $token ); + + $this->requestedClear = $request->getBool( 'clear' ); + + } + } + + /** + * Static callback for the HTMLForm in showUploads, to process + * Note the stash has to be recreated since this is being called in a static context. + * This works, because there really is only one stash per logged-in user, despite appearances. + * + * @return Status + */ + public static function tryClearStashedUploads( $formData ) { + wfDebug( __METHOD__ . " form data : " . print_r( $formData, 1 ) ); + if ( isset( $formData['clear'] ) and $formData['clear'] ) { + $stash = new UploadStash(); + wfDebug( "stash has: " . print_r( $stash->listFiles(), 1 ) ); + if ( ! $stash->clear() ) { + return Status::newFatal( 'uploadstash-errclear' ); + } + } + return Status::newGood(); + } + + /** + * Default action when we don't have a subpage -- just show links to the uploads we have, + * Also show a button to clear stashed files + * @param Status : $status - the result of processRequest + */ + private function showUploads( $status = null ) { + global $wgOut; + if ( $status === null ) { + $status = Status::newGood(); + } + + // sets the title, etc. + $this->setHeaders(); + $this->outputHeader(); + + + // create the form, which will also be used to execute a callback to process incoming form data + // this design is extremely dubious, but supposedly HTMLForm is our standard now? + + $form = new HTMLForm( array( + 'Clear' => array( + 'type' => 'hidden', + 'default' => true, + 'name' => 'clear', + ) + ), 'clearStashedUploads' ); + $form->setSubmitCallback( array( __CLASS__, 'tryClearStashedUploads' ) ); + $form->setTitle( $this->getTitle() ); + $form->addHiddenField( 'clear', true, array( 'type' => 'boolean' ) ); + $form->setSubmitText( wfMsg( 'uploadstash-clear' ) ); + + $form->prepareForm(); + $formResult = $form->tryAuthorizedSubmit(); + + + // show the files + form, if there are any, or just say there are none + $refreshHtml = Html::element( 'a', array( 'href' => $this->getTitle()->getLocalURL() ), wfMsg( 'uploadstash-refresh' ) ); + $files = $this->stash->listFiles(); + if ( count( $files ) ) { + sort( $files ); + $fileListItemsHtml = ''; + foreach ( $files as $file ) { + $fileListItemsHtml .= Html::rawElement( 'li', array(), + Html::element( 'a', array( 'href' => + $this->getTitle( "file/$file" )->getLocalURL() ), $file ) + ); + } + $wgOut->addHtml( Html::rawElement( 'ul', array(), $fileListItemsHtml ) ); + $form->displayForm( $formResult ); + $wgOut->addHtml( Html::rawElement( 'p', array(), $refreshHtml ) ); + } else { + $wgOut->addHtml( Html::rawElement( 'p', array(), + Html::element( 'span', array(), wfMsg( 'uploadstash-nofiles' ) ) + . ' ' + . $refreshHtml + ) ); + } + + return true; + } +} + +class SpecialUploadStashTooLargeException extends MWException {}; diff --git a/includes/specials/SpecialUserlogin.php b/includes/specials/SpecialUserlogin.php index 8b8d0e9e..ccace79d 100644 --- a/includes/specials/SpecialUserlogin.php +++ b/includes/specials/SpecialUserlogin.php @@ -1,11 +1,28 @@ <?php /** + * Implements Special:UserLogin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * constructor + * Constructor */ function wfSpecialUserlogin( $par = '' ) { global $wgRequest; @@ -18,7 +35,8 @@ function wfSpecialUserlogin( $par = '' ) { } /** - * implements Special:Login + * Implements Special:UserLogin + * * @ingroup SpecialPage */ class LoginForm { @@ -41,7 +59,7 @@ class LoginForm { var $mName, $mPassword, $mRetype, $mReturnTo, $mCookieCheck, $mPosted; var $mAction, $mCreateaccount, $mCreateaccountMail, $mMailmypassword; var $mLoginattempt, $mRemember, $mEmail, $mDomain, $mLanguage; - var $mSkipCookieCheck, $mReturnToQuery, $mToken; + var $mSkipCookieCheck, $mReturnToQuery, $mToken, $mStickHTTPS; private $mExtUser = null; @@ -50,7 +68,7 @@ class LoginForm { * @param $request WebRequest: a WebRequest object passed by reference * @param $par String: subpage parameter */ - function LoginForm( &$request, $par = '' ) { + function __construct( &$request, $par = '' ) { global $wgAuth, $wgHiddenPrefs, $wgEnableEmail, $wgRedirectOnLogin; $this->mType = ( $par == 'signup' ) ? $par : $request->getText( 'type' ); # Check for [[Special:Userlogin/signup]] @@ -58,6 +76,7 @@ class LoginForm { $this->mPassword = $request->getText( 'wpPassword' ); $this->mRetype = $request->getText( 'wpRetype' ); $this->mDomain = $request->getText( 'wpDomain' ); + $this->mReason = $request->getText( 'wpReason' ); $this->mReturnTo = $request->getVal( 'returnto' ); $this->mReturnToQuery = $request->getVal( 'returntoquery' ); $this->mCookieCheck = $request->getVal( 'wpCookieCheck' ); @@ -70,9 +89,10 @@ class LoginForm { $this->mLoginattempt = $request->getCheck( 'wpLoginattempt' ); $this->mAction = $request->getVal( 'action' ); $this->mRemember = $request->getCheck( 'wpRemember' ); + $this->mStickHTTPS = $request->getCheck( 'wpStickHTTPS' ); $this->mLanguage = $request->getText( 'uselang' ); $this->mSkipCookieCheck = $request->getCheck( 'wpSkipCookieCheck' ); - $this->mToken = ($this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' ); + $this->mToken = ( $this->mType == 'signup' ) ? $request->getVal( 'wpCreateaccountToken' ) : $request->getVal( 'wpLoginToken' ); if ( $wgRedirectOnLogin ) { $this->mReturnTo = $wgRedirectOnLogin; @@ -107,14 +127,14 @@ class LoginForm { if ( !is_null( $this->mCookieCheck ) ) { $this->onCookieRedirectCheck( $this->mCookieCheck ); return; - } else if( $this->mPosted ) { + } elseif( $this->mPosted ) { if( $this->mCreateaccount ) { return $this->addNewAccount(); - } else if ( $this->mCreateaccountMail ) { + } elseif ( $this->mCreateaccountMail ) { return $this->addNewAccountMailPassword(); - } else if ( $this->mMailmypassword ) { + } elseif ( $this->mMailmypassword ) { return $this->mailPassword(); - } else if ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { + } elseif ( ( 'submitlogin' == $this->mAction ) || $this->mLoginattempt ) { return $this->processLogin(); } } @@ -128,13 +148,13 @@ class LoginForm { global $wgOut; if ( $this->mEmail == '' ) { - $this->mainLoginForm( wfMsg( 'noemail', htmlspecialchars( $this->mName ) ) ); + $this->mainLoginForm( wfMsgExt( 'noemailcreate', array( 'parsemag', 'escape' ) ) ); return; } $u = $this->addNewaccountInternal(); - if ($u == null) { + if ( $u == null ) { return; } @@ -144,47 +164,46 @@ class LoginForm { $result = $this->mailPasswordInternal( $u, false, 'createaccount-title', 'createaccount-text' ); wfRunHooks( 'AddNewAccount', array( $u, true ) ); - $u->addNewUserLogEntry(); + $u->addNewUserLogEntry( true, $this->mReason ); $wgOut->setPageTitle( wfMsg( 'accmailtitle' ) ); $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); - if( WikiError::isError( $result ) ) { - $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); + if( !$result->isGood() ) { + $this->mainLoginForm( wfMsg( 'mailerror', $result->getWikiText() ) ); } else { $wgOut->addWikiMsg( 'accmailtext', $u->getName(), $u->getEmail() ); $wgOut->returnToMain( false ); } - $u = 0; } - /** * @private */ function addNewAccount() { - global $wgUser, $wgEmailAuthentication; + global $wgUser, $wgEmailAuthentication, $wgOut; # 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 # smart (and sensible) and save that language as the user's preference global $wgLoginLanguageSelector; - if( $wgLoginLanguageSelector && $this->mLanguage ) + if( $wgLoginLanguageSelector && $this->mLanguage ) { $u->setOption( 'language', $this->mLanguage ); + } # Send out an email authentication message if needed if( $wgEmailAuthentication && User::isValidEmailAddr( $u->getEmail() ) ) { - global $wgOut; - $error = $u->sendConfirmationMail(); - if( WikiError::isError( $error ) ) { - $wgOut->addWikiMsg( 'confirmemail_sendfailed', $error->getMessage() ); - } else { + $status = $u->sendConfirmationMail(); + if( $status->isGood() ) { $wgOut->addWikiMsg( 'confirmemail_oncreate' ); + } else { + $wgOut->addWikiText( $status->getWikiText( 'confirmemail_sendfailed' ) ); } } @@ -206,7 +225,6 @@ class LoginForm { } } else { # Confirm that the account was created - global $wgOut; $self = SpecialPage::getTitleFor( 'Userlogin' ); $wgOut->setPageTitle( wfMsgHtml( 'accountcreated' ) ); $wgOut->setArticleRelated( false ); @@ -214,7 +232,7 @@ class LoginForm { $wgOut->addHTML( wfMsgWikiHtml( 'accountcreatedtext', $u->getName() ) ); $wgOut->returnToMain( false, $self ); wfRunHooks( 'AddNewAccount', array( $u, false ) ); - $u->addNewUserLogEntry(); + $u->addNewUserLogEntry( false, $this->mReason ); return true; } } @@ -254,16 +272,16 @@ class LoginForm { # Request forgery checks. if ( !self::getCreateaccountToken() ) { self::setCreateaccountToken(); - $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); + $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) ); return false; } - + # The user didn't pass a createaccount token if ( !$this->mToken ) { $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); return false; } - + # Validate the createaccount token if ( $this->mToken !== self::getCreateaccountToken() ) { $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); @@ -272,7 +290,7 @@ class LoginForm { # Check permissions if ( !$wgUser->isAllowed( 'createaccount' ) ) { - $this->userNotPrivilegedMessage(); + $wgOut->permissionRequired( 'createaccount' ); return false; } elseif ( $wgUser->isBlockedFromCreateAccount() ) { $this->userBlockedMessage(); @@ -359,7 +377,7 @@ class LoginForm { return false; } - self::clearCreateaccountToken(); + self::clearCreateaccountToken(); return $this->initUser( $u, false ); } @@ -413,16 +431,17 @@ class LoginForm { * creation. */ public function authenticateUserData() { - global $wgUser, $wgAuth; + global $wgUser, $wgAuth, $wgMemc; + 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 // but wrong-token attempts do. - + // If the user doesn't have a login token yet, set one. if ( !self::getLoginToken() ) { self::setLoginToken(); @@ -432,7 +451,7 @@ class LoginForm { if ( !$this->mToken ) { return self::NEED_TOKEN; } - + global $wgPasswordAttemptThrottle; $throttleCount = 0; @@ -440,18 +459,17 @@ class LoginForm { $throttleKey = wfMemcKey( 'password-throttle', wfGetIP(), md5( $this->mName ) ); $count = $wgPasswordAttemptThrottle['count']; $period = $wgPasswordAttemptThrottle['seconds']; - - global $wgMemc; + $throttleCount = $wgMemc->get( $throttleKey ); if ( !$throttleCount ) { $wgMemc->add( $throttleKey, 1, $period ); // start counter - } else if ( $throttleCount < $count ) { - $wgMemc->incr($throttleKey); - } else if ( $throttleCount >= $count ) { + } elseif ( $throttleCount < $count ) { + $wgMemc->incr( $throttleKey ); + } elseif ( $throttleCount >= $count ) { return self::THROTTLED; } } - + // Validate the login token if ( $this->mToken !== self::getLoginToken() ) { return self::WRONG_TOKEN; @@ -464,7 +482,7 @@ class LoginForm { // for user existence using User::newFromName($name)->getId() below // will effectively be using stale data. if ( $wgUser->getName() === $this->mName ) { - wfDebug( __METHOD__.": already logged in as {$this->mName}\n" ); + wfDebug( __METHOD__ . ": already logged in as {$this->mName}\n" ); return self::SUCCESS; } @@ -505,7 +523,7 @@ class LoginForm { } global $wgBlockDisablesLogin; - if (!$u->checkPassword( $this->mPassword )) { + if ( !$u->checkPassword( $this->mPassword ) ) { if( $u->checkTemporaryPassword( $this->mPassword ) ) { // The e-mailed temporary password should not be used for actu- // al logins; that's a very sloppy habit, and insecure if an @@ -533,7 +551,7 @@ 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 @@ -543,8 +561,8 @@ class LoginForm { $wgUser = $u; // Please reset throttle for successful logins, thanks! - if($throttleCount) { - $wgMemc->delete($throttleKey); + if( $throttleCount ) { + $wgMemc->delete( $throttleKey ); } if ( $isAutoCreated ) { @@ -567,7 +585,7 @@ class LoginForm { global $wgAuth, $wgUser, $wgAutocreatePolicy; if ( $wgUser->isBlockedFromCreateAccount() ) { - wfDebug( __METHOD__.": user is blocked from account creation\n" ); + wfDebug( __METHOD__ . ": user is blocked from account creation\n" ); return self::CREATE_BLOCKED; } @@ -591,22 +609,22 @@ class LoginForm { return self::NOT_EXISTS; } if ( !$wgAuth->userExists( $user->getName() ) ) { - wfDebug( __METHOD__.": user does not exist\n" ); + 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" ); + wfDebug( __METHOD__ . ": \$wgAuth->authenticate() returned false, aborting\n" ); return self::WRONG_PLUGIN_PASS; } } - wfDebug( __METHOD__.": creating account\n" ); - $user = $this->initUser( $user, true ); + wfDebug( __METHOD__ . ": creating account\n" ); + $this->initUser( $user, true ); return self::SUCCESS; } function processLogin() { - global $wgUser, $wgAuth; + global $wgUser; switch ( $this->authenticateUserData() ) { case self::SUCCESS: @@ -637,8 +655,10 @@ class LoginForm { return $this->cookieRedirectCheck( 'login' ); } break; - + case self::NEED_TOKEN: + $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) ); + break; case self::WRONG_TOKEN: $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); break; @@ -650,7 +670,7 @@ class LoginForm { $this->mainLoginForm( wfMsg( 'wrongpassword' ) ); break; case self::NOT_EXISTS: - if( $wgUser->isAllowed( 'createaccount' ) ){ + if( $wgUser->isAllowed( 'createaccount' ) ) { $this->mainLoginForm( wfMsgWikiHtml( 'nosuchuser', htmlspecialchars( $this->mName ) ) ); } else { $this->mainLoginForm( wfMsg( 'nosuchusershort', htmlspecialchars( $this->mName ) ) ); @@ -676,7 +696,7 @@ class LoginForm { array( 'parsemag', 'escape' ), $this->mName ) ); break; default: - throw new MWException( "Unhandled case value" ); + throw new MWException( 'Unhandled case value' ); } } @@ -692,27 +712,27 @@ class LoginForm { */ function mailPassword() { global $wgUser, $wgOut, $wgAuth; - + if ( wfReadOnly() ) { $wgOut->readOnlyPage(); return false; } - + if( !$wgAuth->allowPasswordChange() ) { $this->mainLoginForm( wfMsg( 'resetpass_forbidden' ) ); return; } - # Check against blocked IPs so blocked users can't flood admins + # Check against blocked IPs so blocked users can't flood admins # with password resets if( $wgUser->isBlocked() ) { $this->mainLoginForm( wfMsg( 'blocked-mailpassword' ) ); return; } - + # Check for hooks $error = null; - if ( ! wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$error ) ) ) { + if ( !wfRunHooks( 'UserLoginMailPassword', array( $this->mName, &$error ) ) ) { $this->mainLoginForm( $error ); return; } @@ -729,7 +749,7 @@ class LoginForm { $this->mainLoginForm( wfMsg( 'sessionfailure' ) ); return; } - + # Check against the rate limiter if( $wgUser->pingLimiter( 'mailpassword' ) ) { $wgOut->rateLimited(); @@ -767,11 +787,11 @@ class LoginForm { } $result = $this->mailPasswordInternal( $u, true, 'passwordremindertitle', 'passwordremindertext' ); - if( WikiError::isError( $result ) ) { - $this->mainLoginForm( wfMsg( 'mailerror', $result->getMessage() ) ); - } else { + if( $result->isGood() ) { $this->mainLoginForm( wfMsg( 'passwordsent', $u->getName() ), 'success' ); self::clearLoginToken(); + } else { + $this->mainLoginForm( $result->getWikiText( 'mailerror' ) ); } } @@ -781,21 +801,21 @@ class LoginForm { * @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 + * @return Status object * @private */ function mailPasswordInternal( $u, $throttle = true, $emailTitle = 'passwordremindertitle', $emailText = 'passwordremindertext' ) { global $wgServer, $wgScript, $wgUser, $wgNewPasswordExpiry; if ( $u->getEmail() == '' ) { - return new WikiError( wfMsg( 'noemail', $u->getName() ) ); + return Status::newFatal( 'noemail', $u->getName() ); } $ip = wfGetIP(); if( !$ip ) { - return new WikiError( wfMsg( 'badipaddress' ) ); + return Status::newFatal( 'badipaddress' ); } - - wfRunHooks( 'User::mailPasswordInternal', array(&$wgUser, &$ip, &$u) ); + + wfRunHooks( 'User::mailPasswordInternal', array( &$wgUser, &$ip, &$u ) ); $np = $u->randomPassword(); $u->setNewpassword( $np, $throttle ); @@ -824,7 +844,7 @@ class LoginForm { # Run any hooks; display injected HTML if any, else redirect $injected_html = ''; - wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html)); + wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) ); if( $injected_html !== '' ) { $this->displaySuccessfulLogin( 'loginsuccess', $injected_html ); @@ -833,7 +853,12 @@ class LoginForm { if ( !$titleObj instanceof Title ) { $titleObj = Title::newMainPage(); } - $wgOut->redirect( $titleObj->getFullURL( $this->mReturnToQuery ) ); + $redirectUrl = $titleObj->getFullURL( $this->mReturnToQuery ); + global $wgSecureLogin; + if( $wgSecureLogin && !$this->mStickHTTPS ) { + $redirectUrl = preg_replace( '/^https:/', 'http:', $redirectUrl ); + } + $wgOut->redirect( $redirectUrl ); } } @@ -844,11 +869,10 @@ class LoginForm { * @private */ function successfulCreation() { - global $wgUser, $wgOut; - + global $wgUser; # Run any hooks; display injected HTML $injected_html = ''; - wfRunHooks('UserLoginComplete', array(&$wgUser, &$injected_html)); + wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) ); $this->displaySuccessfulLogin( 'welcomecreation', $injected_html ); } @@ -873,22 +897,6 @@ class LoginForm { } /** */ - function userNotPrivilegedMessage($errors) { - global $wgOut; - - $wgOut->setPageTitle( wfMsg( 'permissionserrors' ) ); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - $wgOut->setArticleRelated( false ); - - $wgOut->addWikitext( $wgOut->formatPermissionsErrorMessage( $errors, 'createaccount' ) ); - // Stuff that might want to be added at the end. For example, instruc- - // tions if blocked. - $wgOut->addWikiMsg( 'cantcreateaccount-nonblock-text' ); - - $wgOut->returnToMain( false ); - } - - /** */ function userBlockedMessage() { global $wgOut, $wgUser; @@ -920,14 +928,15 @@ class LoginForm { */ function mainLoginForm( $msg, $msgtype = 'error' ) { global $wgUser, $wgOut, $wgHiddenPrefs, $wgEnableEmail; - global $wgCookiePrefix, $wgLoginLanguageSelector; + global $wgRequest, $wgLoginLanguageSelector; global $wgAuth, $wgEmailConfirmToEdit, $wgCookieExpiration; - + global $wgSecureLogin; + $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - + if ( $this->mType == 'signup' ) { - // Block signup here if in readonly. Keeps user from - // going through the process (filling out data, etc) + // Block signup here if in readonly. Keeps user from + // going through the process (filling out data, etc) // and being informed later. if ( wfReadOnly() ) { $wgOut->readOnlyPage(); @@ -945,12 +954,10 @@ class LoginForm { if ( $wgUser->isLoggedIn() ) { $this->mName = $wgUser->getName(); } else { - $this->mName = isset( $_COOKIE[$wgCookiePrefix.'UserName'] ) ? $_COOKIE[$wgCookiePrefix.'UserName'] : null; + $this->mName = $wgRequest->getCookie( 'UserName' ); } } - $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); - if ( $this->mType == 'signup' ) { $template = new UsercreateTemplate(); $q = 'action=submitlogin&type=signup'; @@ -965,26 +972,29 @@ class LoginForm { if ( !empty( $this->mReturnTo ) ) { $returnto = '&returnto=' . wfUrlencode( $this->mReturnTo ); - if ( !empty( $this->mReturnToQuery ) ) + if ( !empty( $this->mReturnToQuery ) ) { $returnto .= '&returntoquery=' . wfUrlencode( $this->mReturnToQuery ); + } $q .= $returnto; $linkq .= $returnto; } # Pass any language selection on to the mode switch link - if( $wgLoginLanguageSelector && $this->mLanguage ) + if( $wgLoginLanguageSelector && $this->mLanguage ) { $linkq .= '&uselang=' . $this->mLanguage; + } - $link = '<a href="' . htmlspecialchars ( $titleObj->getLocalUrl( $linkq ) ) . '">'; + $link = '<a href="' . htmlspecialchars ( $titleObj->getLocalURL( $linkq ) ) . '">'; $link .= wfMsgHtml( $linkmsg . 'link' ); # Calling either 'gotaccountlink' or 'nologinlink' $link .= '</a>'; # Don't show a "create account" link if the user can't - if( $this->showCreateOrLoginLink( $wgUser ) ) - $template->set( 'link', wfMsgWikiHtml( $linkmsg, $link ) ); - else + if( $this->showCreateOrLoginLink( $wgUser ) ) { + $template->set( 'link', wfMsgExt( $linkmsg, array( 'parseinline', 'replaceafter' ), $link ) ); + } else { $template->set( 'link', '' ); + } $template->set( 'header', '' ); $template->set( 'name', $this->mName ); @@ -993,8 +1003,9 @@ class LoginForm { $template->set( 'email', $this->mEmail ); $template->set( 'realname', $this->mRealName ); $template->set( 'domain', $this->mDomain ); + $template->set( 'reason', $this->mReason ); - $template->set( 'action', $titleObj->getLocalUrl( $q ) ); + $template->set( 'action', $titleObj->getLocalURL( $q ) ); $template->set( 'message', $msg ); $template->set( 'messagetype', $msgtype ); $template->set( 'createemail', $wgEnableEmail && $wgUser->isLoggedIn() ); @@ -1003,7 +1014,10 @@ class LoginForm { $template->set( 'emailrequired', $wgEmailConfirmToEdit ); $template->set( 'canreset', $wgAuth->allowPasswordChange() ); $template->set( 'canremember', ( $wgCookieExpiration > 0 ) ); - $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) or $this->mRemember ); + $template->set( 'usereason', $wgUser->isLoggedIn() ); + $template->set( 'remember', $wgUser->getOption( 'rememberpassword' ) || $this->mRemember ); + $template->set( 'cansecurelogin', ( $wgSecureLogin === true ) ); + $template->set( 'stickHTTPS', $this->mStickHTTPS ); if ( $this->mType == 'signup' ) { if ( !self::getCreateaccountToken() ) { @@ -1016,7 +1030,7 @@ class LoginForm { } $template->set( 'token', self::getLoginToken() ); } - + # Prepare language selection links as needed if( $wgLoginLanguageSelector ) { $template->set( 'languages', $this->makeLanguageSelector() ); @@ -1032,7 +1046,7 @@ class LoginForm { wfRunHooks( 'UserLoginForm', array( &$template ) ); } - //Changes the title depending on permissions for creating account + // Changes the title depending on permissions for creating account if ( $wgUser->isAllowed( 'createaccount' ) ) { $wgOut->setPageTitle( wfMsg( 'userlogin' ) ); } else { @@ -1041,7 +1055,7 @@ class LoginForm { $wgOut->setRobotPolicy( 'noindex,nofollow' ); $wgOut->setArticleRelated( false ); - $wgOut->disallowUserJs(); // just in case... + $wgOut->disallowUserJs(); // just in case... $wgOut->addTemplate( $template ); } @@ -1071,7 +1085,7 @@ class LoginForm { global $wgDisableCookieCheck, $wgRequest; return $wgDisableCookieCheck ? true : $wgRequest->checkSessionCookie(); } - + /** * Get the login token from the current session */ @@ -1079,7 +1093,7 @@ class LoginForm { global $wgRequest; return $wgRequest->getSessionData( 'wsLoginToken' ); } - + /** * Randomly generate a new login token and attach it to the current session */ @@ -1089,7 +1103,7 @@ class LoginForm { // because the latter reuses $_SESSION['wsEditToken'] $wgRequest->setSessionData( 'wsLoginToken', User::generateToken() ); } - + /** * Remove any login token attached to the current session */ @@ -1105,7 +1119,7 @@ class LoginForm { global $wgRequest; return $wgRequest->getSessionData( 'wsCreateaccountToken' ); } - + /** * Randomly generate a new createaccount token and attach it to the current session */ @@ -1113,7 +1127,7 @@ class LoginForm { global $wgRequest; $wgRequest->setSessionData( 'wsCreateaccountToken', User::generateToken() ); } - + /** * Remove any createaccount token attached to the current session */ @@ -1130,7 +1144,9 @@ class LoginForm { $titleObj = SpecialPage::getTitleFor( 'Userlogin' ); $query = array( 'wpCookieCheck' => $type ); - if ( $this->mReturnTo ) $query['returnto'] = $this->mReturnTo; + if ( $this->mReturnTo ) { + $query['returnto'] = $this->mReturnTo; + } $check = $titleObj->getFullURL( $query ); return $wgOut->redirect( $check ); @@ -1143,7 +1159,7 @@ class LoginForm { if ( !$this->hasSessionCookie() ) { if ( $type == 'new' ) { return $this->mainLoginForm( wfMsgExt( 'nocookiesnew', array( 'parseinline' ) ) ); - } else if ( $type == 'login' ) { + } elseif ( $type == 'login' ) { return $this->mainLoginForm( wfMsgExt( 'nocookieslogin', array( 'parseinline' ) ) ); } else { # shouldn't happen @@ -1177,7 +1193,7 @@ class LoginForm { foreach( $langs as $lang ) { $lang = trim( $lang, '* ' ); $parts = explode( '|', $lang ); - if (count($parts) >= 2) { + if ( count( $parts ) >= 2 ) { $links[] = $this->makeLanguageSelectorLink( $parts[0], $parts[1] ); } } @@ -1198,10 +1214,12 @@ class LoginForm { global $wgUser; $self = SpecialPage::getTitleFor( 'Userlogin' ); $attr = array( 'uselang' => $lang ); - if( $this->mType == 'signup' ) + if( $this->mType == 'signup' ) { $attr['type'] = 'signup'; - if( $this->mReturnTo ) + } + if( $this->mReturnTo ) { $attr['returnto'] = $this->mReturnTo; + } $skin = $wgUser->getSkin(); return $skin->linkKnown( $self, diff --git a/includes/specials/SpecialUserlogout.php b/includes/specials/SpecialUserlogout.php index e23df612..39b5b284 100644 --- a/includes/specials/SpecialUserlogout.php +++ b/includes/specials/SpecialUserlogout.php @@ -1,33 +1,63 @@ <?php /** + * Implements Special:Upload + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * constructor + * Implements Special:Userlogout + * + * @ingroup SpecialPage */ -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'], '&' ) !== false ) { - wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" ); - wfHttpError( 400, wfMsg( 'loginerror' ), wfMsg( 'suspicious-userlogout' ) ); - return; +class SpecialUserlogout extends UnlistedSpecialPage { + + function __construct() { + parent::__construct( 'Userlogout' ); } - - $oldName = $wgUser->getName(); - $wgUser->logout(); - $wgOut->setRobotPolicy( 'noindex,nofollow' ); - // Hook. - $injected_html = ''; - wfRunHooks( 'UserLogoutComplete', array(&$wgUser, &$injected_html, $oldName) ); + function execute( $par ) { + 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'], '&' ) !== false ) { + wfDebug( "Special:Userlogout request {$_SERVER['REQUEST_URI']} looks suspicious, denying.\n" ); + wfHttpError( 400, wfMsg( 'loginerror' ), wfMsg( 'suspicious-userlogout' ) ); + return; + } + + $this->setHeaders(); + $this->outputHeader(); - $wgOut->addHTML( wfMsgExt( 'logouttext', array( 'parse' ) ) . $injected_html ); - $wgOut->returnToMain(); + $oldName = $wgUser->getName(); + $wgUser->logout(); + + $wgOut->addWikiMsg( 'logouttext' ); + + // Hook. + $injected_html = ''; + wfRunHooks( 'UserLogoutComplete', array( &$wgUser, &$injected_html, $oldName ) ); + $wgOut->addHTML( $injected_html ); + + $wgOut->returnToMain(); + } } diff --git a/includes/specials/SpecialUserrights.php b/includes/specials/SpecialUserrights.php index 36caf9a6..6ea8668b 100644 --- a/includes/specials/SpecialUserrights.php +++ b/includes/specials/SpecialUserrights.php @@ -1,13 +1,29 @@ <?php /** - * Special page to allow managing user group membership + * Implements Special:Userrights + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file * @ingroup SpecialPage */ /** - * A class to manage user levels rights. + * Special page to allow managing user group membership + * * @ingroup SpecialPage */ class UserrightsPage extends SpecialPage { @@ -32,10 +48,10 @@ class UserrightsPage extends SpecialPage { public function userCanChangeRights( $user, $checkIfSelf = true ) { $available = $this->changeableGroups(); return !empty( $available['add'] ) - or !empty( $available['remove'] ) - or ( ( $this->isself || !$checkIfSelf ) and + || !empty( $available['remove'] ) + || ( ( $this->isself || !$checkIfSelf ) && ( !empty( $available['add-self'] ) - or !empty( $available['remove-self'] ) ) ); + || !empty( $available['remove-self'] ) ) ); } /** @@ -49,7 +65,7 @@ class UserrightsPage extends SpecialPage { // any groups, it's a bit silly to give them the user search prompt. global $wgUser, $wgRequest, $wgOut; - if( $par ) { + if( $par !== null ) { $this->mTarget = $par; } else { $this->mTarget = $wgRequest->getVal( 'user' ); @@ -67,7 +83,7 @@ class UserrightsPage extends SpecialPage { $available = $this->changeableGroups(); - if ( !$this->mTarget ) { + if ( $this->mTarget === null ) { /* * If the user specified no target, and they can only * edit their own groups, automatically set them as the @@ -77,8 +93,9 @@ class UserrightsPage extends SpecialPage { $this->mTarget = $wgUser->getName(); } - if ( $this->mTarget == $wgUser->getName() ) + if ( User::getCanonicalName( $this->mTarget ) == $wgUser->getName() ) { $this->isself = true; + } if( !$this->userCanChangeRights( $wgUser, true ) ) { // fixme... there may be intermediate groups we can mention. @@ -99,8 +116,9 @@ class UserrightsPage extends SpecialPage { $this->setHeaders(); // show the general form - if ( count( $available['add'] ) || count( $available['remove'] ) ) + if ( count( $available['add'] ) || count( $available['remove'] ) ) { $this->switchForm(); + } if( $wgRequest->wasPosted() ) { // save settings @@ -121,7 +139,7 @@ class UserrightsPage extends SpecialPage { } // show some more forms - if( $this->mTarget ) { + if( $this->mTarget !== null ) { $this->editUserGroupsForm( $this->mTarget ); } } @@ -139,12 +157,14 @@ class UserrightsPage extends SpecialPage { * @return null */ function saveUserGroups( $username, $reason = '' ) { - global $wgRequest, $wgUser, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + global $wgRequest, $wgOut; - $user = $this->fetchUser( $username ); - if( $user instanceof WikiErrorMsg ) { - $wgOut->addWikiMsgArray( $user->getMessageKey(), $user->getMessageArgs() ); + $status = $this->fetchUser( $username ); + if( !$status->isOK() ) { + $wgOut->addWikiText( $status->getWikiText() ); return; + } else { + $user = $status->value; } $allgroups = $this->getAllGroups(); @@ -162,7 +182,7 @@ class UserrightsPage extends SpecialPage { $removegroup[] = $group; } } - + $this->doSaveUserGroups( $user, $addgroup, $removegroup, $reason ); } @@ -247,10 +267,12 @@ class UserrightsPage extends SpecialPage { function editUserGroupsForm( $username ) { global $wgOut; - $user = $this->fetchUser( $username ); - if( $user instanceof WikiErrorMsg ) { - $wgOut->addWikiMsgArray( $user->getMessageKey(), $user->getMessageArgs() ); + $status = $this->fetchUser( $username ); + if( !$status->isOK() ) { + $wgOut->addWikiText( $status->getWikiText() ); return; + } else { + $user = $status->value; } $groups = $user->getGroups(); @@ -267,7 +289,7 @@ 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 WikiErrorMsg + * @return Status object */ public function fetchUser( $username ) { global $wgUser, $wgUserrightsInterwikiDelimiter; @@ -278,21 +300,21 @@ class UserrightsPage extends SpecialPage { $database = ''; } else { list( $name, $database ) = array_map( 'trim', $parts ); - + if( $database == wfWikiID() ) { $database = ''; } else { if( !$wgUser->isAllowed( 'userrights-interwiki' ) ) { - return new WikiErrorMsg( 'userrights-no-interwiki' ); + return Status::newFatal( 'userrights-no-interwiki' ); } if( !UserRightsProxy::validDatabase( $database ) ) { - return new WikiErrorMsg( 'userrights-nodatabase', $database ); + return Status::newFatal( 'userrights-nodatabase', $database ); } } } - if( $name == '' ) { - return new WikiErrorMsg( 'nouserspecified' ); + if( $name === '' ) { + return Status::newFatal( 'nouserspecified' ); } if( $name{0} == '#' ) { @@ -307,13 +329,13 @@ class UserrightsPage extends SpecialPage { } if( !$name ) { - return new WikiErrorMsg( 'noname' ); + return Status::newFatal( 'noname' ); } } else { $name = User::getCanonicalName( $name ); - if( !$name ) { + if( $name === false ) { // invalid name - return new WikiErrorMsg( 'nosuchusershort', $username ); + return Status::newFatal( 'nosuchusershort', $username ); } } @@ -324,10 +346,10 @@ class UserrightsPage extends SpecialPage { } if( !$user || $user->isAnon() ) { - return new WikiErrorMsg( 'nosuchusershort', $username ); + return Status::newFatal( 'nosuchusershort', $username ); } - return $user; + return Status::newGood( $user ); } function makeGroupNameList( $ids ) { @@ -352,14 +374,13 @@ class UserrightsPage extends SpecialPage { function switchForm() { global $wgOut, $wgScript; $wgOut->addHTML( - Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) . - Xml::hidden( 'title', $this->getTitle()->getPrefixedText() ) . - Xml::openElement( 'fieldset' ) . - Xml::element( 'legend', array(), wfMsg( 'userrights-lookup-user' ) ) . - Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, $this->mTarget ) . ' ' . + Html::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript, 'name' => 'uluser', 'id' => 'mw-userrights-form1' ) ) . + Html::hidden( 'title', $this->getTitle()->getPrefixedText() ) . + Xml::fieldset( wfMsg( 'userrights-lookup-user' ) ) . + Xml::inputLabel( wfMsg( 'userrights-user-editname' ), 'user', 'username', 30, str_replace( '_', ' ', $this->mTarget ) ) . ' ' . Xml::submitButton( wfMsg( 'editusergroup' ) ) . - Xml::closeElement( 'fieldset' ) . - Xml::closeElement( 'form' ) . "\n" + Html::closeElement( 'fieldset' ) . + Html::closeElement( 'form' ) . "\n" ); } @@ -396,8 +417,9 @@ class UserrightsPage extends SpecialPage { global $wgOut, $wgUser, $wgLang; $list = array(); - foreach( $groups as $group ) + foreach( $groups as $group ) { $list[] = self::buildGroupLink( $group ); + } $autolist = array(); if ( $user instanceof User ) { @@ -417,8 +439,8 @@ class UserrightsPage extends SpecialPage { } $wgOut->addHTML( Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->getTitle()->getLocalURL(), 'name' => 'editGroup', 'id' => 'mw-userrights-form2' ) ) . - Xml::hidden( 'user', $this->mTarget ) . - Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) . + Html::hidden( 'user', $this->mTarget ) . + Html::hidden( 'wpEditToken', $wgUser->editToken( $this->mTarget ) ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', array(), wfMsg( 'userrights-editusergroup' ) ) . wfMsgExt( 'editinguser', array( 'parse' ), wfEscapeWikiText( $user->getName() ) ) . @@ -437,7 +459,8 @@ class UserrightsPage extends SpecialPage { <tr> <td></td> <td class='mw-submit'>" . - Xml::submitButton( wfMsg( 'saveusergroups' ), array( 'name' => 'saveusergroups', 'accesskey' => 's' ) ) . + Xml::submitButton( wfMsg( 'saveusergroups' ), + array( 'name' => 'saveusergroups' ) + $wgUser->getSkin()->tooltipAndAccessKeyAttribs( 'userrights-set' ) ) . "</td> </tr>" . Xml::closeElement( 'table' ) . "\n" . @@ -511,7 +534,7 @@ class UserrightsPage extends SpecialPage { foreach( $columns as $name => $column ) { if( $column === array() ) continue; - $ret .= xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) ); + $ret .= Xml::element( 'th', null, wfMsg( 'userrights-' . $name . '-col' ) ); } $ret.= "</tr>\n<tr>\n"; foreach( $columns as $column ) { @@ -522,7 +545,7 @@ class UserrightsPage extends SpecialPage { $attr = $checkbox['disabled'] ? array( 'disabled' => 'disabled' ) : array(); if ( $checkbox['irreversible'] ) { - $text = htmlspecialchars( wfMsg( 'userrights-irreversible-marker', + $text = htmlspecialchars( wfMsg( 'userrights-irreversible-marker', User::getGroupMember( $group ) ) ); } else { $text = htmlspecialchars( User::getGroupMember( $group ) ); diff --git a/includes/specials/SpecialVersion.php b/includes/specials/SpecialVersion.php index ebc50bab..101823db 100644 --- a/includes/specials/SpecialVersion.php +++ b/includes/specials/SpecialVersion.php @@ -1,35 +1,56 @@ <?php +/** + * Implements Special:Version + * + * Copyright © 2005 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup SpecialPage + */ /** * Give information about the version of MediaWiki, PHP, the DB and extensions * * @ingroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @copyright Copyright © 2005, Ævar Arnfjörð Bjarmason - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ class SpecialVersion extends SpecialPage { - private $firstExtOpened = true; + + protected $firstExtOpened = false; - static $viewvcUrls = array( + protected static $extensionTypes = false; + + protected static $viewvcUrls = array( 'svn+ssh://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', 'http://svn.wikimedia.org/svnroot/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', # Doesn't work at the time of writing but maybe some day: 'https://svn.wikimedia.org/viewvc/mediawiki' => 'http://svn.wikimedia.org/viewvc/mediawiki', ); - function __construct(){ + public function __construct(){ parent::__construct( 'Version' ); } /** * main() */ - function execute( $par ) { - global $wgOut, $wgMessageCache, $wgSpecialVersionShowHooks, $wgContLang; - $wgMessageCache->loadAllMessages(); - + public function execute( $par ) { + global $wgOut, $wgSpecialVersionShowHooks, $wgContLang; + $this->setHeaders(); $this->outputHeader(); $wgOut->allowClickjacking(); @@ -37,74 +58,76 @@ class SpecialVersion extends SpecialPage { $wgOut->addHTML( Xml::openElement( 'div', array( 'dir' => $wgContLang->getDir() ) ) ); $text = - $this->MediaWikiCredits() . + $this->getMediaWikiCredits() . $this->softwareInformation() . - $this->extensionCredits(); + $this->getExtensionCredits(); if ( $wgSpecialVersionShowHooks ) { - $text .= $this->wgHooks(); + $text .= $this->getWgHooks(); } + $wgOut->addWikiText( $text ); $wgOut->addHTML( $this->IPInfo() ); $wgOut->addHTML( '</div>' ); } - /**#@+ - * @private - */ - /** - * @return wiki text showing the license information + * Returns wiki text showing the license information. + * + * @return string */ - static function MediaWikiCredits() { - global $wgContLang; - + private static function getMediaWikiCredits() { $ret = Xml::element( 'h2', array( 'id' => 'mw-version-license' ), wfMsg( 'version-license' ) ); // This text is always left-to-right. - $ret .= '<div dir="ltr">'; + $ret .= '<div>'; $ret .= "__NOTOC__ - This wiki is powered by '''[http://www.mediawiki.org/ MediaWiki]''', - 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, 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 - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - MediaWiki 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 [{{SERVER}}{{SCRIPTPATH}}/COPYING 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 - or [http://www.gnu.org/licenses/old-licenses/gpl-2.0.html read it online]. - "; + " . self::getCopyrightAndAuthorList() . "\n + " . wfMsg( 'version-license-info' ); $ret .= '</div>'; return str_replace( "\t\t", '', $ret ) . "\n"; } /** - * @return wiki text showing the third party software versions (apache, php, mysql). + * Get the "MediaWiki is copyright 2001-20xx by lots of cool guys" text + * + * @return String + */ + public static function getCopyrightAndAuthorList() { + global $wgLang; + + $authorList = array( + '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', 'Andrew Garrett', 'Raimond Spekking', + 'Alexandre Emsenhuber', 'Siebrand Mazeland', 'Chad Horohoe', + 'Roan Kattouw', 'Trevor Parscal', 'Bryan Tong Minh', 'Sam Reed', + 'Victor Vasiliev', 'Rotem Liss', 'Platonides', 'Ashar Voultoiz', + wfMsg( 'version-poweredby-others' ) + ); + + return wfMsg( 'version-poweredby-credits', date( 'Y' ), + $wgLang->listToText( $authorList ) ); + } + + /** + * Returns wiki text showing the third party software versions (apache, php, mysql). + * + * @return string */ static function softwareInformation() { $dbr = wfGetDB( DB_SLAVE ); // 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 + // 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(); + $software[$dbr->getSoftwareLink()] = $dbr->getServerInfo(); - // Allow a hook to add/remove items + // Allow a hook to add/remove items. wfRunHooks( 'SoftwareInfo', array( &$software ) ); $out = Xml::element( 'h2', array( 'id' => 'mw-version-software' ), wfMsg( 'version-software' ) ) . @@ -113,17 +136,19 @@ class SpecialVersion extends SpecialPage { <th>" . wfMsg( 'version-software-product' ) . "</th> <th>" . wfMsg( 'version-software-version' ) . "</th> </tr>\n"; + foreach( $software as $name => $version ) { $out .= "<tr> <td>" . $name . "</td> <td>" . $version . "</td> </tr>\n"; - } + } + return $out . Xml::closeElement( 'table' ); } /** - * Return a string of the MediaWiki version with SVN revision if available + * Return a string of the MediaWiki version with SVN revision if available. * * @return mixed */ @@ -151,20 +176,23 @@ class SpecialVersion extends SpecialPage { /** * Return a wikitext-formatted string of the MediaWiki version with a link to - * the SVN revision if available + * the SVN revision if available. * * @return mixed */ public static function getVersionLinked() { global $wgVersion, $IP; wfProfileIn( __METHOD__ ); + $info = self::getSvnInfo( $IP ); - if ( isset( $info['checkout-rev'] ) ) { + + 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 { @@ -173,54 +201,115 @@ class SpecialVersion extends SpecialPage { } else { $version = $wgVersion; } + wfProfileOut( __METHOD__ ); return $version; } - /** Generate wikitext showing extensions name, URL, author and description */ - function extensionCredits() { + /** + * Returns an array with the base extension types. + * Type is stored as array key, the message as array value. + * + * TODO: ideally this would return all extension types, including + * those added by SpecialVersionExtensionTypes. This is not possible + * since this hook is passing along $this though. + * + * @since 1.17 + * + * @return array + */ + public static function getExtensionTypes() { + if ( self::$extensionTypes === false ) { + self::$extensionTypes = array( + 'specialpage' => wfMsg( 'version-specialpages' ), + 'parserhook' => wfMsg( 'version-parserhooks' ), + 'variable' => wfMsg( 'version-variables' ), + 'media' => wfMsg( 'version-mediahandlers' ), + 'skin' => wfMsg( 'version-skins' ), + 'other' => wfMsg( 'version-other' ), + ); + + wfRunHooks( 'ExtensionTypes', array( &self::$extensionTypes ) ); + } + + return self::$extensionTypes; + } + + /** + * Returns the internationalized name for an extension type. + * + * @since 1.17 + * + * @param $type String + * + * @return string + */ + public static function getExtensionTypeName( $type ) { + $types = self::getExtensionTypes(); + return isset( $types[$type] ) ? $types[$type] : $types['other']; + } + + /** + * Generate wikitext showing extensions name, URL, author and description. + * + * @return String: Wikitext + */ + function getExtensionCredits() { global $wgExtensionCredits, $wgExtensionFunctions, $wgParser, $wgSkinExtensionFunctions; - if ( ! count( $wgExtensionCredits ) && ! count( $wgExtensionFunctions ) && ! count( $wgSkinExtensionFunctions ) ) + if ( !count( $wgExtensionCredits ) && !count( $wgExtensionFunctions ) && !count( $wgSkinExtensionFunctions ) ) { return ''; + } - $extensionTypes = array( - 'specialpage' => wfMsg( 'version-specialpages' ), - 'parserhook' => wfMsg( 'version-parserhooks' ), - 'variable' => wfMsg( 'version-variables' ), - 'media' => wfMsg( 'version-mediahandlers' ), - 'other' => wfMsg( 'version-other' ), - ); + $extensionTypes = self::getExtensionTypes(); + + /** + * @deprecated as of 1.17, use hook ExtensionTypes instead. + */ wfRunHooks( 'SpecialVersionExtensionTypes', array( &$this, &$extensionTypes ) ); $out = Xml::element( 'h2', array( 'id' => 'mw-version-ext' ), wfMsg( 'version-extensions' ) ) . 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, 'credits-' . $type ); - - usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); - - foreach ( $wgExtensionCredits[$type] as $extension ) { - $out .= $this->formatCredits( $extension ); - } + // Make sure the 'other' type is set to an array. + if ( !array_key_exists( 'other', $wgExtensionCredits ) ) { + $wgExtensionCredits['other'] = array(); + } + + // Find all extensions that do not have a valid type and give them the type 'other'. + foreach ( $wgExtensionCredits as $type => $extensions ) { + if ( !array_key_exists( $type, $extensionTypes ) ) { + $wgExtensionCredits['other'] = array_merge( $wgExtensionCredits['other'], $extensions ); + } + } + + // Loop through the extension categories to display their extensions in the list. + foreach ( $extensionTypes as $type => $message ) { + if ( $type != 'other' ) { + $out .= $this->getExtensionCategory( $type, $message ); } } + + // We want the 'other' type to be last in the list. + $out .= $this->getExtensionCategory( 'other', $extensionTypes['other'] ); if ( count( $wgExtensionFunctions ) ) { $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 = $wgParser->getTags(); + $cnt = count( $tags ); + + if ( $cnt ) { + for ( $i = 0; $i < $cnt; ++$i ) { $tags[$i] = "<{$tags[$i]}>"; + } $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() ) ) { + if( count( $fhooks = $wgParser->getFunctionHooks() ) ) { $out .= $this->openExtType( wfMsg( 'version-parser-function-hooks' ), 'parser-function-hooks' ); $out .= '<tr><td colspan="4">' . $this->listToText( $fhooks ) . "</td></tr>\n"; } @@ -229,11 +318,43 @@ class SpecialVersion extends SpecialPage { $out .= $this->openExtType( wfMsg( 'version-skin-extension-functions' ), 'skin-extension-functions' ); $out .= '<tr><td colspan="4">' . $this->listToText( $wgSkinExtensionFunctions ) . "</td></tr>\n"; } + $out .= Xml::closeElement( 'table' ); + return $out; } + + /** + * Creates and returns the HTML for a single extension category. + * + * @since 1.17 + * + * @param $type String + * @param $message String + * + * @return string + */ + protected function getExtensionCategory( $type, $message ) { + global $wgExtensionCredits; + + $out = ''; + + if ( array_key_exists( $type, $wgExtensionCredits ) && count( $wgExtensionCredits[$type] ) > 0 ) { + $out .= $this->openExtType( $message, 'credits-' . $type ); + + usort( $wgExtensionCredits[$type], array( $this, 'compare' ) ); + + foreach ( $wgExtensionCredits[$type] as $extension ) { + $out .= $this->getCreditsForExtension( $extension ); + } + } - /** Callback to sort extensions by type */ + return $out; + } + + /** + * Callback to sort extensions by type. + */ function compare( $a, $b ) { global $wgLang; if( $a['name'] === $b['name'] ) { @@ -245,8 +366,16 @@ class SpecialVersion extends SpecialPage { } } - function formatCredits( $extension ) { + /** + * Creates and formats the creidts for a single extension and returns this. + * + * @param $extension Array + * + * @return string + */ + function getCreditsForExtension( array $extension ) { $name = isset( $extension['name'] ) ? $extension['name'] : '[no name]'; + if ( isset( $extension['path'] ) ) { $svnInfo = self::getSvnInfo( dirname($extension['path']) ); $directoryRev = isset( $svnInfo['directory-rev'] ) ? $svnInfo['directory-rev'] : null; @@ -258,12 +387,13 @@ class SpecialVersion extends SpecialPage { $viewvcUrl = null; } - # Make main link (or just the name if there is no URL) + # 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'] ) . @@ -272,7 +402,7 @@ class SpecialVersion extends SpecialPage { $versionText = ''; } - # Make subversion text/link + # Make subversion text/link. if ( $checkoutRev ) { $svnText = wfMsg( 'version-svn-revision', $directoryRev, $checkoutRev ); $svnText = isset( $viewvcUrl ) ? "[$viewvcUrl $svnText]" : $svnText; @@ -280,11 +410,13 @@ class SpecialVersion extends SpecialPage { $svnText = false; } - # Make description text + # Make description text. $description = isset ( $extension['description'] ) ? $extension['description'] : ''; + if( isset ( $extension['descriptionmsg'] ) ) { - # Look for a localized description + # 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 @@ -306,17 +438,21 @@ class SpecialVersion extends SpecialPage { $extNameVer = "<tr> <td colspan=\"2\"><em>$mainLink $versionText</em></td>"; } + $author = isset ( $extension['author'] ) ? $extension['author'] : array(); $extDescAuthor = "<td>$description</td> <td>" . $this->listToText( (array)$author, false ) . "</td> </tr>\n"; + return $extNameVer . $extDescAuthor; } /** - * @return string + * Generate wikitext showing hooks in $wgHooks. + * + * @return String: wikitext */ - function wgHooks() { + private function getWgHooks() { global $wgHooks; if ( count( $wgHooks ) ) { @@ -346,32 +482,39 @@ class SpecialVersion extends SpecialPage { $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"; + $out .= '<tr class="sv-space">' . Html::element( 'td', $opt ) . "</tr>\n"; } - $this->firstExtOpened = false; - - if( $name ) + $this->firstExtOpened = true; + + if( $name ) { $opt['id'] = "sv-$name"; + } $out .= "<tr>" . Xml::element( 'th', $opt, $text ) . "</tr>\n"; + return $out; } /** - * @return string + * Get information about client's IP address. + * + * @return String: HTML fragment */ - function IPInfo() { + private function IPInfo() { $ip = str_replace( '--', ' - ', htmlspecialchars( wfGetIP() ) ); return "<!-- visited from $ip -->\n" . "<span style='display:none'>visited from $ip</span>"; } /** - * @param array $list - * @param bool $sort - * @return string + * Convert an array of items into a list for display. + * + * @param $list Array of elements to display + * @param $sort Boolean: whether to sort the items in $list + * + * @return String */ function listToText( $list, $sort = true ) { $cnt = count( $list ); @@ -391,9 +534,12 @@ class SpecialVersion extends SpecialPage { } /** - * @param mixed $list Will convert an array to string if given and return - * the paramater unaltered otherwise - * @return mixed + * Convert an array or object to a string for display. + * + * @param $list Mixed: will convert an array to string if given and return + * the paramater unaltered otherwise + * + * @return Mixed */ static function arrayToString( $list ) { if( is_array( $list ) && count( $list ) == 1 ) @@ -462,41 +608,47 @@ class SpecialVersion extends SpecialPage { } } } + return false; } - // subversion is release 1.4 or above + // 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 + * @param $dir String: directory of the svn checkout + * + * @return Integer: revision number as int */ public static function getSvnRevision( $dir ) { $info = self::getSvnInfo( $dir ); + if ( $info === false ) { return false; } elseif ( isset( $info['checkout-rev'] ) ) { @@ -506,5 +658,4 @@ class SpecialVersion extends SpecialPage { } } - /**#@-*/ } diff --git a/includes/specials/SpecialWantedcategories.php b/includes/specials/SpecialWantedcategories.php index 5e5a4f17..b588dbf0 100644 --- a/includes/specials/SpecialWantedcategories.php +++ b/includes/specials/SpecialWantedcategories.php @@ -1,5 +1,24 @@ <?php /** + * Implements Special:Wantedcategories + * + * Copyright © 2005 Ævar Arnfjörð Bjarmason + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ @@ -8,10 +27,6 @@ * A querypage to list the most wanted categories - implements Special:Wantedcategories * * @ingroup SpecialPage - * - * @author Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * @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 WantedQueryPage { diff --git a/includes/specials/SpecialWantedfiles.php b/includes/specials/SpecialWantedfiles.php index 189b9d8b..d6c1157b 100644 --- a/includes/specials/SpecialWantedfiles.php +++ b/includes/specials/SpecialWantedfiles.php @@ -1,17 +1,33 @@ <?php -/* +/** + * Implements Special:Wantedfiles + * + * Copyright © 2008 Soxred93 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Soxred93 <soxred93@gmail.com> */ /** - * Querypage that lists the most wanted files - implements Special:Wantedfiles + * Querypage that lists the most wanted files * * @ingroup SpecialPage - * - * @author Soxred93 <soxred93@gmail.com> - * @copyright Copyright © 2008, Soxred93 - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ class WantedFilesPage extends WantedQueryPage { @@ -19,9 +35,19 @@ class WantedFilesPage extends WantedQueryPage { return 'Wantedfiles'; } + /** + * KLUGE: The results may contain false positives for files + * that exist e.g. in a shared repo. Setting this at least + * keeps them from showing up as redlinks in the output, even + * if it doesn't fix the real problem (bug 6220). + */ + function forceExistenceCheck() { + return true; + } + function getSQL() { $dbr = wfGetDB( DB_SLAVE ); - list( $imagelinks, $page ) = $dbr->tableNamesN( 'imagelinks', 'page' ); + list( $imagelinks, $image ) = $dbr->tableNamesN( 'imagelinks', 'image' ); $name = $dbr->addQuotes( $this->getName() ); return " @@ -31,8 +57,8 @@ class WantedFilesPage extends WantedQueryPage { il_to as title, COUNT(*) as value FROM $imagelinks - LEFT JOIN $page ON il_to = page_title AND page_namespace = ". NS_FILE ." - WHERE page_title IS NULL + LEFT JOIN $image ON il_to = img_name + WHERE img_name IS NULL GROUP BY il_to "; } diff --git a/includes/specials/SpecialWantedpages.php b/includes/specials/SpecialWantedpages.php index eeca87ab..4e1611bc 100644 --- a/includes/specials/SpecialWantedpages.php +++ b/includes/specials/SpecialWantedpages.php @@ -1,17 +1,35 @@ <?php /** + * Implements Special:Wantedpages + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage */ /** - * implements Special:Wantedpages + * A special page that lists most linked pages that does not exist + * * @ingroup SpecialPage */ class WantedPagesPage extends WantedQueryPage { var $nlinks; - function WantedPagesPage( $inc = false, $nlinks = true ) { + function __construct( $inc = false, $nlinks = true ) { $this->setListoutput( $inc ); $this->nlinks = $nlinks; } diff --git a/includes/specials/SpecialWantedtemplates.php b/includes/specials/SpecialWantedtemplates.php index 329d7a3f..ae43c237 100644 --- a/includes/specials/SpecialWantedtemplates.php +++ b/includes/specials/SpecialWantedtemplates.php @@ -1,19 +1,35 @@ <?php /** + * Implements Special:Wantedtemplates + * + * Copyright © 2008, Danny B. + * Based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com> + * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Danny B. */ /** - * A querypage to list the most wanted templates - implements Special:Wantedtemplates - * based on SpecialWantedcategories.php by Ævar Arnfjörð Bjarmason <avarab@gmail.com> - * makeWlhLink() taken from SpecialMostlinkedtemplates by Rob Church <robchur@gmail.com> + * A querypage to list the most wanted templates * * @ingroup SpecialPage - * - * @author Danny B. - * @copyright Copyright © 2008, Danny B. - * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later */ class WantedTemplatesPage extends WantedQueryPage { diff --git a/includes/specials/SpecialWatchlist.php b/includes/specials/SpecialWatchlist.php index c32af2ae..bb1c194d 100644 --- a/includes/specials/SpecialWatchlist.php +++ b/includes/specials/SpecialWatchlist.php @@ -1,5 +1,22 @@ <?php /** + * Implements Special:Watchlist + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage Watchlist */ @@ -21,7 +38,7 @@ function wfSpecialWatchlist( $par ) { $wgUser->saveSettings(); } - global $wgServer, $wgScriptPath, $wgFeedClasses; + global $wgFeedClasses; $apiParams = array( 'action' => 'feedwatchlist', 'allrev' => 'allrev', 'wlowner' => $wgUser->getName(), 'wltoken' => $wlToken ); $feedTemplate = wfScript('api').'?'; @@ -51,8 +68,7 @@ function wfSpecialWatchlist( $par ) { $wgOut->setPageTitle( wfMsg( 'watchlist' ) ); - $sub = wfMsgExt( 'watchlistfor', 'parseinline', $wgUser->getName() ); - $sub .= '<br />' . WatchlistEditor::buildTools( $wgUser->getSkin() ); + $sub = wfMsgExt( 'watchlistfor2', array( 'parseinline', 'replaceafter' ), $wgUser->getName(), WatchlistEditor::buildTools( $wgUser->getSkin() ) ); $wgOut->setSubtitle( $sub ); if( ( $mode = WatchlistEditor::getMode( $wgRequest, $par ) ) !== false ) { @@ -89,7 +105,7 @@ function wfSpecialWatchlist( $par ) { $prefs['days'] = floatval( $wgUser->getOption( 'watchlistdays' ) ); $prefs['hideminor'] = $wgUser->getBoolOption( 'watchlisthideminor' ); $prefs['hidebots'] = $wgUser->getBoolOption( 'watchlisthidebots' ); - $prefs['hideanons'] = $wgUser->getBoolOption( 'watchlisthideanon' ); + $prefs['hideanons'] = $wgUser->getBoolOption( 'watchlisthideanons' ); $prefs['hideliu'] = $wgUser->getBoolOption( 'watchlisthideliu' ); $prefs['hideown' ] = $wgUser->getBoolOption( 'watchlisthideown' ); $prefs['hidepatrolled' ] = $wgUser->getBoolOption( 'watchlisthidepatrolled' ); @@ -155,10 +171,11 @@ function wfSpecialWatchlist( $par ) { return; } - if( $days <= 0 ) { - $andcutoff = ''; - } else { - $andcutoff = "rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'"; + # Possible where conditions + $conds = array(); + + if( $days > 0 ) { + $conds[] = "rc_timestamp > '".$dbr->timestamp( time() - intval( $days * 86400 ) )."'"; } # If the watchlist is relatively short, it's simplest to zip @@ -170,21 +187,35 @@ function wfSpecialWatchlist( $par ) { # Up estimate of watched items by 15% to compensate for talk pages... # Toggles - $andHideOwn = $hideOwn ? "rc_user != $uid" : ''; - $andHideBots = $hideBots ? "rc_bot = 0" : ''; - $andHideMinor = $hideMinor ? "rc_minor = 0" : ''; - $andHideLiu = $hideLiu ? "rc_user = 0" : ''; - $andHideAnons = $hideAnons ? "rc_user != 0" : ''; - $andHidePatrolled = $wgUser->useRCPatrol() && $hidePatrolled ? "rc_patrolled != 1" : ''; + if( $hideOwn ) { + $conds[] = "rc_user != $uid"; + } + if( $hideBots ) { + $conds[] = 'rc_bot = 0'; + } + if( $hideMinor ) { + $conds[] = 'rc_minor = 0'; + } + if( $hideLiu ) { + $conds[] = 'rc_user = 0'; + } + if( $hideAnons ) { + $conds[] = 'rc_user != 0'; + } + if ( $wgUser->useRCPatrol() && $hidePatrolled ) { + $conds[] = 'rc_patrolled != 1'; + } + if( $nameSpaceClause ) { + $conds[] = $nameSpaceClause; + } # Toggle watchlist content (all recent edits or just the latest) if( $wgUser->getOption( 'extendwatchlist' )) { - $andLatest=''; $limitWatchlist = intval( $wgUser->getOption( 'wllimit' ) ); $usePage = false; } else { # Top log Ids for a page are not stored - $andLatest = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; + $conds[] = 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG; $limitWatchlist = 0; $usePage = true; } @@ -208,14 +239,13 @@ function wfSpecialWatchlist( $par ) { 'id' => 'mw-watchlist-resetbutton' ) ) . wfMsgExt( 'wlheader-showupdated', array( 'parseinline' ) ) . ' ' . Xml::submitButton( wfMsg( 'enotif_reset' ), array( 'name' => 'dummy' ) ) . - Xml::hidden( 'reset', 'all' ) . + Html::hidden( 'reset', 'all' ) . Xml::closeElement( 'form' ); } $form .= '<hr />'; $tables = array( 'recentchanges', 'watchlist' ); $fields = array( "{$recentchanges}.*" ); - $conds = array(); $join_conds = array( 'watchlist' => array('INNER JOIN',"wl_user='{$uid}' AND wl_namespace=rc_namespace AND wl_title=rc_title"), ); @@ -226,15 +256,6 @@ function wfSpecialWatchlist( $par ) { if( $limitWatchlist ) { $options['LIMIT'] = $limitWatchlist; } - if( $andcutoff ) $conds[] = $andcutoff; - if( $andLatest ) $conds[] = $andLatest; - if( $andHideOwn ) $conds[] = $andHideOwn; - if( $andHideBots ) $conds[] = $andHideBots; - if( $andHideMinor ) $conds[] = $andHideMinor; - if( $andHideLiu ) $conds[] = $andHideLiu; - if( $andHideAnons ) $conds[] = $andHideAnons; - if( $andHidePatrolled ) $conds[] = $andHidePatrolled; - if( $nameSpaceClause ) $conds[] = $nameSpaceClause; $rollbacker = $wgUser->isAllowed('rollback'); if ( $usePage || $rollbacker ) { @@ -287,29 +308,27 @@ function wfSpecialWatchlist( $par ) { $form .= $wlInfo; $form .= $cutofflinks; $form .= $wgLang->pipeList( $links ); - $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) ); + $form .= Xml::openElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl(), 'id' => 'mw-watchlist-form-namespaceselector' ) ); $form .= '<hr /><p>'; - $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; - $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; - $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . ' '; + $form .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' '; + $form .= Xml::namespaceSelector( $nameSpace, '' ) . ' '; + $form .= Xml::checkLabel( wfMsg('invert'), 'invert', 'nsinvert', $invert ) . ' '; $form .= Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . '</p>'; - $form .= Xml::hidden( 'days', $days ); + $form .= Html::hidden( 'days', $days ); if( $hideMinor ) - $form .= Xml::hidden( 'hideMinor', 1 ); + $form .= Html::hidden( 'hideMinor', 1 ); if( $hideBots ) - $form .= Xml::hidden( 'hideBots', 1 ); + $form .= Html::hidden( 'hideBots', 1 ); if( $hideAnons ) - $form .= Xml::hidden( 'hideAnons', 1 ); + $form .= Html::hidden( 'hideAnons', 1 ); if( $hideLiu ) - $form .= Xml::hidden( 'hideLiu', 1 ); + $form .= Html::hidden( 'hideLiu', 1 ); if( $hideOwn ) - $form .= Xml::hidden( 'hideOwn', 1 ); + $form .= Html::hidden( 'hideOwn', 1 ); $form .= Xml::closeElement( 'form' ); $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' ); @@ -320,7 +339,7 @@ function wfSpecialWatchlist( $par ) { /* Do link batch query */ $linkBatch = new LinkBatch; - while ( $row = $dbr->fetchObject( $res ) ) { + foreach ( $res as $row ) { $userNameUnderscored = str_replace( ' ', '_', $row->rc_user_text ); if ( $row->rc_user != 0 ) { $linkBatch->add( NS_USER, $userNameUnderscored ); @@ -337,7 +356,7 @@ function wfSpecialWatchlist( $par ) { $s = $list->beginRecentChangesList(); $counter = 1; - while ( $obj = $dbr->fetchObject( $res ) ) { + foreach ( $res as $obj ) { # Make RC entry $rc = RecentChange::newFromRow( $obj ); $rc->counter = $counter++; @@ -364,7 +383,6 @@ function wfSpecialWatchlist( $par ) { } $s .= $list->endRecentChangesList(); - $dbr->freeResult( $res ); $wgOut->addHTML( $s ); } @@ -444,8 +462,9 @@ function wlCutoffLinks( $days, $page = 'Watchlist', $options = array() ) { /** * Count the number of items on a user's watchlist * - * @param $talk Include talk pages - * @return integer + * @param $user User object + * @param $talk Boolean: include talk pages + * @return Integer */ function wlCountItems( &$user, $talk = true ) { $dbr = wfGetDB( DB_SLAVE, 'watchlist' ); @@ -455,7 +474,6 @@ function wlCountItems( &$user, $talk = true ) { array( 'wl_user' => $user->mId ), 'wlCountItems' ); $row = $dbr->fetchObject( $res ); $count = $row->count; - $dbr->freeResult( $res ); # Halve to remove talk pages if needed if( !$talk ) diff --git a/includes/specials/SpecialWhatlinkshere.php b/includes/specials/SpecialWhatlinkshere.php index b63c0eee..360f3f68 100644 --- a/includes/specials/SpecialWhatlinkshere.php +++ b/includes/specials/SpecialWhatlinkshere.php @@ -1,13 +1,29 @@ <?php /** - * @todo Use some variant of Pager or something; the pagination here is lousy. + * Implements Special:Whatlinkshere + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html * * @file - * @ingroup SpecialPage + * @todo Use some variant of Pager or something; the pagination here is lousy. */ /** - * implements Special:Whatlinkshere + * Implements Special:Whatlinkshere + * * @ingroup SpecialPage */ class SpecialWhatLinksHere extends SpecialPage { @@ -60,7 +76,7 @@ class SpecialWhatLinksHere extends SpecialPage { return; } - $this->selfTitle = SpecialPage::getTitleFor( 'Whatlinkshere', $this->target->getPrefixedDBkey() ); + $this->selfTitle = $this->getTitle( $this->target->getPrefixedDBkey() ); $wgOut->setPageTitle( wfMsg( 'whatlinkshere-title', $this->target->getPrefixedText() ) ); $wgOut->setSubtitle( wfMsg( 'whatlinkshere-backlink', $this->skin->link( $this->target, $this->target->getPrefixedText(), array(), array( 'redirect' => 'no' ) ) ) ); @@ -171,29 +187,25 @@ class SpecialWhatLinksHere extends SpecialPage { // templatelinks comes second so that the templatelinks row overwrites the // pagelinks row, so we get (inclusion) rather than nothing if( $fetchlinks ) { - while ( $row = $dbr->fetchObject( $plRes ) ) { + foreach ( $plRes as $row ) { $row->is_template = 0; $row->is_image = 0; $rows[$row->page_id] = $row; } - $dbr->freeResult( $plRes ); - } if( !$hidetrans ) { - while ( $row = $dbr->fetchObject( $tlRes ) ) { + foreach ( $tlRes as $row ) { $row->is_template = 1; $row->is_image = 0; $rows[$row->page_id] = $row; } - $dbr->freeResult( $tlRes ); } if( !$hideimages ) { - while ( $row = $dbr->fetchObject( $ilRes ) ) { + foreach ( $ilRes as $row ) { $row->is_template = 0; $row->is_image = 1; $rows[$row->page_id] = $row; } - $dbr->freeResult( $ilRes ); } // Sort by key and then change the keys to 0-based indices @@ -224,7 +236,7 @@ class SpecialWhatLinksHere extends SpecialPage { $wgOut->addHTML( $prevnext ); } - $wgOut->addHTML( $this->listStart() ); + $wgOut->addHTML( $this->listStart( $level ) ); foreach ( $rows as $row ) { $nt = Title::makeTitle( $row->page_namespace, $row->page_title ); @@ -244,8 +256,8 @@ class SpecialWhatLinksHere extends SpecialPage { } } - protected function listStart() { - return Xml::openElement( 'ul', array ( 'id' => 'mw-whatlinkshere-list' ) ); + protected function listStart( $level ) { + return Xml::openElement( 'ul', ( $level ? array() : array( 'id' => 'mw-whatlinkshere-list' ) ) ); } protected function listItem( $row, $nt, $notClose = false ) { @@ -303,7 +315,7 @@ class SpecialWhatLinksHere extends SpecialPage { protected function wlhLink( Title $target, $text ) { static $title = null; if ( $title === null ) - $title = SpecialPage::getTitleFor( 'Whatlinkshere' ); + $title = $this->getTitle(); return $this->skin->linkKnown( $title, @@ -368,9 +380,9 @@ class SpecialWhatLinksHere extends SpecialPage { $f = Xml::openElement( 'form', array( 'action' => $wgScript ) ); # Values that should not be forgotten - $f .= Xml::hidden( 'title', SpecialPage::getTitleFor( 'Whatlinkshere' )->getPrefixedText() ); + $f .= Html::hidden( 'title', $this->getTitle()->getPrefixedText() ); foreach ( $this->opts->getUnconsumedValues() as $name => $value ) { - $f .= Xml::hidden( $name, $value ); + $f .= Html::hidden( $name, $value ); } $f .= Xml::fieldset( wfMsg( 'whatlinkshere' ) ); @@ -382,7 +394,7 @@ class SpecialWhatLinksHere extends SpecialPage { $f .= ' '; # Namespace selector - $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . + $f .= Xml::label( wfMsg( 'namespace' ), 'namespace' ) . ' ' . Xml::namespaceSelector( $namespace, '' ); $f .= ' '; diff --git a/includes/specials/SpecialWithoutinterwiki.php b/includes/specials/SpecialWithoutinterwiki.php index a5d60d2f..90c1f441 100644 --- a/includes/specials/SpecialWithoutinterwiki.php +++ b/includes/specials/SpecialWithoutinterwiki.php @@ -1,14 +1,31 @@ <?php /** + * Implements Special:Withoutinterwiki + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * * @file * @ingroup SpecialPage + * @author Rob Church <robchur@gmail.com> */ /** * Special page lists pages without language links * * @ingroup SpecialPage - * @author Rob Church <robchur@gmail.com> */ class WithoutInterwikiPage extends PageQueryPage { private $prefix = ''; @@ -31,7 +48,7 @@ class WithoutInterwikiPage extends PageQueryPage { return Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . Xml::openElement( 'fieldset' ) . Xml::element( 'legend', null, wfMsg( 'withoutinterwiki-legend' ) ) . - Xml::hidden( 'title', $t->getPrefixedText() ) . + Html::hidden( 'title', $t->getPrefixedText() ) . Xml::inputLabel( wfMsg( 'allpagesprefix' ), 'prefix', 'wiprefix', 20, $prefix ) . ' ' . Xml::submitButton( wfMsg( 'withoutinterwiki-submit' ) ) . Xml::closeElement( 'fieldset' ) . @@ -75,7 +92,7 @@ class WithoutInterwikiPage extends PageQueryPage { } function wfSpecialWithoutinterwiki() { - global $wgRequest, $wgContLang; + global $wgRequest; list( $limit, $offset ) = wfCheckLimits(); // Only searching the mainspace anyway $prefix = Title::capitalize( $wgRequest->getVal( 'prefix' ), NS_MAIN ); |