Revert r86001: Brion says it's too scary :D will recommit in pieces

This commit is contained in:
Happy-melon 2011-04-13 23:36:27 +00:00
parent 887f4eff82
commit bc4a096805
18 changed files with 635 additions and 1173 deletions

View file

@ -264,17 +264,6 @@ $reason: the reason for the move (added in 1.13)
$user: the User object about to be created (read-only, incomplete)
$message: out parameter: error message to display on abort
'ActionBeforeFormDisplay': Modify the form shown for an action (added 1.18)
$action: String
$form: HTMLForm
$page: Article
'ActionModifyFormFields': Modify the descriptor array which will be used to create an
action form
$action: String
$fields: Array
$page: Article
'AddNewAccount': after a user account is created
$user: the User object that was created. (Parameter added in 1.7)
$byEmail: true when account was created "by email" (added in 1.12)
@ -395,6 +384,12 @@ the database
$article: the article (object) being loaded from the database
$content: the content (string) of the article
'ArticleConfirmDelete': before writing the confirmation form for article
deletion
$article: the article (object) being deleted
$output: the OutputPage object ($wgOut)
&$reason: the reason (string) the article is being deleted
'ArticleContentOnDiff': before showing the article content below a diff.
Use this to change the content in this area or how it is loaded.
$diffEngine: the DifferenceEngine

View file

@ -1,440 +0,0 @@
<?php
/**
* Actions are things which can be done to pages (edit, delete, rollback, etc). They
* are distinct from Special Pages because an action must apply to exactly one page.
*
* To add an action in an extension, create a subclass of Action, and add the key to
* $wgActions. There is also the deprecated UnknownAction hook
*
*
* 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
*
* @file
*/
abstract class Action {
// Page on which we're performing the action
// @var Article
protected $page;
// RequestContext if specified; otherwise we'll use the Context from the Page
// @var RequestContext
protected $context;
// The fields used to create the HTMLForm
// @var Array
protected $fields;
/**
* Get the Action subclass which should be used to handle this action, false if
* the action is disabled, or null if it's not recognised
* @param $action String
* @return bool|null|string
*/
private final static function getClass( $action ){
global $wgActions;
$action = strtolower( $action );
if( !isset( $wgActions[$action] ) ){
return null;
}
if( $wgActions[$action] === false ){
return false;
}
elseif( $wgActions[$action] === true ){
return ucfirst( $action ) . 'Action';
}
else {
return $wgActions[$action];
}
}
/**
* Get an appropriate Action subclass for the given action
* @param $action String
* @param $page Article
* @return Action|false|null false if the action is disabled, null
* if it is not recognised
*/
public final static function factory( $action, Article $page ){
$class = self::getClass( $action );
if( $class ){
$obj = new $class( $page );
return $obj;
}
return null;
}
/**
* Check if a given action is recognised, even if it's disabled
*
* @param $name String: name of an action
* @return Bool
*/
public final static function exists( $name ) {
return self::getClass( $name ) !== null;
}
/**
* Get the RequestContext in use here
* @return RequestContext
*/
protected final function getContext(){
if( $this->context instanceof RequestContext ){
return $this->context;
}
return $this->page->getContext();
}
/**
* Get the WebRequest being used for this instance
*
* @return WebRequest
*/
protected final function getRequest() {
return $this->getContext()->request;
}
/**
* Get the OutputPage being used for this instance
*
* @return OutputPage
*/
protected final function getOutput() {
return $this->getContext()->output;
}
/**
* Shortcut to get the skin being used for this instance
*
* @return User
*/
protected final function getUser() {
return $this->getContext()->user;
}
/**
* Shortcut to get the skin being used for this instance
*
* @return Skin
*/
protected final function getSkin() {
return $this->getContext()->skin;
}
/**
* Shortcut to get the Title object from the page
* @return Title
*/
protected final function getTitle(){
return $this->page->getTitle();
}
/**
* Protected constructor: use Action::factory( $action, $page ) to actually build
* these things in the real world
* @param Article $page
*/
protected function __construct( Article $page ){
$this->page = $page;
}
/**
* Return the name of the action this object responds to
* @return String lowercase
*/
public abstract function getName();
/**
* Get the permission required to perform this action. Often, but not always,
* the same as the action name
*/
public abstract function getRestriction();
/**
* Checks if the given user (identified by an object) can perform this action. Can be
* overridden by sub-classes with more complicated permissions schemes. Failures here
* must throw subclasses of ErrorPageError
*
* @param $user User: the user to check, or null to use the context user
* @throws ErrorPageError
*/
protected function checkCanExecute( User $user ) {
if( $this->requiresWrite() && wfReadOnly() ){
throw new ReadOnlyError();
}
if( $this->getRestriction() !== null && !$user->isAllowed( $this->getRestriction() ) ){
throw new PermissionsError( $this->getRestriction() );
}
if( $this->requiresUnblock() && $user->isBlocked() ){
$block = $user->mBlock;
throw new UserBlockedError( $block );
}
}
/**
* Whether this action requires the wiki not to be locked
* @return Bool
*/
public function requiresWrite(){
return true;
}
/**
* Whether this action can still be executed by a blocked user
* @return Bool
*/
public function requiresUnblock(){
return true;
}
/**
* Set output headers for noindexing etc. This function will not be called through
* the execute() entry point, so only put UI-related stuff in here.
*/
protected function setHeaders() {
$out = $this->getOutput();
$out->setRobotPolicy( "noindex,nofollow" );
$out->setPageTitle( $this->getTitle()->getPrefixedText() );
$this->getOutput()->setSubtitle( $this->getDescription() );
$out->setArticleRelated( true );
}
/**
* Returns the name that goes in the \<h1\> page title
*
* Derived classes can override this, but usually it is easier to keep the
* default behaviour. Messages can be added at run-time, see
* MessageCache.php.
*
* @return String
*/
protected function getDescription() {
return wfMsg( strtolower( $this->getName() ) );
}
/**
* The basic pattern for actions is to display some sort of HTMLForm UI, maybe with
* some stuff underneath (history etc); to do some processing on submission of that
* form (delete, protect, etc) and to do something exciting on 'success', be that
* display something new or redirect to somewhere. Some actions have more exotic
* behaviour, but that's what subclassing is for :D
*/
public function show(){
$this->setHeaders();
// This will throw exceptions if there's a problem
$this->checkCanExecute( $this->getUser() );
$form = $this->getForm();
if( $form instanceof HTMLForm ){
if( $form->show() ){
$this->onSuccess();
}
} else {
// You're using the wrong type of Action
throw new MWException( "Action::getFormFields() must produce a form. Use GetAction if you don't want one." );
}
}
/**
* Execute the action in a silent fashion: do not display anything or release any errors.
* @param $data Array values that would normally be in the POST request
* @param $captureErrors Bool whether to catch exceptions and just return false
* @return Bool whether execution was successful
*/
public function execute( array $data = null, $captureErrors = true ){
try {
// Set a new context so output doesn't leak.
$this->context = clone $this->page->getContext();
// This will throw exceptions if there's a problem
$this->checkCanExecute( $this->getUser() );
$form = $this->getForm();
if( $form instanceof HTMLForm ){
// Great, so there's a form. Ignore it and go straight to the submission callback
$fields = array();
foreach( $this->fields as $key => $params ){
if( isset( $data[$key] ) ){
$fields[$key] = $data[$key];
} elseif( isset( $params['default'] ) ) {
$fields[$key] = $params['default'];
} else {
$fields[$key] = null;
}
}
$status = $this->onSubmit( $fields );
if( $status === true ){
// This might do permanent stuff
$this->onSuccess();
return true;
} else {
return false;
}
} else {
// You're using the wrong type of Action
throw new MWException( "Action::getFormFields() must produce a form. Use GetAction if you don't want one." );
}
}
catch ( ErrorPageError $e ){
if( $captureErrors ){
return false;
} else {
throw $e;
}
}
}
/**
* Get an HTMLForm descriptor array, or false if you don't want a form
* @return Array
*/
protected abstract function getFormFields();
/**
* Add pre- or post-text to the form
* @return String
*/
protected function preText(){ return ''; }
protected function postText(){ return ''; }
/**
* Play with the HTMLForm if you need to more substantially
* @param &$form HTMLForm
*/
protected function alterForm( HTMLForm &$form ){}
/**
* Get the HTMLForm to control behaviour
* @return HTMLForm|null
*/
protected function getForm(){
$this->fields = $this->getFormFields();
// Give hooks a chance to alter the form, adding extra fields or text etc
wfRunHooks( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
if( $this->fields === false ){
return null;
}
$form = new HTMLForm( $this->fields, $this->getContext() );
$form->setSubmitCallback( array( $this, 'onSubmit' ) );
$form->addHiddenField( 'action', $this->getName() );
$form->addPreText( $this->preText() );
$form->addPostText( $this->postText() );
$this->alterForm( $form );
// Give hooks a chance to alter the form, adding extra fields or text etc
wfRunHooks( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
return $form;
}
/**
* Process the form on POST submission. If you return false from getFormFields(),
* this will obviously never be reached. If you don't want to do anything with the
* form, just return false here
* @param $data Array
* @return Bool|Array true for success, false for didn't-try, array of errors on failure
*/
public abstract function onSubmit( $data );
/**
* Do something exciting on successful processing of the form. This might be to show
* a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
* protect, etc).
*/
public abstract function onSuccess();
}
/**
* Actions generally fall into two groups: the show-a-form-then-do-something-with-the-input
* format (protect, delete, move, etc), and the just-do-something format (watch, rollback,
* patrol, etc).
*/
abstract class FormlessAction extends Action {
/**
* Show something on GET request. This is displayed as the postText() of the HTMLForm
* if there is one; you can always use alterForm() to add pre text if you need it. If
* you call addPostText() from alterForm() as well as overriding this function, you
* might get strange ordering.
* @return String|null will be added to the HTMLForm if present, or just added to the
* output if not. Return null to not add anything
*/
public abstract function onView();
/**
* We don't want an HTMLForm
*/
protected function getFormFields(){
return false;
}
public function onSubmit( $data ){
return false;
}
public function onSuccess(){
return false;
}
public function show(){
$this->setHeaders();
// This will throw exceptions if there's a problem
$this->checkCanExecute( $this->getUser() );
$this->getOutput()->addHTML( $this->onView() );
}
/**
* Execute the action silently, not giving any output. Since these actions don't have
* forms, they probably won't have any data, but some (eg rollback) may do
* @param $data Array values that would normally be in the GET request
* @param $captureErrors Bool whether to catch exceptions and just return false
* @return Bool whether execution was successful
*/
public function execute( array $data = null, $captureErrors = true){
try {
// Set a new context so output doesn't leak.
$this->context = clone $this->page->getContext();
if( is_array( $data ) ){
$this->context->setRequest( new FauxRequest( $data, false ) );
}
// This will throw exceptions if there's a problem
$this->checkCanExecute( $this->getUser() );
$this->onView();
return true;
}
catch ( ErrorPageError $e ){
if( $captureErrors ){
return false;
} else {
throw $e;
}
}
}
}

View file

@ -2345,10 +2345,27 @@ class Article {
/**
* User-interface handler for the "watch" action
* @deprecated since 1.18
*/
public function watch() {
Action::factory( 'watch', $this )->show();
global $wgOut;
if ( $wgOut->getUser()->isAnon() ) {
$wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
return;
}
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
if ( $this->doWatch() ) {
$wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'addedwatchtext', $this->mTitle->getPrefixedText() );
}
$wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
}
/**
@ -2357,27 +2374,64 @@ class Article {
* This is safe to be called multiple times
*
* @return bool true on successful watch operation
* @deprecated since 1.18
*/
public function doWatch() {
return Action::factory( 'watch', $this )->execute();
global $wgUser;
if ( $wgUser->isAnon() ) {
return false;
}
if ( wfRunHooks( 'WatchArticle', array( &$wgUser, &$this ) ) ) {
$wgUser->addWatch( $this->mTitle );
return wfRunHooks( 'WatchArticleComplete', array( &$wgUser, &$this ) );
}
return false;
}
/**
* User interface handler for the "unwatch" action.
* @deprecated since 1.18
*/
public function unwatch() {
Action::factory( 'unwatch', $this )->show();
global $wgOut;
if ( $wgOut->getUser()->isAnon() ) {
$wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
return;
}
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
if ( $this->doUnwatch() ) {
$wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'removedwatchtext', $this->mTitle->getPrefixedText() );
}
$wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
}
/**
* Stop watching a page
* @return bool true on successful unwatch
* @deprecated since 1.18
*/
public function doUnwatch() {
return Action::factory( 'unwatch', $this )->execute();
global $wgUser;
if ( $wgUser->isAnon() ) {
return false;
}
if ( wfRunHooks( 'UnwatchArticle', array( &$wgUser, &$this ) ) ) {
$wgUser->removeWatch( $this->mTitle );
return wfRunHooks( 'UnwatchArticleComplete', array( &$wgUser, &$this ) );
}
return false;
}
/**
@ -2609,28 +2663,229 @@ class Article {
* @param &$hasHistory Boolean: whether the page has a history
* @return mixed String containing deletion reason or empty string, or boolean false
* if no revision occurred
* @deprecated since 1.18
*/
public function generateReason( &$hasHistory ) {
return DeleteAction::getAutoReason( $this );
global $wgContLang;
$dbw = wfGetDB( DB_MASTER );
// Get the last revision
$rev = Revision::newFromTitle( $this->mTitle );
if ( is_null( $rev ) ) {
return false;
}
// Get the article's contents
$contents = $rev->getText();
$blank = false;
// If the page is blank, use the text from the previous revision,
// which can only be blank if there's a move/import/protect dummy revision involved
if ( $contents == '' ) {
$prev = $rev->getPrevious();
if ( $prev ) {
$contents = $prev->getText();
$blank = true;
}
}
// Find out if there was only one contributor
// Only scan the last 20 revisions
$res = $dbw->select( 'revision', 'rev_user_text',
array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
__METHOD__,
array( 'LIMIT' => 20 )
);
if ( $res === false ) {
// This page has no revisions, which is very weird
return false;
}
$hasHistory = ( $res->numRows() > 1 );
$row = $dbw->fetchObject( $res );
if ( $row ) { // $row is false if the only contributor is hidden
$onlyAuthor = $row->rev_user_text;
// Try to find a second contributor
foreach ( $res as $row ) {
if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
$onlyAuthor = false;
break;
}
}
} else {
$onlyAuthor = false;
}
// Generate the summary with a '$1' placeholder
if ( $blank ) {
// The current revision is blank and the one before is also
// blank. It's just not our lucky day
$reason = wfMsgForContent( 'exbeforeblank', '$1' );
} else {
if ( $onlyAuthor ) {
$reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
} else {
$reason = wfMsgForContent( 'excontent', '$1' );
}
}
if ( $reason == '-' ) {
// Allow these UI messages to be blanked out cleanly
return '';
}
// Replace newlines with spaces to prevent uglyness
$contents = preg_replace( "/[\n\r]/", ' ', $contents );
// Calculate the maximum amount of chars to get
// Max content length = max comment length - length of the comment (excl. $1)
$maxLength = 255 - ( strlen( $reason ) - 2 );
$contents = $wgContLang->truncate( $contents, $maxLength );
// Remove possible unfinished links
$contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
// Now replace the '$1' placeholder
$reason = str_replace( '$1', $contents, $reason );
return $reason;
}
/*
* UI entry point for page deletion
* @deprecated since 1.18
*/
public function delete() {
return Action::factory( 'delete', $this )->show();
global $wgOut, $wgRequest;
$confirm = $wgRequest->wasPosted() &&
$wgOut->getUser()->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
$this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
$this->DeleteReason = $wgRequest->getText( 'wpReason' );
$reason = $this->DeleteReasonList;
if ( $reason != 'other' && $this->DeleteReason != '' ) {
// Entry from drop down menu + additional comment
$reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
} elseif ( $reason == 'other' ) {
$reason = $this->DeleteReason;
}
# Flag to hide all contents of the archived revisions
$suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgOut->getUser()->isAllowed( 'suppressrevision' );
# This code desperately needs to be totally rewritten
# Read-only check...
if ( wfReadOnly() ) {
$wgOut->readOnlyPage();
return;
}
# Check permissions
$permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgOut->getUser() );
if ( count( $permission_errors ) > 0 ) {
$wgOut->showPermissionsErrorPage( $permission_errors );
return;
}
$wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->mTitle->getPrefixedText() ) );
# Better double-check that it hasn't been deleted yet!
$dbw = wfGetDB( DB_MASTER );
$conds = $this->mTitle->pageCond();
$latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
if ( $latest === false ) {
$wgOut->showFatalError(
Html::rawElement(
'div',
array( 'class' => 'error mw-error-cannotdelete' ),
wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
)
);
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
LogEventsList::showLogExtract(
$wgOut,
'delete',
$this->mTitle->getPrefixedText()
);
return;
}
# Hack for big sites
$bigHistory = $this->isBigDeletion();
if ( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
global $wgLang, $wgDeleteRevisionsLimit;
$wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
return;
}
if ( $confirm ) {
$this->doDelete( $reason, $suppress );
if ( $wgRequest->getCheck( 'wpWatch' ) && $wgOut->getUser()->isLoggedIn() ) {
$this->doWatch();
} elseif ( $this->mTitle->userIsWatching() ) {
$this->doUnwatch();
}
return;
}
// Generate deletion reason
$hasHistory = false;
if ( !$reason ) {
$reason = $this->generateReason( $hasHistory );
}
// If the page has a history, insert a warning
if ( $hasHistory && !$confirm ) {
global $wgLang;
$skin = $wgOut->getSkin();
$revisions = $this->estimateRevisionCount();
//FIXME: lego
$wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
wfMsgHtml( 'word-separator' ) . $skin->link( $this->mTitle,
wfMsgHtml( 'history' ),
array( 'rel' => 'archives' ),
array( 'action' => 'history' ) ) .
'</strong>'
);
if ( $bigHistory ) {
global $wgDeleteRevisionsLimit;
$wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
}
}
return $this->confirmDelete( $reason );
}
/**
* @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
* @deprecated since 1.18
*/
public function isBigDeletion() {
global $wgDeleteRevisionsLimit;
return $wgDeleteRevisionsLimit && $this->estimateRevisionCount() > $wgDeleteRevisionsLimit;
if ( $wgDeleteRevisionsLimit ) {
$revCount = $this->estimateRevisionCount();
return $revCount > $wgDeleteRevisionsLimit;
}
return false;
}
/**
@ -2698,20 +2953,151 @@ class Article {
return $authors;
}
/**
* Output deletion confirmation dialog
* FIXME: Move to another file?
* @param $reason String: prefilled reason
*/
public function confirmDelete( $reason ) {
global $wgOut;
wfDebug( "Article::confirmDelete\n" );
$deleteBackLink = $wgOut->getSkin()->linkKnown( $this->mTitle );
$wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$wgOut->addWikiMsg( 'confirmdeletetext' );
wfRunHooks( 'ArticleConfirmDelete', array( $this, $wgOut, &$reason ) );
if ( $wgOut->getUser()->isAllowed( 'suppressrevision' ) ) {
$suppress = "<tr id=\"wpDeleteSuppressRow\">
<td></td>
<td class='mw-input'><strong>" .
Xml::checkLabel( wfMsg( 'revdelete-suppress' ),
'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
"</strong></td>
</tr>";
} else {
$suppress = '';
}
$checkWatch = $wgOut->getUser()->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching();
$form = Xml::openElement( 'form', array( 'method' => 'post',
'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
"<tr id=\"wpDeleteReasonListRow\">
<td class='mw-label'>" .
Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' ) .
"</td>
<td class='mw-input'>" .
Xml::listDropDown( 'wpDeleteReasonList',
wfMsgForContent( 'deletereason-dropdown' ),
wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
"</td>
</tr>
<tr id=\"wpDeleteReasonRow\">
<td class='mw-label'>" .
Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) .
"</td>
<td class='mw-input'>" .
Html::input( 'wpReason', $reason, 'text', array(
'size' => '60',
'maxlength' => '255',
'tabindex' => '2',
'id' => 'wpReason',
'autofocus'
) ) .
"</td>
</tr>";
# Disallow watching if user is not logged in
if ( $wgOut->getUser()->isLoggedIn() ) {
$form .= "
<tr>
<td></td>
<td class='mw-input'>" .
Xml::checkLabel( wfMsg( 'watchthis' ),
'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
"</td>
</tr>";
}
$form .= "
$suppress
<tr>
<td></td>
<td class='mw-submit'>" .
Xml::submitButton( wfMsg( 'deletepage' ),
array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
"</td>
</tr>" .
Xml::closeElement( 'table' ) .
Xml::closeElement( 'fieldset' ) .
Html::hidden( 'wpEditToken', $wgOut->getUser()->editToken() ) .
Xml::closeElement( 'form' );
if ( $wgOut->getUser()->isAllowed( 'editinterface' ) ) {
$skin = $wgOut->getSkin();
$title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
$link = $skin->link(
$title,
wfMsgHtml( 'delete-edit-reasonlist' ),
array(),
array( 'action' => 'edit' )
);
$form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
}
$wgOut->addHTML( $form );
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
LogEventsList::showLogExtract( $wgOut, 'delete',
$this->mTitle->getPrefixedText()
);
}
/**
* Perform a deletion and output success or failure messages
* @deprecated since 1.18
*/
public function doDelete( $reason, $suppress = false ) {
return DeleteAction::doDeleteArticle(
$this,
$this->getContext(),
array(
'Suppress' => $suppress !== false,
'Reason' => $reason,
),
true
);
global $wgOut;
$id = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
$error = '';
if ( $this->doDeleteArticle( $reason, $suppress, $id, $error ) ) {
$deleted = $this->mTitle->getPrefixedText();
$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
$loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
$wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink );
$wgOut->returnToMain( false );
} else {
if ( $error == '' ) {
$wgOut->showFatalError(
Html::rawElement(
'div',
array( 'class' => 'error mw-error-cannotdelete' ),
wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
)
);
$wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
LogEventsList::showLogExtract(
$wgOut,
'delete',
$this->mTitle->getPrefixedText()
);
} else {
$wgOut->showFatalError( $error );
}
}
}
/**
@ -2727,19 +3113,143 @@ class Article {
* @param $id int article ID
* @param $commit boolean defaults to true, triggers transaction end
* @return boolean true if successful
*
* @deprecated since 1.18
*/
public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
return DeleteAction::doDeleteArticle(
$this,
$this->getContext(),
global $wgDeferredUpdateList, $wgUseTrackbacks;
global $wgUser;
wfDebug( __METHOD__ . "\n" );
if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$wgUser, &$reason, &$error ) ) ) {
return false;
}
$dbw = wfGetDB( DB_MASTER );
$t = $this->mTitle->getDBkey();
$id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
if ( $t === '' || $id == 0 ) {
return false;
}
$u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable( $this->getRawText() ), -1 );
array_push( $wgDeferredUpdateList, $u );
// Bitfields to further suppress the content
if ( $suppress ) {
$bitfield = 0;
// This should be 15...
$bitfield |= Revision::DELETED_TEXT;
$bitfield |= Revision::DELETED_COMMENT;
$bitfield |= Revision::DELETED_USER;
$bitfield |= Revision::DELETED_RESTRICTED;
} else {
$bitfield = 'rev_deleted';
}
$dbw->begin();
// For now, shunt the revision data into the archive table.
// Text is *not* removed from the text table; bulk storage
// is left intact to avoid breaking block-compression or
// immutable storage schemes.
//
// For backwards compatibility, note that some older archive
// table entries will have ar_text and ar_flags fields still.
//
// In the future, we may keep revisions and mark them with
// the rev_deleted field, which is reserved for this purpose.
$dbw->insertSelect( 'archive', array( 'page', 'revision' ),
array(
'Suppress' => $suppress !== false,
'Reason' => $reason,
),
$commit
'ar_namespace' => 'page_namespace',
'ar_title' => 'page_title',
'ar_comment' => 'rev_comment',
'ar_user' => 'rev_user',
'ar_user_text' => 'rev_user_text',
'ar_timestamp' => 'rev_timestamp',
'ar_minor_edit' => 'rev_minor_edit',
'ar_rev_id' => 'rev_id',
'ar_text_id' => 'rev_text_id',
'ar_text' => '\'\'', // Be explicit to appease
'ar_flags' => '\'\'', // MySQL's "strict mode"...
'ar_len' => 'rev_len',
'ar_page_id' => 'page_id',
'ar_deleted' => $bitfield
), array(
'page_id' => $id,
'page_id = rev_page'
), __METHOD__
);
# Delete restrictions for it
$dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
# Now that it's safely backed up, delete it
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
$ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
if ( !$ok ) {
$dbw->rollback();
return false;
}
# Fix category table counts
$cats = array();
$res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
foreach ( $res as $row ) {
$cats [] = $row->cl_to;
}
$this->updateCategoryCounts( array(), $cats );
# If using cascading deletes, we can skip some explicit deletes
if ( !$dbw->cascadingDeletes() ) {
$dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
if ( $wgUseTrackbacks )
$dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
# Delete outgoing links
$dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
$dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
$dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
$dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
$dbw->delete( 'externallinks', array( 'el_from' => $id ) );
$dbw->delete( 'langlinks', array( 'll_from' => $id ) );
$dbw->delete( 'redirect', array( 'rd_from' => $id ) );
}
# If using cleanup triggers, we can skip some manual deletes
if ( !$dbw->cleanupTriggers() ) {
# Clean up recentchanges entries...
$dbw->delete( 'recentchanges',
array( 'rc_type != ' . RC_LOG,
'rc_namespace' => $this->mTitle->getNamespace(),
'rc_title' => $this->mTitle->getDBkey() ),
__METHOD__ );
$dbw->delete( 'recentchanges',
array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
__METHOD__ );
}
# Clear caches
Article::onArticleDelete( $this->mTitle );
# Clear the cached article id so the interface doesn't act like we exist
$this->mTitle->resetArticleID( 0 );
# Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
$log = new LogPage( $logtype );
# Make sure logging got through
$log->addEntry( 'delete', $this->mTitle, $reason, array() );
if ( $commit ) {
$dbw->commit();
}
wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$wgUser, $reason, $id ) );
return true;
}
/**

View file

@ -14,7 +14,6 @@ global $wgAutoloadLocalClasses;
$wgAutoloadLocalClasses = array(
# Includes
'Action' => 'includes/Action.php',
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
'AlphabeticPager' => 'includes/Pager.php',
@ -52,6 +51,7 @@ $wgAutoloadLocalClasses = array(
'ConfEditorToken' => 'includes/ConfEditor.php',
'ConstantDependency' => 'includes/CacheDependency.php',
'CreativeCommonsRdf' => 'includes/Metadata.php',
'Credits' => 'includes/Credits.php',
'CSSJanus' => 'includes/libs/CSSJanus.php',
'CSSMin' => 'includes/libs/CSSMin.php',
'DependencyWrapper' => 'includes/CacheDependency.php',
@ -274,12 +274,6 @@ $wgAutoloadLocalClasses = array(
'ZhClient' => 'includes/ZhClient.php',
'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
# includes/actions
'CreditsAction' => 'includes/actions/CreditsAction.php',
'DeleteAction' => 'includes/actions/DeleteAction.php',
'UnwatchAction' => 'includes/actions/WatchAction.php',
'WatchAction' => 'includes/actions/WatchAction.php',
# includes/api
'ApiBase' => 'includes/api/ApiBase.php',
'ApiBlock' => 'includes/api/ApiBlock.php',

View file

@ -19,35 +19,34 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*
* @file
* @ingroup Actions
* @author <evan@wikitravel.org>
*/
class CreditsAction extends FormlessAction {
public function getName(){
return 'credits';
}
public function getRestriction(){
return null;
}
class Credits {
/**
* This is largely cadged from PageHistory::history
* @param $article Article object
*/
public function onView() {
public static function showPage( Article $article ) {
global $wgOut;
wfProfileIn( __METHOD__ );
if ( $this->page->getID() == 0 ) {
$wgOut->setPageTitle( $article->mTitle->getPrefixedText() );
$wgOut->setSubtitle( wfMsg( 'creditspage' ) );
$wgOut->setArticleFlag( false );
$wgOut->setArticleRelated( true );
$wgOut->setRobotPolicy( 'noindex,nofollow' );
if ( $article->mTitle->getArticleID() == 0 ) {
$s = wfMsg( 'nocredits' );
} else {
$s = $this->getCredits( -1 );
$s = self::getCredits( $article, -1 );
}
wfProfileOut( __METHOD__ );
$wgOut->addHTML( $s );
return $s;
wfProfileOut( __METHOD__ );
}
/**
@ -57,14 +56,14 @@ class CreditsAction extends FormlessAction {
* @param $showIfMax Bool: whether to contributors if there more than $cnt
* @return String: html
*/
protected function getCredits( $cnt, $showIfMax = true ) {
public static function getCredits( Article $article, $cnt, $showIfMax = true ) {
wfProfileIn( __METHOD__ );
$s = '';
if ( isset( $cnt ) && $cnt != 0 ) {
$s = self::getAuthor( $this->page );
$s = self::getAuthor( $article );
if ( $cnt > 1 || $cnt < 0 ) {
$s .= ' ' . $this->getContributors( $cnt - 1, $showIfMax );
$s .= ' ' . self::getContributors( $article, $cnt - 1, $showIfMax );
}
}
@ -99,16 +98,16 @@ class CreditsAction extends FormlessAction {
* @param $showIfMax Bool: whether to contributors if there more than $cnt
* @return String: html
*/
protected function getContributors( $cnt, $showIfMax ) {
protected static function getContributors( Article $article, $cnt, $showIfMax ) {
global $wgLang, $wgHiddenPrefs;
$contributors = $this->page->getContributors();
$contributors = $article->getContributors();
$others_link = false;
# Hmm... too many to fit!
if ( $cnt > 0 && $contributors->count() > $cnt ) {
$others_link = $this->othersLink();
$others_link = self::othersLink( $article );
if ( !$showIfMax )
return wfMsgExt( 'othercontribs', 'parsemag', $others_link, $contributors->count() );
}
@ -193,11 +192,12 @@ class CreditsAction extends FormlessAction {
$real = false;
}
$page = $user->isAnon()
? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
: $user->getUserPage();
$skin = $wgUser->getSkin();
$page = $user->isAnon() ?
SpecialPage::getTitleFor( 'Contributions', $user->getName() ) :
$user->getUserPage();
return Linker::link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
return $skin->link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
}
/**
@ -224,10 +224,11 @@ class CreditsAction extends FormlessAction {
* @param $article Article object
* @return String: html
*/
protected function othersLink() {
protected static function othersLink( Article $article ) {
global $wgUser;
return Linker::link(
$this->getTitle(),
$skin = $wgUser->getSkin();
return $skin->link(
$article->getTitle(),
wfMsgHtml( 'others' ),
array(),
array( 'action' => 'credits' ),

View file

@ -27,10 +27,12 @@ if( !defined( 'MEDIAWIKI' ) ) {
}
# Create a site configuration object. Not used for much in a default install
if ( !defined( 'MW_COMPILED' ) ) {
require_once( "$IP/includes/SiteConfiguration.php" );
if ( !defined( 'MW_PHP4' ) ) {
if ( !defined( 'MW_COMPILED' ) ) {
require_once( "$IP/includes/SiteConfiguration.php" );
}
$wgConf = new SiteConfiguration;
}
$wgConf = new SiteConfiguration;
/** @endcond */
/** MediaWiki version number */
@ -5021,38 +5023,6 @@ $wgMaxRedirectLinksRetrieved = 500;
/** @} */ # end special pages }
/*************************************************************************//**
* @name Actions
* @{
*/
/**
* Array of allowed values for the title=foo&action=<action> parameter. Syntax is:
* 'foo' => 'ClassName' Load the specified class which subclasses Action
* 'foo' => true Load the class FooAction which subclasses Action
* 'foo' => false The action is disabled; show an error message
* Unsetting core actions will probably cause things to complain loudly.
*/
$wgActions = array(
'credits' => true,
'delete' => true,
'unwatch' => true,
'watch' => true,
);
/**
* Array of disabled article actions, e.g. view, edit, dublincore, delete, etc.
* @deprecated since 1.18; just set $wgActions['action'] = false instead
*/
$wgDisabledActions = array();
/**
* Allow the "info" action, very inefficient at the moment
*/
$wgAllowPageInfo = false;
/** @} */ # end actions }
/*************************************************************************//**
* @name Robot (search engine crawler) policy
* See also $wgNoFollowLinks.
@ -5318,9 +5288,17 @@ $wgUpdateRowsPerQuery = 100;
* @{
*/
/** Allow the "info" action, very inefficient at the moment */
$wgAllowPageInfo = false;
/** Name of the external diff engine to use */
$wgExternalDiffEngine = false;
/**
* Array of disabled article actions, e.g. view, edit, dublincore, delete, etc.
*/
$wgDisabledActions = array();
/**
* Disable redirects to special pages and interwiki redirects, which use a 302
* and have no "redirected from" link. Note this is only for articles with #Redirect

View file

@ -1153,9 +1153,9 @@ class EditPage {
$dbw = wfGetDB( DB_MASTER );
$dbw->begin();
if ( $this->watchthis ) {
Action::factory( 'watch', $this->mArticle )->execute();
$this->mArticle->doWatch();
} else {
Action::factory( 'watch', $this->mArticle )->execute();
$this->mArticle->doUnwatch();
}
$dbw->commit();
}

View file

@ -125,9 +125,9 @@ class FileDeleteForm {
if( $article->doDeleteArticle( $reason, $suppress, $id, false ) ) {
global $wgRequest;
if( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
Action::factory( 'watch', $article )->execute();
$article->doWatch();
} elseif( $title->userIsWatching() ) {
Action::factory( 'unwatch', $article )->execute();
$article->doUnwatch();
}
$status = $file->delete( $reason, $suppress );
if( $status->ok ) {

View file

@ -317,9 +317,9 @@ class ProtectionForm {
}
if( $wgRequest->getCheck( 'mwProtectWatch' ) && $wgUser->isLoggedIn() ) {
Action::factory( 'watch', $this->mArticle )->execute();
$this->mArticle->doWatch();
} elseif( $this->mTitle->userIsWatching() ) {
Action::factory( 'unwatch', $this->mArticle )->execute();
$this->mArticle->doUnwatch();
}
return $ok;
}

View file

@ -270,14 +270,6 @@ if ( !$wgEnotifMinorEdits ) {
$wgHiddenPrefs[] = 'enotifminoredits';
}
# $wgDisabledActions is deprecated as of 1.18
foreach( $wgDisabledActions as $action ){
$wgActions[$action] = false;
}
if( !$wgAllowPageInfo ){
$wgActions['info'] = false;
}
if ( !$wgHtml5Version && $wgHtml5 && $wgAllowRdfaAttributes ) {
# see http://www.w3.org/TR/rdfa-in-html/#document-conformance
if ( $wgMimeType == 'application/xhtml+xml' ) {

View file

@ -471,16 +471,9 @@ class MediaWiki {
return;
}
$act = $this->getAction();
$action = $this->getAction();
$action = Action::factory( $this->getAction(), $article );
if( $action instanceof Action ){
$action->show();
wfProfileOut( __METHOD__ );
return;
}
switch( $act ) {
switch( $action ) {
case 'view':
$this->context->output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
$article->view();
@ -491,6 +484,9 @@ class MediaWiki {
$raw->view();
wfProfileOut( __METHOD__ . '-raw' );
break;
case 'watch':
case 'unwatch':
case 'delete':
case 'revert':
case 'rollback':
case 'protect':
@ -500,7 +496,7 @@ class MediaWiki {
case 'render':
case 'deletetrackback':
case 'purge':
$article->$act();
$article->$action();
break;
case 'print':
$article->view();
@ -521,6 +517,9 @@ class MediaWiki {
$rdf->show();
}
break;
case 'credits':
Credits::showPage( $article );
break;
case 'submit':
if ( session_id() == '' ) {
// Send a cookie so anons get talk message notifications
@ -533,7 +532,7 @@ class MediaWiki {
$external = $this->context->request->getVal( 'externaledit' );
$section = $this->context->request->getVal( 'section' );
$oldid = $this->context->request->getVal( 'oldid' );
if ( !$this->getVal( 'UseExternalEditor' ) || $act == 'submit' || $internal ||
if ( !$this->getVal( 'UseExternalEditor' ) || $action == 'submit' || $internal ||
$section || $oldid || ( !$this->context->user->getOption( 'externaleditor' ) && !$external ) ) {
$editor = new EditPage( $article );
$editor->submit();
@ -562,7 +561,7 @@ class MediaWiki {
$special->execute( '' );
break;
default:
if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
if ( wfRunHooks( 'UnknownAction', array( $action, $article ) ) ) {
$this->context->output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
}

View file

@ -1,476 +0,0 @@
<?php
/**
* Performs the watch and unwatch actions on a page
*
* 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
*
* @file
* @ingroup Actions
*/
class DeleteAction extends Action {
public function getName(){
return 'delete';
}
public function getRestriction(){
return 'delete';
}
protected function getDescription(){
return wfMsg( 'delete-confirm', $this->getTitle()->getPrefixedText() );
}
/**
* Check that the deletion can be executed. In addition to checking the user permissions,
* check that the page is not too big and has not already been deleted.
* @throws ErrorPageError
* @see Action::checkCanExecute
*/
protected function checkCanExecute( User $user ){
// Check that the article hasn't already been deleted
$dbw = wfGetDB( DB_MASTER );
$conds = $this->getTitle()->pageCond();
$latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
if ( $latest === false ) {
// Get the deletion log
$log = '';
LogEventsList::showLogExtract(
$log,
'delete',
$this->getTitle()->getPrefixedText()
);
$msg = new Message( 'cannotdelete' );
$msg->params( $this->getTitle()->getPrefixedText() ); // This parameter is parsed
$msg->rawParams( $log ); // This is not
throw new ErrorPageError( 'internalerror', $msg );
}
// Limit deletions of big pages
$bigHistory = $this->isBigDeletion();
if ( $bigHistory && !$user->isAllowed( 'bigdelete' ) ) {
global $wgDeleteRevisionsLimit;
throw new ErrorPageError(
'internalerror',
'delete-toobig',
$this->getContext()->lang->formatNum( $wgDeleteRevisionsLimit )
);
}
return parent::checkCanExecute( $user );
}
protected function getFormFields(){
// TODO: add more useful things here?
$infoText = Html::rawElement(
'strong',
array(),
Linker::link( $this->getTitle(), $this->getTitle()->getText() )
);
$arr = array(
'Page' => array(
'type' => 'info',
'raw' => true,
'default' => $infoText,
),
'Reason' => array(
'type' => 'selectandother',
'label-message' => 'deletecomment',
'options-message' => 'deletereason-dropdown',
'size' => '60',
'maxlength' => '255',
'default' => self::getAutoReason( $this->page),
),
);
if( $this->getUser()->isLoggedIn() ){
$arr['Watch'] = array(
'type' => 'check',
'label-message' => 'watchthis',
'default' => $this->getUser()->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching()
);
}
if( $this->getUser()->isAllowed( 'suppressrevision' ) ){
$arr['Suppress'] = array(
'type' => 'check',
'label-message' => 'revdelete-suppress',
'default' => false,
);
}
return $arr;
}
/**
* Text to go at the top of the form, before the opening fieldset
* @see Action::preText()
* @return String
*/
protected function preText() {
// If the page has a history, insert a warning
if ( $this->page->estimateRevisionCount() ) {
global $wgLang;
$link = Linker::link(
$this->getTitle(),
wfMsgHtml( 'history' ),
array( 'rel' => 'archives' ),
array( 'action' => 'history' )
);
return Html::rawElement(
'strong',
array( 'class' => 'mw-delete-warning-revisions' ),
wfMessage(
'historywarning',
$wgLang->formatNum( $this->page->estimateRevisionCount() )
)->rawParams( $link )->parse()
);
}
}
/**
* Text to go at the bottom of the form, below the closing fieldset
* @see Action::postText()
* @return string
*/
protected function postText(){
$s = '';
LogEventsList::showLogExtract(
$s,
'delete',
$this->getTitle()->getPrefixedText()
);
return Html::element( 'h2', array(), LogPage::logName( 'delete' ) ) . $s;
}
protected function alterForm( HTMLForm &$form ){
$form->setWrapperLegend( wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) );
if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
$link = Linker::link(
Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' ),
wfMsgHtml( 'delete-edit-reasonlist' ),
array(),
array( 'action' => 'edit' )
);
$form->addHeaderText( '<p class="mw-delete-editreasons">' . $link . '</p>' );
}
}
/**
* Function called on form submission. Privilege checks and validation have already been
* completed by this point; we just need to jump out to the heavy-lifting function,
* which is implemented as a static method so it can be called from other places
* TODO: make those other places call $action->execute() properly
* @see Action::onSubmit()
* @param $data Array
* @return Array|Bool
*/
public function onSubmit( $data ){
$status = self::doDeleteArticle( $this->page, $this->getContext(), $data, true );
return $status;
}
public function onSuccess(){
// Watch or unwatch, if requested
if( $this->getRequest()->getCheck( 'wpWatch' ) && $this->getUser()->isLoggedIn() ) {
Action::factory( 'watch', $this->page )->execute();
} elseif ( $this->getTitle()->userIsWatching() ) {
Action::factory( 'unwatch', $this->page )->execute();
}
$this->getOutput()->setPagetitle( wfMsg( 'actioncomplete' ) );
$this->getOutput()->addWikiMsg(
'deletedtext',
$this->getTitle()->getPrefixedText(),
'[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]'
);
$this->getOutput()->returnToMain( false );
}
/**
* @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
*/
protected function isBigDeletion() {
global $wgDeleteRevisionsLimit;
return $wgDeleteRevisionsLimit && $this->page->estimateRevisionCount() > $wgDeleteRevisionsLimit;
}
/**
* Back-end article deletion
* Deletes the article with database consistency, writes logs, purges caches
*
* @param $commit boolean defaults to true, triggers transaction end
* @return Bool|Array true if successful, error array on failure
*/
public static function doDeleteArticle( Article $page, RequestContext $context, array $data, $commit = true ) {
global $wgDeferredUpdateList, $wgUseTrackbacks;
wfDebug( __METHOD__ . "\n" );
// The normal syntax from HTMLSelectAndOtherField is for the reason to be in the form
// 'Reason' => array( <full reason>, <dropdown>, <custom> ), but it's reasonable for other
// functions to just pass 'Reason' => <reason>
$data['Reason'] = (array)$data['Reason'];
$error = null;
if ( !wfRunHooks( 'ArticleDelete', array( &$page, &$context->user, &$data['Reason'][0], &$error ) ) ) {
return $error;
}
$title = $page->getTitle();
$id = $page->getID( Title::GAID_FOR_UPDATE );
if ( $title->getDBkey() === '' || $id == 0 ) {
return false;
}
$updates = new SiteStatsUpdate( 0, 1, - (int)$page->isCountable( $page->getRawText() ), -1 );
array_push( $wgDeferredUpdateList, $updates );
// Bitfields to further suppress the content
if ( isset( $data['Suppress'] ) && $data['Suppress'] ) {
$bitfield = 0;
// This should be 15...
$bitfield |= Revision::DELETED_TEXT;
$bitfield |= Revision::DELETED_COMMENT;
$bitfield |= Revision::DELETED_USER;
$bitfield |= Revision::DELETED_RESTRICTED;
$logtype = 'suppress';
} else {
// Otherwise, leave it unchanged
$bitfield = 'rev_deleted';
$logtype = 'delete';
}
$dbw = wfGetDB( DB_MASTER );
$dbw->begin();
// For now, shunt the revision data into the archive table.
// Text is *not* removed from the text table; bulk storage
// is left intact to avoid breaking block-compression or
// immutable storage schemes.
//
// For backwards compatibility, note that some older archive
// table entries will have ar_text and ar_flags fields still.
//
// In the future, we may keep revisions and mark them with
// the rev_deleted field, which is reserved for this purpose.
$dbw->insertSelect(
'archive',
array( 'page', 'revision' ),
array(
'ar_namespace' => 'page_namespace',
'ar_title' => 'page_title',
'ar_comment' => 'rev_comment',
'ar_user' => 'rev_user',
'ar_user_text' => 'rev_user_text',
'ar_timestamp' => 'rev_timestamp',
'ar_minor_edit' => 'rev_minor_edit',
'ar_rev_id' => 'rev_id',
'ar_text_id' => 'rev_text_id',
'ar_text' => "''", // Be explicit to appease
'ar_flags' => "''", // MySQL's "strict mode"...
'ar_len' => 'rev_len',
'ar_page_id' => 'page_id',
'ar_deleted' => $bitfield
),
array(
'page_id' => $id,
'page_id = rev_page'
),
__METHOD__
);
// Delete restrictions for it
$dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
// Now that it's safely backed up, delete it
$dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
// getArticleId() uses slave, could be laggy
if ( $dbw->affectedRows() == 0 ) {
$dbw->rollback();
return false;
}
// Fix category table counts
$res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
$cats = array();
foreach ( $res as $row ) {
$cats[] = $row->cl_to;
}
$page->updateCategoryCounts( array(), $cats );
// If using cascading deletes, we can skip some explicit deletes
if ( !$dbw->cascadingDeletes() ) {
$dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
if ( $wgUseTrackbacks ){
$dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
}
// Delete outgoing links
$dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
$dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
$dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
$dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
$dbw->delete( 'externallinks', array( 'el_from' => $id ) );
$dbw->delete( 'langlinks', array( 'll_from' => $id ) );
$dbw->delete( 'redirect', array( 'rd_from' => $id ) );
}
// If using cleanup triggers, we can skip some manual deletes
if ( !$dbw->cleanupTriggers() ) {
// Clean up recentchanges entries...
$dbw->delete( 'recentchanges',
array(
'rc_type != ' . RC_LOG,
'rc_namespace' => $title->getNamespace(),
'rc_title' => $title->getDBkey() ),
__METHOD__
);
$dbw->delete(
'recentchanges',
array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
__METHOD__
);
}
// Clear caches
// TODO: should this be in here or left in Article?
Article::onArticleDelete( $title );
// Clear the cached article id so the interface doesn't act like we exist
$title->resetArticleID( 0 );
// Log the deletion, if the page was suppressed, log it at Oversight instead
$log = new LogPage( $logtype );
// Make sure logging got through
$log->addEntry( 'delete', $title, $data['Reason'][0], array() );
if ( $commit ) {
$dbw->commit();
}
wfRunHooks( 'ArticleDeleteComplete', array( &$page, &$context->user, $data['Reason'][0], $id ) );
return true;
}
/**
* Auto-generates a deletion reason. Also sets $this->hasHistory if the page has old
* revisions.
*
* @return mixed String containing default reason or empty string, or boolean false
* if no revision was found
*/
public static function getAutoReason( Article $page ) {
global $wgContLang;
$dbw = wfGetDB( DB_MASTER );
// Get the last revision
$rev = Revision::newFromTitle( $page->getTitle() );
if ( is_null( $rev ) ) {
return false;
}
// Get the article's contents
$contents = $rev->getText();
$blank = false;
// If the page is blank, use the text from the previous revision,
// which can only be blank if there's a move/import/protect dummy revision involved
if ( $contents == '' ) {
$prev = $rev->getPrevious();
if ( $prev ) {
$contents = $prev->getText();
$blank = true;
}
}
// Find out if there was only one contributor
// Only scan the last 20 revisions
$res = $dbw->select( 'revision', 'rev_user_text',
array(
'rev_page' => $page->getID(),
$dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
),
__METHOD__,
array( 'LIMIT' => 20 )
);
if ( $res === false ) {
// This page has no revisions, which is very weird
return false;
}
$row = $dbw->fetchObject( $res );
if ( $row ) { // $row is false if the only contributor is hidden
$onlyAuthor = $row->rev_user_text;
// Try to find a second contributor
foreach ( $res as $row ) {
if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
$onlyAuthor = false;
break;
}
}
} else {
$onlyAuthor = false;
}
// Generate the summary with a '$1' placeholder
if ( $blank ) {
// The current revision is blank and the one before is also
// blank. It's just not our lucky day
$reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
} else {
if ( $onlyAuthor ) {
$reason = wfMessage( 'excontentauthor', '$1', $onlyAuthor )->inContentLanguage()->text();
} else {
$reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
}
}
if ( $reason == '-' ) {
// Allow these UI messages to be blanked out cleanly
return '';
}
// Replace newlines with spaces to prevent uglyness
$contents = preg_replace( "/[\n\r]/", ' ', $contents );
// Calculate the maximum number of chars to get
// Max content length = max comment length - length of the comment (excl. $1)
$maxLength = 255 - ( strlen( $reason ) - 2 );
$contents = $wgContLang->truncate( $contents, $maxLength );
// Remove possible unfinished links
$contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
// Now replace the '$1' placeholder
$reason = str_replace( '$1', $contents, $reason );
return $reason;
}
}

View file

@ -1,82 +0,0 @@
<?php
/**
* Performs the watch and unwatch actions on a page
*
* 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
*
* @file
* @ingroup Actions
*/
class WatchAction extends FormlessAction {
public function getName(){
return 'watch';
}
public function getRestriction(){
return 'read';
}
protected function getDescription(){
return wfMsg( 'addedwatch' );
}
protected function checkCanExecute( User $user ){
if ( $user->isAnon() ) {
throw new ErrorPageError( 'watchnologin', 'watchnologintext' );
}
return parent::checkCanExecute( $user );
}
public function onView() {
wfProfileIn( __METHOD__ );
$user = $this->getUser();
if ( wfRunHooks( 'WatchArticle', array( &$user, &$this->page ) ) ) {
$this->getUser()->addWatch( $this->getTitle() );
wfRunHooks( 'WatchArticleComplete', array( &$user, &$this->page ) );
}
wfProfileOut( __METHOD__ );
return wfMessage( 'addedwatchtext', $this->getTitle()->getPrefixedText() )->parse();
}
}
class UnwatchAction extends WatchAction {
public function getName(){
return 'unwatch';
}
protected function getDescription(){
return wfMsg( 'removedwatch' );
}
public function onView() {
wfProfileIn( __METHOD__ );
$user = $this->getUser();
if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$this->page ) ) ) {
$this->getUser()->removeWatch( $this->getTitle() );
wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$this->page ) );
}
wfProfileOut( __METHOD__ );
return wfMessage( 'removedwatchtext', $this->getTitle()->getPrefixedText() )->parse();
}
}

View file

@ -645,9 +645,9 @@ abstract class ApiBase {
$articleObj = new Article( $titleObj );
if ( $value ) {
Action::factory( 'watch', $articleObj )->execute();
$articleObj->doWatch();
} else {
Action::factory( 'unwatch', $articleObj )->execute();
$articleObj->doUnwatch();
}
}

View file

@ -123,6 +123,11 @@ class ApiDelete extends ApiBase {
* @return Title::getUserPermissionsErrors()-like array
*/
public static function delete( &$article, $token, &$reason = null ) {
global $wgUser;
if ( $article->isBigDeletion() && !$wgUser->isAllowed( 'bigdelete' ) ) {
global $wgDeleteRevisionsLimit;
return array( array( 'delete-toobig', $wgDeleteRevisionsLimit ) );
}
$title = $article->getTitle();
$errors = self::getPermissionsError( $title, $token );
if ( count( $errors ) ) {
@ -131,28 +136,21 @@ class ApiDelete extends ApiBase {
// Auto-generate a summary, if necessary
if ( is_null( $reason ) ) {
$reason = DeleteAction::getAutoReason( $article );
// Need to pass a throwaway variable because generateReason expects
// a reference
$hasHistory = false;
$reason = $article->generateReason( $hasHistory );
if ( $reason === false ) {
return array( array( 'cannotdelete' ) );
}
}
$action = Action::factory( 'delete', $article );
$data = array(
'Reason' => $reason,
'Suppress' => false, // The thought of people doing this through the API is scary...
);
try {
$action->execute( $data, false );
}
catch ( ErrorPageError $e ){
if( $e->msg == 'delete-toobig' ){
global $wgDeleteRevisionsLimit;
return array( array( 'delete-toobig', $wgDeleteRevisionsLimit ) );
} else {
array( array( 'cannotdelete', $article->mTitle->getPrefixedText() ) );
}
$error = '';
// Luckily, Article.php provides a reusable delete function that does the hard work for us
if ( $article->doDeleteArticle( $reason, false, 0, true, $error ) ) {
return array();
} else {
return array( array( 'cannotdelete', $article->mTitle->getPrefixedText() ) );
}
}

View file

@ -59,11 +59,11 @@ class ApiWatch extends ApiBase {
if ( $params['unwatch'] ) {
$res['unwatched'] = '';
$res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
$success = Action::factory( 'unwatch', $article )->execute();
$success = $article->doUnwatch();
} else {
$res['watched'] = '';
$res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
$success = Action::factory( 'watch', $article )->execute();
$success = $article->doWatch();
}
if ( !$success ) {
$this->dieUsageMsg( array( 'hookaborted' ) );

View file

@ -359,11 +359,8 @@ class MovePageForm extends UnlistedSpecialPage {
$article = new Article( $nt );
# Disallow deletions of big articles
global $wgDeleteRevisionsLimit;
if ( $wgDeleteRevisionsLimit
&& $this->estimateRevisionCount() > $wgDeleteRevisionsLimit
&& !$nt->userCan( 'bigdelete' ) )
{
$bigHistory = $article->isBigDeletion();
if( $bigHistory && !$nt->userCan( 'bigdelete' ) ) {
global $wgDeleteRevisionsLimit;
$this->showForm( array('delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
return;
@ -376,10 +373,7 @@ class MovePageForm extends UnlistedSpecialPage {
}
// This may output an error message and exit
Action::factory( 'delete', $article )->execute(
array( 'Reason' => wfMsgForContent( 'delete_and_move_reason' ) ),
false // Do not capture exceptions
);
$article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) );
}
# don't allow moving to pages with # in

View file

@ -998,9 +998,8 @@ Please report this to an [[Special:ListUsers/sysop|administrator]], making note
'unexpected' => 'Unexpected value: "$1"="$2".',
'formerror' => 'Error: could not submit form',
'badarticleerror' => 'This action cannot be performed on this page.',
'cannotdelete' => 'The page or file "$1" could not be deleted. It may have already been deleted by someone else. The deletion log is provided below for convenience.
$2',
'cannotdelete' => 'The page or file "$1" could not be deleted.
It may have already been deleted by someone else.',
'badtitle' => 'Bad title',
'badtitletext' => 'The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title.
It may contain one or more characters which cannot be used in titles.',
@ -2784,7 +2783,7 @@ Feedback and further assistance:
'delete-confirm' => 'Delete "$1"',
'delete-backlink' => '← $1', # only translate this message to other languages if you have to change it
'delete-legend' => 'Delete',
'historywarning' => "'''Warning:''' The page you are about to delete has a $2 with approximately $1 {{PLURAL:$1|revision|revisions}}:",
'historywarning' => "'''Warning:''' The page you are about to delete has a history with approximately $1 {{PLURAL:$1|revision|revisions}}:",
'confirmdeletetext' => 'You are about to delete a page along with all of its history.
Please confirm that you intend to do this, that you understand the consequences, and that you are doing this in accordance with [[{{MediaWiki:Policy-url}}|the policy]].',
'actioncomplete' => 'Action complete',