--- /dev/null
+<?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
* 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();
}
/**
public function doPurge() {
global $wgUseSquid;
+ if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
+ return false;
+ }
+
// Invalidate the cache
$this->mTitle->invalidateCache();
$this->clear();
/**
* 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();
}
/**
* 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();
}
/**
$wgAutoloadLocalClasses = array(
# Includes
+ 'Action' => 'includes/Action.php',
'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
'AjaxResponse' => 'includes/AjaxResponse.php',
'AlphabeticPager' => 'includes/Pager.php',
'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',
'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',
+++ /dev/null
-<?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' )
- );
- }
-}
}
# 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 */
/** @} */ # 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.
* @{
*/
-/** 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
$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();
}
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 ) {
}
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;
}
$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' ) {
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();
$raw->view();
wfProfileOut( __METHOD__ . '-raw' );
break;
- case 'watch':
- case 'unwatch':
case 'delete':
case 'revert':
case 'rollback':
case 'markpatrolled':
case 'render':
case 'deletetrackback':
- case 'purge':
- $article->$action();
+ $article->$act();
break;
case 'print':
$article->view();
$rdf->show();
}
break;
- case 'credits':
- Credits::showPage( $article );
- break;
case 'submit':
if ( session_id() == '' ) {
// Send a cookie so anons get talk message notifications
$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();
$special->execute( '' );
break;
default:
- if ( wfRunHooks( 'UnknownAction', array( $action, $article ) ) ) {
+ if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
$this->context->output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
}
--- /dev/null
+<?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' )
+ );
+ }
+}
--- /dev/null
+<?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;
+ }
+}
--- /dev/null
+<?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() );
+ }
+}
--- /dev/null
+<?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();
+ }
+}
$articleObj = new Article( $titleObj );
if ( $value ) {
- $articleObj->doWatch();
+ Action::factory( 'watch', $articleObj )->execute();
} else {
- $articleObj->doUnwatch();
+ Action::factory( 'unwatch', $articleObj )->execute();
}
}
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' ) );