From: Happy-melon Date: Wed, 13 Apr 2011 23:36:27 +0000 (+0000) Subject: Revert r86001: Brion says it's too scary :D will recommit in pieces X-Git-Tag: 1.31.0-rc.0~30862 X-Git-Url: https://git.heureux-cyclage.org/?a=commitdiff_plain;h=bc4a096805e87a11cb12ef0211f92313fff4d688;p=lhc%2Fweb%2Fwiklou.git Revert r86001: Brion says it's too scary :D will recommit in pieces --- diff --git a/docs/hooks.txt b/docs/hooks.txt index 1fe061c0ce..9935299a0e 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -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 diff --git a/includes/Action.php b/includes/Action.php deleted file mode 100644 index 44fafcaa77..0000000000 --- a/includes/Action.php +++ /dev/null @@ -1,440 +0,0 @@ -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 \ 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; - } - } - } -} \ No newline at end of file diff --git a/includes/Article.php b/includes/Article.php index 97eb17b147..b9288c21e5 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -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( "
\n$1\n
\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( '' . + wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) . + wfMsgHtml( 'word-separator' ) . $skin->link( $this->mTitle, + wfMsgHtml( 'history' ), + array( 'rel' => 'archives' ), + array( 'action' => 'history' ) ) . + '' + ); + + if ( $bigHistory ) { + global $wgDeleteRevisionsLimit; + $wgOut->wrapWikiMsg( "
\n$1\n
\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 = " + + " . + Xml::checkLabel( wfMsg( 'revdelete-suppress' ), + 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) . + " + "; + } 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' ) ) . + " + " . + Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' ) . + " + " . + Xml::listDropDown( 'wpDeleteReasonList', + wfMsgForContent( 'deletereason-dropdown' ), + wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) . + " + + + " . + Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) . + " + " . + Html::input( 'wpReason', $reason, 'text', array( + 'size' => '60', + 'maxlength' => '255', + 'tabindex' => '2', + 'id' => 'wpReason', + 'autofocus' + ) ) . + " + "; + + # Disallow watching if user is not logged in + if ( $wgOut->getUser()->isLoggedIn() ) { + $form .= " + + + " . + Xml::checkLabel( wfMsg( 'watchthis' ), + 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) . + " + "; + } + + $form .= " + $suppress + + + " . + Xml::submitButton( wfMsg( 'deletepage' ), + array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) . + " + " . + 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 .= '

' . $link . '

'; + } + + $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; } /** diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index ccf3123512..9ba9d863c5 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -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', diff --git a/includes/Credits.php b/includes/Credits.php new file mode 100644 index 0000000000..e4c8be54e2 --- /dev/null +++ b/includes/Credits.php @@ -0,0 +1,238 @@ +. + * + * 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 + * @author + */ + +class Credits { + /** + * This is largely cadged from PageHistory::history + * @param $article Article object + */ + public static function showPage( Article $article ) { + global $wgOut; + + wfProfileIn( __METHOD__ ); + + $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 = self::getCredits( $article, -1 ); + } + + $wgOut->addHTML( $s ); + + wfProfileOut( __METHOD__ ); + } + + /** + * Get a list of contributors of $article + * @param $article Article object + * @param $cnt Int: maximum list of contributors to show + * @param $showIfMax Bool: whether to contributors if there more than $cnt + * @return String: html + */ + public static function getCredits( Article $article, $cnt, $showIfMax = true ) { + wfProfileIn( __METHOD__ ); + $s = ''; + + if ( isset( $cnt ) && $cnt != 0 ) { + $s = self::getAuthor( $article ); + if ( $cnt > 1 || $cnt < 0 ) { + $s .= ' ' . self::getContributors( $article, $cnt - 1, $showIfMax ); + } + } + + wfProfileOut( __METHOD__ ); + return $s; + } + + /** + * Get the last author with the last modification time + * @param $article Article object + */ + protected static function getAuthor( Article $article ) { + global $wgLang; + + $user = User::newFromId( $article->getUser() ); + + $timestamp = $article->getTimestamp(); + if ( $timestamp ) { + $d = $wgLang->date( $article->getTimestamp(), true ); + $t = $wgLang->time( $article->getTimestamp(), true ); + } else { + $d = ''; + $t = ''; + } + return wfMsgExt( 'lastmodifiedatby', 'parsemag', $d, $t, self::userLink( $user ), $user->getName() ); + } + + /** + * Get a list of contributors of $article + * @param $article Article object + * @param $cnt Int: maximum list of contributors to show + * @param $showIfMax Bool: whether to contributors if there more than $cnt + * @return String: html + */ + protected static function getContributors( Article $article, $cnt, $showIfMax ) { + global $wgLang, $wgHiddenPrefs; + + $contributors = $article->getContributors(); + + $others_link = false; + + # Hmm... too many to fit! + if ( $cnt > 0 && $contributors->count() > $cnt ) { + $others_link = self::othersLink( $article ); + if ( !$showIfMax ) + return wfMsgExt( 'othercontribs', 'parsemag', $others_link, $contributors->count() ); + } + + $real_names = array(); + $user_names = array(); + $anon_ips = array(); + + # Sift for real versus user names + foreach ( $contributors as $user ) { + $cnt--; + if ( $user->isLoggedIn() ) { + $link = self::link( $user ); + if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) { + $real_names[] = $link; + } else { + $user_names[] = $link; + } + } else { + $anon_ips[] = self::link( $user ); + } + + if ( $cnt == 0 ) { + break; + } + } + + if ( count( $real_names ) ) { + $real = $wgLang->listToText( $real_names ); + } else { + $real = false; + } + + # "ThisSite user(s) A, B and C" + if ( count( $user_names ) ) { + $user = wfMsgExt( + 'siteusers', + 'parsemag', + $wgLang->listToText( $user_names ), count( $user_names ) + ); + } else { + $user = false; + } + + if ( count( $anon_ips ) ) { + $anon = wfMsgExt( + 'anonusers', + 'parsemag', + $wgLang->listToText( $anon_ips ), count( $anon_ips ) + ); + } else { + $anon = false; + } + + # This is the big list, all mooshed together. We sift for blank strings + $fulllist = array(); + foreach ( array( $real, $user, $anon, $others_link ) as $s ) { + if ( $s ) { + array_push( $fulllist, $s ); + } + } + + # Make the list into text... + $creds = $wgLang->listToText( $fulllist ); + + # "Based on work by ..." + return strlen( $creds ) + ? wfMsgExt( 'othercontribs', 'parsemag', $creds, count( $fulllist ) ) + : ''; + } + + /** + * Get a link to $user's user page + * @param $user User object + * @return String: html + */ + protected static function link( User $user ) { + global $wgUser, $wgHiddenPrefs; + if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) { + $real = $user->getRealName(); + } else { + $real = false; + } + + $skin = $wgUser->getSkin(); + $page = $user->isAnon() ? + SpecialPage::getTitleFor( 'Contributions', $user->getName() ) : + $user->getUserPage(); + + return $skin->link( $page, htmlspecialchars( $real ? $real : $user->getName() ) ); + } + + /** + * Get a link to $user's user page + * @param $user User object + * @return String: html + */ + protected static function userLink( User $user ) { + $link = self::link( $user ); + if ( $user->isAnon() ) { + return wfMsgExt( 'anonuser', array( 'parseinline', 'replaceafter' ), $link ); + } else { + global $wgHiddenPrefs; + if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) { + return $link; + } else { + return wfMsgExt( 'siteuser', 'parsemag', $link, $user->getName() ); + } + } + } + + /** + * Get a link to action=credits of $article page + * @param $article Article object + * @return String: html + */ + protected static function othersLink( Article $article ) { + global $wgUser; + $skin = $wgUser->getSkin(); + return $skin->link( + $article->getTitle(), + wfMsgHtml( 'others' ), + array(), + array( 'action' => 'credits' ), + array( 'known' ) + ); + } +} diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index e447983b0c..ba72d76540 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -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= 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 diff --git a/includes/EditPage.php b/includes/EditPage.php index fb6f2bc71e..cafb641426 100644 --- a/includes/EditPage.php +++ b/includes/EditPage.php @@ -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(); } diff --git a/includes/FileDeleteForm.php b/includes/FileDeleteForm.php index 9101d6e4f0..f77d6978c1 100644 --- a/includes/FileDeleteForm.php +++ b/includes/FileDeleteForm.php @@ -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 ) { diff --git a/includes/ProtectionForm.php b/includes/ProtectionForm.php index 6f04e97274..86aca3e249 100644 --- a/includes/ProtectionForm.php +++ b/includes/ProtectionForm.php @@ -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; } diff --git a/includes/Setup.php b/includes/Setup.php index a7bf62991e..837740ee61 100644 --- a/includes/Setup.php +++ b/includes/Setup.php @@ -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' ) { diff --git a/includes/Wiki.php b/includes/Wiki.php index a095a363d3..f54e2b35fa 100644 --- a/includes/Wiki.php +++ b/includes/Wiki.php @@ -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' ); } } diff --git a/includes/actions/CreditsAction.php b/includes/actions/CreditsAction.php deleted file mode 100644 index 576834faaa..0000000000 --- a/includes/actions/CreditsAction.php +++ /dev/null @@ -1,237 +0,0 @@ -. - * - * 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 - * @author - */ - -class CreditsAction extends FormlessAction { - - public function getName(){ - return 'credits'; - } - - public function getRestriction(){ - return null; - } - - /** - * This is largely cadged from PageHistory::history - */ - public function onView() { - wfProfileIn( __METHOD__ ); - - if ( $this->page->getID() == 0 ) { - $s = wfMsg( 'nocredits' ); - } else { - $s = $this->getCredits( -1 ); - } - - wfProfileOut( __METHOD__ ); - - return $s; - } - - /** - * Get a list of contributors of $article - * @param $article Article object - * @param $cnt Int: maximum list of contributors to show - * @param $showIfMax Bool: whether to contributors if there more than $cnt - * @return String: html - */ - protected function getCredits( $cnt, $showIfMax = true ) { - wfProfileIn( __METHOD__ ); - $s = ''; - - if ( isset( $cnt ) && $cnt != 0 ) { - $s = self::getAuthor( $this->page ); - if ( $cnt > 1 || $cnt < 0 ) { - $s .= ' ' . $this->getContributors( $cnt - 1, $showIfMax ); - } - } - - wfProfileOut( __METHOD__ ); - return $s; - } - - /** - * Get the last author with the last modification time - * @param $article Article object - */ - protected static function getAuthor( Article $article ) { - global $wgLang; - - $user = User::newFromId( $article->getUser() ); - - $timestamp = $article->getTimestamp(); - if ( $timestamp ) { - $d = $wgLang->date( $article->getTimestamp(), true ); - $t = $wgLang->time( $article->getTimestamp(), true ); - } else { - $d = ''; - $t = ''; - } - return wfMsgExt( 'lastmodifiedatby', 'parsemag', $d, $t, self::userLink( $user ), $user->getName() ); - } - - /** - * Get a list of contributors of $article - * @param $article Article object - * @param $cnt Int: maximum list of contributors to show - * @param $showIfMax Bool: whether to contributors if there more than $cnt - * @return String: html - */ - protected function getContributors( $cnt, $showIfMax ) { - global $wgLang, $wgHiddenPrefs; - - $contributors = $this->page->getContributors(); - - $others_link = false; - - # Hmm... too many to fit! - if ( $cnt > 0 && $contributors->count() > $cnt ) { - $others_link = $this->othersLink(); - if ( !$showIfMax ) - return wfMsgExt( 'othercontribs', 'parsemag', $others_link, $contributors->count() ); - } - - $real_names = array(); - $user_names = array(); - $anon_ips = array(); - - # Sift for real versus user names - foreach ( $contributors as $user ) { - $cnt--; - if ( $user->isLoggedIn() ) { - $link = self::link( $user ); - if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) { - $real_names[] = $link; - } else { - $user_names[] = $link; - } - } else { - $anon_ips[] = self::link( $user ); - } - - if ( $cnt == 0 ) { - break; - } - } - - if ( count( $real_names ) ) { - $real = $wgLang->listToText( $real_names ); - } else { - $real = false; - } - - # "ThisSite user(s) A, B and C" - if ( count( $user_names ) ) { - $user = wfMsgExt( - 'siteusers', - 'parsemag', - $wgLang->listToText( $user_names ), count( $user_names ) - ); - } else { - $user = false; - } - - if ( count( $anon_ips ) ) { - $anon = wfMsgExt( - 'anonusers', - 'parsemag', - $wgLang->listToText( $anon_ips ), count( $anon_ips ) - ); - } else { - $anon = false; - } - - # This is the big list, all mooshed together. We sift for blank strings - $fulllist = array(); - foreach ( array( $real, $user, $anon, $others_link ) as $s ) { - if ( $s ) { - array_push( $fulllist, $s ); - } - } - - # Make the list into text... - $creds = $wgLang->listToText( $fulllist ); - - # "Based on work by ..." - return strlen( $creds ) - ? wfMsgExt( 'othercontribs', 'parsemag', $creds, count( $fulllist ) ) - : ''; - } - - /** - * Get a link to $user's user page - * @param $user User object - * @return String: html - */ - protected static function link( User $user ) { - global $wgUser, $wgHiddenPrefs; - if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) { - $real = $user->getRealName(); - } else { - $real = false; - } - - $page = $user->isAnon() - ? SpecialPage::getTitleFor( 'Contributions', $user->getName() ) - : $user->getUserPage(); - - return Linker::link( $page, htmlspecialchars( $real ? $real : $user->getName() ) ); - } - - /** - * Get a link to $user's user page - * @param $user User object - * @return String: html - */ - protected static function userLink( User $user ) { - $link = self::link( $user ); - if ( $user->isAnon() ) { - return wfMsgExt( 'anonuser', array( 'parseinline', 'replaceafter' ), $link ); - } else { - global $wgHiddenPrefs; - if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) { - return $link; - } else { - return wfMsgExt( 'siteuser', 'parsemag', $link, $user->getName() ); - } - } - } - - /** - * Get a link to action=credits of $article page - * @param $article Article object - * @return String: html - */ - protected function othersLink() { - global $wgUser; - return Linker::link( - $this->getTitle(), - wfMsgHtml( 'others' ), - array(), - array( 'action' => 'credits' ), - array( 'known' ) - ); - } -} diff --git a/includes/actions/DeleteAction.php b/includes/actions/DeleteAction.php deleted file mode 100644 index 3f8097f3ac..0000000000 --- a/includes/actions/DeleteAction.php +++ /dev/null @@ -1,476 +0,0 @@ -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( '

' . $link . '

' ); - } - } - - /** - * 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( , , ), but it's reasonable for other - // functions to just pass '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; - } -} diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php deleted file mode 100644 index e3fb31b620..0000000000 --- a/includes/actions/WatchAction.php +++ /dev/null @@ -1,82 +0,0 @@ -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(); - } -} diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index fe6ce54645..336bc79e03 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -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(); } } diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php index 3a61ccd85a..0eba21956c 100644 --- a/includes/api/ApiDelete.php +++ b/includes/api/ApiDelete.php @@ -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() ) ); } } diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php index 685306dff3..fe52328ef5 100644 --- a/includes/api/ApiWatch.php +++ b/includes/api/ApiWatch.php @@ -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' ) ); diff --git a/includes/specials/SpecialMovepage.php b/includes/specials/SpecialMovepage.php index f6259c24c7..2be33d0fdf 100644 --- a/includes/specials/SpecialMovepage.php +++ b/includes/specials/SpecialMovepage.php @@ -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 diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 5239e65b21..d00d9602df 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -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',