r86001, now with less scariness :P I took out the delete action and did purge instea...
authorHappy-melon <happy-melon@users.mediawiki.org>
Thu, 14 Apr 2011 10:38:29 +0000 (10:38 +0000)
committerHappy-melon <happy-melon@users.mediawiki.org>
Thu, 14 Apr 2011 10:38:29 +0000 (10:38 +0000)
16 files changed:
includes/Action.php [new file with mode: 0644]
includes/Article.php
includes/AutoLoader.php
includes/Credits.php [deleted file]
includes/DefaultSettings.php
includes/EditPage.php
includes/FileDeleteForm.php
includes/ProtectionForm.php
includes/Setup.php
includes/Wiki.php
includes/actions/CreditsAction.php [new file with mode: 0644]
includes/actions/DeleteAction.php [new file with mode: 0644]
includes/actions/PurgeAction.php [new file with mode: 0644]
includes/actions/WatchAction.php [new file with mode: 0644]
includes/api/ApiBase.php
includes/api/ApiWatch.php

diff --git a/includes/Action.php b/includes/Action.php
new file mode 100644 (file)
index 0000000..609e8e0
--- /dev/null
@@ -0,0 +1,441 @@
+<?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 $class;
+       }
+
+       /**
+        * 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 User 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 main action entry point.  Do all output for display and send it to the context
+        * output.  Do not use globals $wgOut, $wgRequest, etc, in implementations; use
+        * $this->getOutput(), etc.
+        * @throws ErrorPageError
+        */
+       public abstract function show();
+
+       /**
+        * 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 abstract function execute();
+}
+
+abstract class FormAction extends Action {
+
+       /**
+        * Get an HTMLForm descriptor array
+        * @return Array
+        */
+       protected abstract function getFormFields();
+
+       /**
+        * Add pre- or post-text to the form
+        * @return String HTML which will be sent to $form->addPreText()
+        */
+       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 ) );
+
+               $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();
+
+       /**
+        * 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->show() ){
+                       $this->onSuccess();
+               }
+       }
+
+       /**
+        * @see Action::execute()
+        * @throws ErrorPageError
+        * @param array|null $data
+        * @param bool $captureErrors
+        * @return bool
+        */
+       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() );
+
+                       $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;
+                       }
+               }
+               catch ( ErrorPageError $e ){
+                       if( $captureErrors ){
+                               return false;
+                       } else {
+                               throw $e;
+                       }
+               }
+       }
+}
+
+/**
+ * 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.
+        * @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
index b9288c2..55cf08d 100644 (file)
@@ -1672,32 +1672,7 @@ class Article {
         * Handle action=purge
         */
        public function purge() {
-               global $wgRequest, $wgOut;
-
-               if ( $wgOut->getUser()->isAllowed( 'purge' ) || $wgRequest->wasPosted() ) {
-                       //FIXME: shouldn't this be in doPurge()?
-                       if ( wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
-                               $this->doPurge();
-                               $this->view();
-                       }
-               } else {
-                       $formParams = array(
-                               'method' => 'post',
-                               'action' =>  $wgRequest->getRequestURL(),
-                       );
-
-                       $wgOut->addWikiMsg( 'confirm-purge-top' );
-
-                       $form  = Html::openElement( 'form', $formParams );
-                       $form .= Xml::submitButton( wfMsg( 'confirm_purge_button' ) );
-                       $form .= Html::closeElement( 'form' );
-
-                       $wgOut->addHTML( $form );
-                       $wgOut->addWikiMsg( 'confirm-purge-bottom' );
-
-                       $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
-                       $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               }
+               return Action::factory( 'purge', $this )->show();
        }
 
        /**
@@ -1706,6 +1681,10 @@ class Article {
        public function doPurge() {
                global $wgUseSquid;
 
+               if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
+                       return false;
+               }
+
                // Invalidate the cache
                $this->mTitle->invalidateCache();
                $this->clear();
@@ -2345,27 +2324,10 @@ class Article {
 
        /**
         * User-interface handler for the "watch" action
+        * @deprecated since 1.18
         */
        public function watch() {
-               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() );
+               Action::factory( 'watch', $this )->show();
        }
 
        /**
@@ -2374,64 +2336,27 @@ class Article {
         * This is safe to be called multiple times
         *
         * @return bool true on successful watch operation
+        * @deprecated since 1.18
         */
        public function doWatch() {
-               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;
+               return Action::factory( 'watch', $this )->execute();
        }
 
        /**
         * User interface handler for the "unwatch" action.
+        * @deprecated since 1.18
         */
        public function unwatch() {
-               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() );
+               Action::factory( 'unwatch', $this )->show();
        }
 
        /**
         * Stop watching a page
         * @return bool true on successful unwatch
+        * @deprecated since 1.18
         */
        public function doUnwatch() {
-               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;
+               return Action::factory( 'unwatch', $this )->execute();
        }
 
        /**
index 9ba9d86..54d62e8 100644 (file)
@@ -14,6 +14,7 @@ global $wgAutoloadLocalClasses;
 
 $wgAutoloadLocalClasses = array(
        # Includes
+       'Action' => 'includes/Action.php',
        'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
        'AjaxResponse' => 'includes/AjaxResponse.php',
        'AlphabeticPager' => 'includes/Pager.php',
@@ -51,7 +52,6 @@ $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,6 +274,12 @@ $wgAutoloadLocalClasses = array(
        'ZhClient' => 'includes/ZhClient.php',
        'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
 
+       # includes/actions
+       'CreditsAction' => 'includes/actions/CreditsAction.php',
+       'PurgeAction' => 'includes/actions/PurgeAction.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
deleted file mode 100644 (file)
index e4c8be5..0000000
+++ /dev/null
@@ -1,238 +0,0 @@
-<?php
-/**
- * Formats credits for articles
- *
- * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
- *
- * @file
- * @author <evan@wikitravel.org>
- */
-
-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' )
-               );
-       }
-}
index ba72d76..33884fd 100644 (file)
@@ -27,12 +27,10 @@ if( !defined( 'MEDIAWIKI' ) ) {
 }
 
 # Create a site configuration object. Not used for much in a default install
-if ( !defined( 'MW_PHP4' ) ) {
-       if ( !defined( 'MW_COMPILED' ) ) {
-               require_once( "$IP/includes/SiteConfiguration.php" );
-       }
-       $wgConf = new SiteConfiguration;
+if ( !defined( 'MW_COMPILED' ) ) {
+       require_once( "$IP/includes/SiteConfiguration.php" );
 }
+$wgConf = new SiteConfiguration;
 /** @endcond */
 
 /** MediaWiki version number */
@@ -5023,6 +5021,38 @@ $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,
+       'purge' => 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.
@@ -5288,17 +5318,9 @@ $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
index cafb641..fb6f2bc 100644 (file)
@@ -1153,9 +1153,9 @@ class EditPage {
                        $dbw = wfGetDB( DB_MASTER );
                        $dbw->begin();
                        if ( $this->watchthis ) {
-                               $this->mArticle->doWatch();
+                               Action::factory( 'watch', $this->mArticle )->execute();
                        } else {
-                               $this->mArticle->doUnwatch();
+                               Action::factory( 'watch', $this->mArticle )->execute();
                        }
                        $dbw->commit();
                }
index f77d697..9101d6e 100644 (file)
@@ -125,9 +125,9 @@ class FileDeleteForm {
                                if( $article->doDeleteArticle( $reason, $suppress, $id, false ) ) {
                                        global $wgRequest;
                                        if( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
-                                               $article->doWatch();
+                                               Action::factory( 'watch', $article )->execute();
                                        } elseif( $title->userIsWatching() ) {
-                                               $article->doUnwatch();
+                                               Action::factory( 'unwatch', $article )->execute();
                                        }
                                        $status = $file->delete( $reason, $suppress );
                                        if( $status->ok ) {
index 86aca3e..6f04e97 100644 (file)
@@ -317,9 +317,9 @@ class ProtectionForm {
                }
 
                if( $wgRequest->getCheck( 'mwProtectWatch' ) && $wgUser->isLoggedIn() ) {
-                       $this->mArticle->doWatch();
+                       Action::factory( 'watch', $this->mArticle )->execute();
                } elseif( $this->mTitle->userIsWatching() ) {
-                       $this->mArticle->doUnwatch();
+                       Action::factory( 'unwatch', $this->mArticle )->execute();
                }
                return $ok;
        }
index 837740e..a7bf629 100644 (file)
@@ -270,6 +270,14 @@ 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' ) {
index f54e2b3..43b2a88 100644 (file)
@@ -471,9 +471,16 @@ class MediaWiki {
                        return;
                }
 
-               $action = $this->getAction();
+               $act = $this->getAction();
 
-               switch( $action ) {
+               $action = Action::factory( $this->getAction(), $article );
+               if( $action instanceof Action ){
+                       $action->show();
+                       wfProfileOut( __METHOD__ );
+                       return;
+               }
+
+               switch( $act ) {
                        case 'view':
                                $this->context->output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
                                $article->view();
@@ -484,8 +491,6 @@ class MediaWiki {
                                $raw->view();
                                wfProfileOut( __METHOD__ . '-raw' );
                                break;
-                       case 'watch':
-                       case 'unwatch':
                        case 'delete':
                        case 'revert':
                        case 'rollback':
@@ -495,8 +500,7 @@ class MediaWiki {
                        case 'markpatrolled':
                        case 'render':
                        case 'deletetrackback':
-                       case 'purge':
-                               $article->$action();
+                               $article->$act();
                                break;
                        case 'print':
                                $article->view();
@@ -517,9 +521,6 @@ 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
@@ -532,7 +533,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' ) || $action == 'submit' || $internal ||
+                                       if ( !$this->getVal( 'UseExternalEditor' ) || $act == 'submit' || $internal ||
                                           $section || $oldid || ( !$this->context->user->getOption( 'externaleditor' ) && !$external ) ) {
                                                $editor = new EditPage( $article );
                                                $editor->submit();
@@ -561,7 +562,7 @@ class MediaWiki {
                                $special->execute( '' );
                                break;
                        default:
-                               if ( wfRunHooks( 'UnknownAction', array( $action, $article ) ) ) {
+                               if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
                                        $this->context->output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
                                }
                }
diff --git a/includes/actions/CreditsAction.php b/includes/actions/CreditsAction.php
new file mode 100644 (file)
index 0000000..576834f
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+/**
+ * Formats credits for articles
+ *
+ * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author <evan@wikitravel.org>
+ */
+
+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
new file mode 100644 (file)
index 0000000..3f8097f
--- /dev/null
@@ -0,0 +1,476 @@
+<?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;
+       }
+}
diff --git a/includes/actions/PurgeAction.php b/includes/actions/PurgeAction.php
new file mode 100644 (file)
index 0000000..e212fbf
--- /dev/null
@@ -0,0 +1,93 @@
+<?php
+/**
+ * Formats credits for articles
+ *
+ * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * @file
+ * @ingroup Actions
+ * @author <evan@wikitravel.org>
+ */
+
+class PurgeAction extends FormAction {
+
+       public function getName(){
+               return 'purge';
+       }
+
+       public function getRestriction(){
+               return null;
+       }
+
+       public function requiresUnblock(){
+               return false;
+       }
+
+       public function getDescription(){
+               return '';
+       }
+
+       /**
+        * Just get an empty form with a single submit button
+        * @return array
+        */
+       protected function getFormFields(){
+               return array();
+       }
+
+       public function onSubmit( $data ){
+               $this->page->doPurge();
+               return true;
+       }
+
+       /**
+        * purge is slightly wierd because it can be either formed or formless depending
+        * on user permissions
+        */
+       public function show(){
+               $this->setHeaders();
+
+               // This will throw exceptions if there's a problem
+               $this->checkCanExecute( $this->getUser() );
+
+               if( $this->getUser()->isAllowed( 'purge' ) ){
+                       $this->onSubmit( array() );
+                       $this->onSuccess();
+               } else {
+                       $form = $this->getForm();
+                       if( $form->show() ){
+                               $this->onSuccess();
+                       }
+               }
+       }
+
+       protected function alterForm( HTMLForm $form ){
+               $form->setSubmitText( wfMsg( 'confirm_purge_button' ) );
+       }
+
+       protected function preText(){
+               return wfMessage( 'confirm-purge-top' )->parse();
+       }
+
+       protected function postText(){
+               return wfMessage( 'confirm-purge-bottom' )->parse();
+       }
+
+       public function onSuccess(){
+               $this->getOutput()->redirect( $this->getTitle() );
+       }
+}
diff --git a/includes/actions/WatchAction.php b/includes/actions/WatchAction.php
new file mode 100644 (file)
index 0000000..43e0b47
--- /dev/null
@@ -0,0 +1,86 @@
+<?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';
+       }
+
+       public function requiresUnblock(){
+               return false;
+       }
+
+       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();
+       }
+}
index 336bc79..fe6ce54 100644 (file)
@@ -645,9 +645,9 @@ abstract class ApiBase {
 
                $articleObj = new Article( $titleObj );
                if ( $value ) {
-                       $articleObj->doWatch();
+                       Action::factory( 'watch', $articleObj )->execute();
                } else {
-                       $articleObj->doUnwatch();
+                       Action::factory( 'unwatch', $articleObj )->execute();
                }
        }
 
index fe52328..685306d 100644 (file)
@@ -59,11 +59,11 @@ class ApiWatch extends ApiBase {
                if ( $params['unwatch'] ) {
                        $res['unwatched'] = '';
                        $res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
-                       $success = $article->doUnwatch();
+                       $success = Action::factory( 'unwatch', $article )->execute();
                } else {
                        $res['watched'] = '';
                        $res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
-                       $success = $article->doWatch();
+                       $success = Action::factory( 'watch', $article )->execute();
                }
                if ( !$success ) {
                        $this->dieUsageMsg( array( 'hookaborted' ) );