r86041 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86040‎ | r86041 | r86042 >
Date:10:38, 14 April 2011
Author:happy-melon
Status:resolved (Comments)
Tags:
Comment:
r86001, now with less scariness :P I took out the delete action and did purge instead, which is a much more self-contained action-with-a-form. Also implement a few changes suggested by Brion on IRC last night.
Modified paths:
  • /trunk/extensions/LiquidThreads/classes/Thread.php (modified) (history)
  • /trunk/extensions/MetavidWiki/includes/articlepages/MV_DataPage.php (modified) (history)
  • /trunk/extensions/MultilingualLiquidThreads/LiquidThreads/classes/Thread.php (modified) (history)
  • /trunk/extensions/ReplaceText/ReplaceTextJob.php (modified) (history)
  • /trunk/phase3/includes/Action.php (added) (history)
  • /trunk/phase3/includes/Article.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/Credits.php (deleted) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/EditPage.php (modified) (history)
  • /trunk/phase3/includes/FileDeleteForm.php (modified) (history)
  • /trunk/phase3/includes/ProtectionForm.php (modified) (history)
  • /trunk/phase3/includes/Setup.php (modified) (history)
  • /trunk/phase3/includes/Wiki.php (modified) (history)
  • /trunk/phase3/includes/actions (added) (history)
  • /trunk/phase3/includes/actions/PurgeAction.php (added) (history)
  • /trunk/phase3/includes/actions/WatchAction.php (modified) (history)
  • /trunk/phase3/includes/api/ApiBase.php (modified) (history)
  • /trunk/phase3/includes/api/ApiWatch.php (modified) (history)

Diff [purge]

Index: trunk/extensions/MultilingualLiquidThreads/LiquidThreads/classes/Thread.php
@@ -113,7 +113,7 @@
114114 NewMessages::writeMessageStateForUpdatedThread( $thread, $change_type, $wgUser );
115115
116116 if ( $wgUser->getOption( 'lqt-watch-threads', false ) ) {
117 - $thread->topmostThread()->root()->doWatch();
 117+ Action::factory( 'watch', $thread->topmostThread()->root() )->execute();
118118 }
119119
120120 return $thread;
Index: trunk/extensions/LiquidThreads/classes/Thread.php
@@ -112,7 +112,7 @@
113113 NewMessages::writeMessageStateForUpdatedThread( $thread, $change_type, $wgUser );
114114
115115 if ( $wgUser->getOption( 'lqt-watch-threads', false ) ) {
116 - $thread->topmostThread()->root()->doWatch();
 116+ Action::factory( 'watch', $thread->topmostThread()->root() )->execute();
117117 }
118118
119119 return $thread;
Index: trunk/extensions/MetavidWiki/includes/articlepages/MV_DataPage.php
@@ -63,9 +63,9 @@
6464 if ( $confirm ) {
6565 $this->doDelete( $reason );
6666 if ( $wgRequest->getCheck( 'wpWatch' ) ) {
67 - $this->doWatch();
 67+ Action::factory( 'watch', $this )->execute();
6868 } elseif ( $this->mTitle->userIsWatching() ) {
69 - $this->doUnwatch();
 69+ Action::factory( 'watch', $this )->execute();
7070 }
7171 return;
7272 }
Index: trunk/extensions/ReplaceText/ReplaceTextJob.php
@@ -42,8 +42,7 @@
4343 $create_redirect = $this->params['create_redirect'];
4444 $this->title->moveTo( $new_title, true, $reason, $create_redirect );
4545 if ( $this->params['watch_page'] ) {
46 - $article = new Article( $new_title );
47 - $article->doWatch();
 46+ Action::factory( 'watch', new Article( $new_title ) )->execute();
4847 }
4948 $wgUser = $actual_user;
5049 } else {
Index: trunk/phase3/includes/Credits.php
@@ -1,238 +0,0 @@
2 -<?php
3 -/**
4 - * Formats credits for articles
5 - *
6 - * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
7 - *
8 - * This program is free software; you can redistribute it and/or modify
9 - * it under the terms of the GNU General Public License as published by
10 - * the Free Software Foundation; either version 2 of the License, or
11 - * (at your option) any later version.
12 - *
13 - * This program is distributed in the hope that it will be useful,
14 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 - * GNU General Public License for more details.
17 - *
18 - * You should have received a copy of the GNU General Public License
19 - * along with this program; if not, write to the Free Software
20 - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
21 - *
22 - * @file
23 - * @author <evan@wikitravel.org>
24 - */
25 -
26 -class Credits {
27 - /**
28 - * This is largely cadged from PageHistory::history
29 - * @param $article Article object
30 - */
31 - public static function showPage( Article $article ) {
32 - global $wgOut;
33 -
34 - wfProfileIn( __METHOD__ );
35 -
36 - $wgOut->setPageTitle( $article->mTitle->getPrefixedText() );
37 - $wgOut->setSubtitle( wfMsg( 'creditspage' ) );
38 - $wgOut->setArticleFlag( false );
39 - $wgOut->setArticleRelated( true );
40 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
41 -
42 - if ( $article->mTitle->getArticleID() == 0 ) {
43 - $s = wfMsg( 'nocredits' );
44 - } else {
45 - $s = self::getCredits( $article, -1 );
46 - }
47 -
48 - $wgOut->addHTML( $s );
49 -
50 - wfProfileOut( __METHOD__ );
51 - }
52 -
53 - /**
54 - * Get a list of contributors of $article
55 - * @param $article Article object
56 - * @param $cnt Int: maximum list of contributors to show
57 - * @param $showIfMax Bool: whether to contributors if there more than $cnt
58 - * @return String: html
59 - */
60 - public static function getCredits( Article $article, $cnt, $showIfMax = true ) {
61 - wfProfileIn( __METHOD__ );
62 - $s = '';
63 -
64 - if ( isset( $cnt ) && $cnt != 0 ) {
65 - $s = self::getAuthor( $article );
66 - if ( $cnt > 1 || $cnt < 0 ) {
67 - $s .= ' ' . self::getContributors( $article, $cnt - 1, $showIfMax );
68 - }
69 - }
70 -
71 - wfProfileOut( __METHOD__ );
72 - return $s;
73 - }
74 -
75 - /**
76 - * Get the last author with the last modification time
77 - * @param $article Article object
78 - */
79 - protected static function getAuthor( Article $article ) {
80 - global $wgLang;
81 -
82 - $user = User::newFromId( $article->getUser() );
83 -
84 - $timestamp = $article->getTimestamp();
85 - if ( $timestamp ) {
86 - $d = $wgLang->date( $article->getTimestamp(), true );
87 - $t = $wgLang->time( $article->getTimestamp(), true );
88 - } else {
89 - $d = '';
90 - $t = '';
91 - }
92 - return wfMsgExt( 'lastmodifiedatby', 'parsemag', $d, $t, self::userLink( $user ), $user->getName() );
93 - }
94 -
95 - /**
96 - * Get a list of contributors of $article
97 - * @param $article Article object
98 - * @param $cnt Int: maximum list of contributors to show
99 - * @param $showIfMax Bool: whether to contributors if there more than $cnt
100 - * @return String: html
101 - */
102 - protected static function getContributors( Article $article, $cnt, $showIfMax ) {
103 - global $wgLang, $wgHiddenPrefs;
104 -
105 - $contributors = $article->getContributors();
106 -
107 - $others_link = false;
108 -
109 - # Hmm... too many to fit!
110 - if ( $cnt > 0 && $contributors->count() > $cnt ) {
111 - $others_link = self::othersLink( $article );
112 - if ( !$showIfMax )
113 - return wfMsgExt( 'othercontribs', 'parsemag', $others_link, $contributors->count() );
114 - }
115 -
116 - $real_names = array();
117 - $user_names = array();
118 - $anon_ips = array();
119 -
120 - # Sift for real versus user names
121 - foreach ( $contributors as $user ) {
122 - $cnt--;
123 - if ( $user->isLoggedIn() ) {
124 - $link = self::link( $user );
125 - if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
126 - $real_names[] = $link;
127 - } else {
128 - $user_names[] = $link;
129 - }
130 - } else {
131 - $anon_ips[] = self::link( $user );
132 - }
133 -
134 - if ( $cnt == 0 ) {
135 - break;
136 - }
137 - }
138 -
139 - if ( count( $real_names ) ) {
140 - $real = $wgLang->listToText( $real_names );
141 - } else {
142 - $real = false;
143 - }
144 -
145 - # "ThisSite user(s) A, B and C"
146 - if ( count( $user_names ) ) {
147 - $user = wfMsgExt(
148 - 'siteusers',
149 - 'parsemag',
150 - $wgLang->listToText( $user_names ), count( $user_names )
151 - );
152 - } else {
153 - $user = false;
154 - }
155 -
156 - if ( count( $anon_ips ) ) {
157 - $anon = wfMsgExt(
158 - 'anonusers',
159 - 'parsemag',
160 - $wgLang->listToText( $anon_ips ), count( $anon_ips )
161 - );
162 - } else {
163 - $anon = false;
164 - }
165 -
166 - # This is the big list, all mooshed together. We sift for blank strings
167 - $fulllist = array();
168 - foreach ( array( $real, $user, $anon, $others_link ) as $s ) {
169 - if ( $s ) {
170 - array_push( $fulllist, $s );
171 - }
172 - }
173 -
174 - # Make the list into text...
175 - $creds = $wgLang->listToText( $fulllist );
176 -
177 - # "Based on work by ..."
178 - return strlen( $creds )
179 - ? wfMsgExt( 'othercontribs', 'parsemag', $creds, count( $fulllist ) )
180 - : '';
181 - }
182 -
183 - /**
184 - * Get a link to $user's user page
185 - * @param $user User object
186 - * @return String: html
187 - */
188 - protected static function link( User $user ) {
189 - global $wgUser, $wgHiddenPrefs;
190 - if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) {
191 - $real = $user->getRealName();
192 - } else {
193 - $real = false;
194 - }
195 -
196 - $skin = $wgUser->getSkin();
197 - $page = $user->isAnon() ?
198 - SpecialPage::getTitleFor( 'Contributions', $user->getName() ) :
199 - $user->getUserPage();
200 -
201 - return $skin->link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
202 - }
203 -
204 - /**
205 - * Get a link to $user's user page
206 - * @param $user User object
207 - * @return String: html
208 - */
209 - protected static function userLink( User $user ) {
210 - $link = self::link( $user );
211 - if ( $user->isAnon() ) {
212 - return wfMsgExt( 'anonuser', array( 'parseinline', 'replaceafter' ), $link );
213 - } else {
214 - global $wgHiddenPrefs;
215 - if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
216 - return $link;
217 - } else {
218 - return wfMsgExt( 'siteuser', 'parsemag', $link, $user->getName() );
219 - }
220 - }
221 - }
222 -
223 - /**
224 - * Get a link to action=credits of $article page
225 - * @param $article Article object
226 - * @return String: html
227 - */
228 - protected static function othersLink( Article $article ) {
229 - global $wgUser;
230 - $skin = $wgUser->getSkin();
231 - return $skin->link(
232 - $article->getTitle(),
233 - wfMsgHtml( 'others' ),
234 - array(),
235 - array( 'action' => 'credits' ),
236 - array( 'known' )
237 - );
238 - }
239 -}
Index: trunk/phase3/includes/ProtectionForm.php
@@ -317,9 +317,9 @@
318318 }
319319
320320 if( $wgRequest->getCheck( 'mwProtectWatch' ) && $wgUser->isLoggedIn() ) {
321 - $this->mArticle->doWatch();
 321+ Action::factory( 'watch', $this->mArticle )->execute();
322322 } elseif( $this->mTitle->userIsWatching() ) {
323 - $this->mArticle->doUnwatch();
 323+ Action::factory( 'unwatch', $this->mArticle )->execute();
324324 }
325325 return $ok;
326326 }
Index: trunk/phase3/includes/Article.php
@@ -1672,32 +1672,7 @@
16731673 * Handle action=purge
16741674 */
16751675 public function purge() {
1676 - global $wgRequest, $wgOut;
1677 -
1678 - if ( $wgOut->getUser()->isAllowed( 'purge' ) || $wgRequest->wasPosted() ) {
1679 - //FIXME: shouldn't this be in doPurge()?
1680 - if ( wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
1681 - $this->doPurge();
1682 - $this->view();
1683 - }
1684 - } else {
1685 - $formParams = array(
1686 - 'method' => 'post',
1687 - 'action' => $wgRequest->getRequestURL(),
1688 - );
1689 -
1690 - $wgOut->addWikiMsg( 'confirm-purge-top' );
1691 -
1692 - $form = Html::openElement( 'form', $formParams );
1693 - $form .= Xml::submitButton( wfMsg( 'confirm_purge_button' ) );
1694 - $form .= Html::closeElement( 'form' );
1695 -
1696 - $wgOut->addHTML( $form );
1697 - $wgOut->addWikiMsg( 'confirm-purge-bottom' );
1698 -
1699 - $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
1700 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
1701 - }
 1676+ return Action::factory( 'purge', $this )->show();
17021677 }
17031678
17041679 /**
@@ -1706,6 +1681,10 @@
17071682 public function doPurge() {
17081683 global $wgUseSquid;
17091684
 1685+ if( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ){
 1686+ return false;
 1687+ }
 1688+
17101689 // Invalidate the cache
17111690 $this->mTitle->invalidateCache();
17121691 $this->clear();
@@ -2345,27 +2324,10 @@
23462325
23472326 /**
23482327 * User-interface handler for the "watch" action
 2328+ * @deprecated since 1.18
23492329 */
23502330 public function watch() {
2351 - global $wgOut;
2352 -
2353 - if ( $wgOut->getUser()->isAnon() ) {
2354 - $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
2355 - return;
2356 - }
2357 -
2358 - if ( wfReadOnly() ) {
2359 - $wgOut->readOnlyPage();
2360 - return;
2361 - }
2362 -
2363 - if ( $this->doWatch() ) {
2364 - $wgOut->setPagetitle( wfMsg( 'addedwatch' ) );
2365 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
2366 - $wgOut->addWikiMsg( 'addedwatchtext', $this->mTitle->getPrefixedText() );
2367 - }
2368 -
2369 - $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
 2331+ Action::factory( 'watch', $this )->show();
23702332 }
23712333
23722334 /**
@@ -2374,64 +2336,27 @@
23752337 * This is safe to be called multiple times
23762338 *
23772339 * @return bool true on successful watch operation
 2340+ * @deprecated since 1.18
23782341 */
23792342 public function doWatch() {
2380 - global $wgUser;
2381 -
2382 - if ( $wgUser->isAnon() ) {
2383 - return false;
2384 - }
2385 -
2386 - if ( wfRunHooks( 'WatchArticle', array( &$wgUser, &$this ) ) ) {
2387 - $wgUser->addWatch( $this->mTitle );
2388 - return wfRunHooks( 'WatchArticleComplete', array( &$wgUser, &$this ) );
2389 - }
2390 -
2391 - return false;
 2343+ return Action::factory( 'watch', $this )->execute();
23922344 }
23932345
23942346 /**
23952347 * User interface handler for the "unwatch" action.
 2348+ * @deprecated since 1.18
23962349 */
23972350 public function unwatch() {
2398 - global $wgOut;
2399 -
2400 - if ( $wgOut->getUser()->isAnon() ) {
2401 - $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' );
2402 - return;
2403 - }
2404 -
2405 - if ( wfReadOnly() ) {
2406 - $wgOut->readOnlyPage();
2407 - return;
2408 - }
2409 -
2410 - if ( $this->doUnwatch() ) {
2411 - $wgOut->setPagetitle( wfMsg( 'removedwatch' ) );
2412 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
2413 - $wgOut->addWikiMsg( 'removedwatchtext', $this->mTitle->getPrefixedText() );
2414 - }
2415 -
2416 - $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() );
 2351+ Action::factory( 'unwatch', $this )->show();
24172352 }
24182353
24192354 /**
24202355 * Stop watching a page
24212356 * @return bool true on successful unwatch
 2357+ * @deprecated since 1.18
24222358 */
24232359 public function doUnwatch() {
2424 - global $wgUser;
2425 -
2426 - if ( $wgUser->isAnon() ) {
2427 - return false;
2428 - }
2429 -
2430 - if ( wfRunHooks( 'UnwatchArticle', array( &$wgUser, &$this ) ) ) {
2431 - $wgUser->removeWatch( $this->mTitle );
2432 - return wfRunHooks( 'UnwatchArticleComplete', array( &$wgUser, &$this ) );
2433 - }
2434 -
2435 - return false;
 2360+ return Action::factory( 'unwatch', $this )->execute();
24362361 }
24372362
24382363 /**
Index: trunk/phase3/includes/Setup.php
@@ -270,6 +270,14 @@
271271 $wgHiddenPrefs[] = 'enotifminoredits';
272272 }
273273
 274+# $wgDisabledActions is deprecated as of 1.18
 275+foreach( $wgDisabledActions as $action ){
 276+ $wgActions[$action] = false;
 277+}
 278+if( !$wgAllowPageInfo ){
 279+ $wgActions['info'] = false;
 280+}
 281+
274282 if ( !$wgHtml5Version && $wgHtml5 && $wgAllowRdfaAttributes ) {
275283 # see http://www.w3.org/TR/rdfa-in-html/#document-conformance
276284 if ( $wgMimeType == 'application/xhtml+xml' ) {
Index: trunk/phase3/includes/EditPage.php
@@ -1153,9 +1153,9 @@
11541154 $dbw = wfGetDB( DB_MASTER );
11551155 $dbw->begin();
11561156 if ( $this->watchthis ) {
1157 - $this->mArticle->doWatch();
 1157+ Action::factory( 'watch', $this->mArticle )->execute();
11581158 } else {
1159 - $this->mArticle->doUnwatch();
 1159+ Action::factory( 'watch', $this->mArticle )->execute();
11601160 }
11611161 $dbw->commit();
11621162 }
Index: trunk/phase3/includes/actions/DeleteAction.php
@@ -0,0 +1,476 @@
 2+<?php
 3+/**
 4+ * Performs the watch and unwatch actions on a page
 5+ *
 6+ * This program is free software; you can redistribute it and/or modify
 7+ * it under the terms of the GNU General Public License as published by
 8+ * the Free Software Foundation; either version 2 of the License, or
 9+ * (at your option) any later version.
 10+ *
 11+ * This program is distributed in the hope that it will be useful,
 12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14+ * GNU General Public License for more details.
 15+ *
 16+ * You should have received a copy of the GNU General Public License
 17+ * along with this program; if not, write to the Free Software
 18+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 19+ *
 20+ * @file
 21+ * @ingroup Actions
 22+ */
 23+
 24+class DeleteAction extends Action {
 25+
 26+ public function getName(){
 27+ return 'delete';
 28+ }
 29+
 30+ public function getRestriction(){
 31+ return 'delete';
 32+ }
 33+
 34+ protected function getDescription(){
 35+ return wfMsg( 'delete-confirm', $this->getTitle()->getPrefixedText() );
 36+ }
 37+
 38+ /**
 39+ * Check that the deletion can be executed. In addition to checking the user permissions,
 40+ * check that the page is not too big and has not already been deleted.
 41+ * @throws ErrorPageError
 42+ * @see Action::checkCanExecute
 43+ */
 44+ protected function checkCanExecute( User $user ){
 45+
 46+ // Check that the article hasn't already been deleted
 47+ $dbw = wfGetDB( DB_MASTER );
 48+ $conds = $this->getTitle()->pageCond();
 49+ $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
 50+ if ( $latest === false ) {
 51+ // Get the deletion log
 52+ $log = '';
 53+ LogEventsList::showLogExtract(
 54+ $log,
 55+ 'delete',
 56+ $this->getTitle()->getPrefixedText()
 57+ );
 58+
 59+ $msg = new Message( 'cannotdelete' );
 60+ $msg->params( $this->getTitle()->getPrefixedText() ); // This parameter is parsed
 61+ $msg->rawParams( $log ); // This is not
 62+
 63+ throw new ErrorPageError( 'internalerror', $msg );
 64+ }
 65+
 66+ // Limit deletions of big pages
 67+ $bigHistory = $this->isBigDeletion();
 68+ if ( $bigHistory && !$user->isAllowed( 'bigdelete' ) ) {
 69+ global $wgDeleteRevisionsLimit;
 70+ throw new ErrorPageError(
 71+ 'internalerror',
 72+ 'delete-toobig',
 73+ $this->getContext()->lang->formatNum( $wgDeleteRevisionsLimit )
 74+ );
 75+ }
 76+
 77+ return parent::checkCanExecute( $user );
 78+ }
 79+
 80+ protected function getFormFields(){
 81+ // TODO: add more useful things here?
 82+ $infoText = Html::rawElement(
 83+ 'strong',
 84+ array(),
 85+ Linker::link( $this->getTitle(), $this->getTitle()->getText() )
 86+ );
 87+
 88+ $arr = array(
 89+ 'Page' => array(
 90+ 'type' => 'info',
 91+ 'raw' => true,
 92+ 'default' => $infoText,
 93+ ),
 94+ 'Reason' => array(
 95+ 'type' => 'selectandother',
 96+ 'label-message' => 'deletecomment',
 97+ 'options-message' => 'deletereason-dropdown',
 98+ 'size' => '60',
 99+ 'maxlength' => '255',
 100+ 'default' => self::getAutoReason( $this->page),
 101+ ),
 102+ );
 103+
 104+ if( $this->getUser()->isLoggedIn() ){
 105+ $arr['Watch'] = array(
 106+ 'type' => 'check',
 107+ 'label-message' => 'watchthis',
 108+ 'default' => $this->getUser()->getBoolOption( 'watchdeletion' ) || $this->getTitle()->userIsWatching()
 109+ );
 110+ }
 111+
 112+ if( $this->getUser()->isAllowed( 'suppressrevision' ) ){
 113+ $arr['Suppress'] = array(
 114+ 'type' => 'check',
 115+ 'label-message' => 'revdelete-suppress',
 116+ 'default' => false,
 117+ );
 118+ }
 119+
 120+ return $arr;
 121+ }
 122+
 123+ /**
 124+ * Text to go at the top of the form, before the opening fieldset
 125+ * @see Action::preText()
 126+ * @return String
 127+ */
 128+ protected function preText() {
 129+
 130+ // If the page has a history, insert a warning
 131+ if ( $this->page->estimateRevisionCount() ) {
 132+ global $wgLang;
 133+
 134+ $link = Linker::link(
 135+ $this->getTitle(),
 136+ wfMsgHtml( 'history' ),
 137+ array( 'rel' => 'archives' ),
 138+ array( 'action' => 'history' )
 139+ );
 140+
 141+ return Html::rawElement(
 142+ 'strong',
 143+ array( 'class' => 'mw-delete-warning-revisions' ),
 144+ wfMessage(
 145+ 'historywarning',
 146+ $wgLang->formatNum( $this->page->estimateRevisionCount() )
 147+ )->rawParams( $link )->parse()
 148+ );
 149+ }
 150+ }
 151+
 152+ /**
 153+ * Text to go at the bottom of the form, below the closing fieldset
 154+ * @see Action::postText()
 155+ * @return string
 156+ */
 157+ protected function postText(){
 158+ $s = '';
 159+ LogEventsList::showLogExtract(
 160+ $s,
 161+ 'delete',
 162+ $this->getTitle()->getPrefixedText()
 163+ );
 164+ return Html::element( 'h2', array(), LogPage::logName( 'delete' ) ) . $s;
 165+ }
 166+
 167+ protected function alterForm( HTMLForm &$form ){
 168+ $form->setWrapperLegend( wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) );
 169+
 170+ if ( $this->getUser()->isAllowed( 'editinterface' ) ) {
 171+ $link = Linker::link(
 172+ Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' ),
 173+ wfMsgHtml( 'delete-edit-reasonlist' ),
 174+ array(),
 175+ array( 'action' => 'edit' )
 176+ );
 177+ $form->addHeaderText( '<p class="mw-delete-editreasons">' . $link . '</p>' );
 178+ }
 179+ }
 180+
 181+ /**
 182+ * Function called on form submission. Privilege checks and validation have already been
 183+ * completed by this point; we just need to jump out to the heavy-lifting function,
 184+ * which is implemented as a static method so it can be called from other places
 185+ * TODO: make those other places call $action->execute() properly
 186+ * @see Action::onSubmit()
 187+ * @param $data Array
 188+ * @return Array|Bool
 189+ */
 190+ public function onSubmit( $data ){
 191+ $status = self::doDeleteArticle( $this->page, $this->getContext(), $data, true );
 192+ return $status;
 193+ }
 194+
 195+ public function onSuccess(){
 196+ // Watch or unwatch, if requested
 197+ if( $this->getRequest()->getCheck( 'wpWatch' ) && $this->getUser()->isLoggedIn() ) {
 198+ Action::factory( 'watch', $this->page )->execute();
 199+ } elseif ( $this->getTitle()->userIsWatching() ) {
 200+ Action::factory( 'unwatch', $this->page )->execute();
 201+ }
 202+
 203+ $this->getOutput()->setPagetitle( wfMsg( 'actioncomplete' ) );
 204+ $this->getOutput()->addWikiMsg(
 205+ 'deletedtext',
 206+ $this->getTitle()->getPrefixedText(),
 207+ '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]'
 208+ );
 209+ $this->getOutput()->returnToMain( false );
 210+ }
 211+
 212+ /**
 213+ * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
 214+ */
 215+ protected function isBigDeletion() {
 216+ global $wgDeleteRevisionsLimit;
 217+ return $wgDeleteRevisionsLimit && $this->page->estimateRevisionCount() > $wgDeleteRevisionsLimit;
 218+ }
 219+
 220+ /**
 221+ * Back-end article deletion
 222+ * Deletes the article with database consistency, writes logs, purges caches
 223+ *
 224+ * @param $commit boolean defaults to true, triggers transaction end
 225+ * @return Bool|Array true if successful, error array on failure
 226+ */
 227+ public static function doDeleteArticle( Article $page, RequestContext $context, array $data, $commit = true ) {
 228+ global $wgDeferredUpdateList, $wgUseTrackbacks;
 229+
 230+ wfDebug( __METHOD__ . "\n" );
 231+
 232+ // The normal syntax from HTMLSelectAndOtherField is for the reason to be in the form
 233+ // 'Reason' => array( <full reason>, <dropdown>, <custom> ), but it's reasonable for other
 234+ // functions to just pass 'Reason' => <reason>
 235+ $data['Reason'] = (array)$data['Reason'];
 236+
 237+ $error = null;
 238+ if ( !wfRunHooks( 'ArticleDelete', array( &$page, &$context->user, &$data['Reason'][0], &$error ) ) ) {
 239+ return $error;
 240+ }
 241+
 242+ $title = $page->getTitle();
 243+ $id = $page->getID( Title::GAID_FOR_UPDATE );
 244+
 245+ if ( $title->getDBkey() === '' || $id == 0 ) {
 246+ return false;
 247+ }
 248+
 249+ $updates = new SiteStatsUpdate( 0, 1, - (int)$page->isCountable( $page->getRawText() ), -1 );
 250+ array_push( $wgDeferredUpdateList, $updates );
 251+
 252+ // Bitfields to further suppress the content
 253+ if ( isset( $data['Suppress'] ) && $data['Suppress'] ) {
 254+ $bitfield = 0;
 255+ // This should be 15...
 256+ $bitfield |= Revision::DELETED_TEXT;
 257+ $bitfield |= Revision::DELETED_COMMENT;
 258+ $bitfield |= Revision::DELETED_USER;
 259+ $bitfield |= Revision::DELETED_RESTRICTED;
 260+
 261+ $logtype = 'suppress';
 262+ } else {
 263+ // Otherwise, leave it unchanged
 264+ $bitfield = 'rev_deleted';
 265+ $logtype = 'delete';
 266+ }
 267+
 268+ $dbw = wfGetDB( DB_MASTER );
 269+ $dbw->begin();
 270+ // For now, shunt the revision data into the archive table.
 271+ // Text is *not* removed from the text table; bulk storage
 272+ // is left intact to avoid breaking block-compression or
 273+ // immutable storage schemes.
 274+ //
 275+ // For backwards compatibility, note that some older archive
 276+ // table entries will have ar_text and ar_flags fields still.
 277+ //
 278+ // In the future, we may keep revisions and mark them with
 279+ // the rev_deleted field, which is reserved for this purpose.
 280+ $dbw->insertSelect(
 281+ 'archive',
 282+ array( 'page', 'revision' ),
 283+ array(
 284+ 'ar_namespace' => 'page_namespace',
 285+ 'ar_title' => 'page_title',
 286+ 'ar_comment' => 'rev_comment',
 287+ 'ar_user' => 'rev_user',
 288+ 'ar_user_text' => 'rev_user_text',
 289+ 'ar_timestamp' => 'rev_timestamp',
 290+ 'ar_minor_edit' => 'rev_minor_edit',
 291+ 'ar_rev_id' => 'rev_id',
 292+ 'ar_text_id' => 'rev_text_id',
 293+ 'ar_text' => "''", // Be explicit to appease
 294+ 'ar_flags' => "''", // MySQL's "strict mode"...
 295+ 'ar_len' => 'rev_len',
 296+ 'ar_page_id' => 'page_id',
 297+ 'ar_deleted' => $bitfield
 298+ ),
 299+ array(
 300+ 'page_id' => $id,
 301+ 'page_id = rev_page'
 302+ ),
 303+ __METHOD__
 304+ );
 305+
 306+ // Delete restrictions for it
 307+ $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
 308+
 309+ // Now that it's safely backed up, delete it
 310+ $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
 311+
 312+ // getArticleId() uses slave, could be laggy
 313+ if ( $dbw->affectedRows() == 0 ) {
 314+ $dbw->rollback();
 315+ return false;
 316+ }
 317+
 318+ // Fix category table counts
 319+ $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
 320+ $cats = array();
 321+ foreach ( $res as $row ) {
 322+ $cats[] = $row->cl_to;
 323+ }
 324+ $page->updateCategoryCounts( array(), $cats );
 325+
 326+ // If using cascading deletes, we can skip some explicit deletes
 327+ if ( !$dbw->cascadingDeletes() ) {
 328+ $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
 329+
 330+ if ( $wgUseTrackbacks ){
 331+ $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
 332+ }
 333+
 334+ // Delete outgoing links
 335+ $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
 336+ $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
 337+ $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
 338+ $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
 339+ $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
 340+ $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
 341+ $dbw->delete( 'redirect', array( 'rd_from' => $id ) );
 342+ }
 343+
 344+ // If using cleanup triggers, we can skip some manual deletes
 345+ if ( !$dbw->cleanupTriggers() ) {
 346+ // Clean up recentchanges entries...
 347+ $dbw->delete( 'recentchanges',
 348+ array(
 349+ 'rc_type != ' . RC_LOG,
 350+ 'rc_namespace' => $title->getNamespace(),
 351+ 'rc_title' => $title->getDBkey() ),
 352+ __METHOD__
 353+ );
 354+ $dbw->delete(
 355+ 'recentchanges',
 356+ array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
 357+ __METHOD__
 358+ );
 359+ }
 360+
 361+ // Clear caches
 362+ // TODO: should this be in here or left in Article?
 363+ Article::onArticleDelete( $title );
 364+
 365+ // Clear the cached article id so the interface doesn't act like we exist
 366+ $title->resetArticleID( 0 );
 367+
 368+ // Log the deletion, if the page was suppressed, log it at Oversight instead
 369+ $log = new LogPage( $logtype );
 370+
 371+ // Make sure logging got through
 372+ $log->addEntry( 'delete', $title, $data['Reason'][0], array() );
 373+
 374+ if ( $commit ) {
 375+ $dbw->commit();
 376+ }
 377+
 378+ wfRunHooks( 'ArticleDeleteComplete', array( &$page, &$context->user, $data['Reason'][0], $id ) );
 379+ return true;
 380+ }
 381+
 382+ /**
 383+ * Auto-generates a deletion reason. Also sets $this->hasHistory if the page has old
 384+ * revisions.
 385+ *
 386+ * @return mixed String containing default reason or empty string, or boolean false
 387+ * if no revision was found
 388+ */
 389+ public static function getAutoReason( Article $page ) {
 390+ global $wgContLang;
 391+
 392+ $dbw = wfGetDB( DB_MASTER );
 393+ // Get the last revision
 394+ $rev = Revision::newFromTitle( $page->getTitle() );
 395+
 396+ if ( is_null( $rev ) ) {
 397+ return false;
 398+ }
 399+
 400+ // Get the article's contents
 401+ $contents = $rev->getText();
 402+ $blank = false;
 403+
 404+ // If the page is blank, use the text from the previous revision,
 405+ // which can only be blank if there's a move/import/protect dummy revision involved
 406+ if ( $contents == '' ) {
 407+ $prev = $rev->getPrevious();
 408+
 409+ if ( $prev ) {
 410+ $contents = $prev->getText();
 411+ $blank = true;
 412+ }
 413+ }
 414+
 415+ // Find out if there was only one contributor
 416+ // Only scan the last 20 revisions
 417+ $res = $dbw->select( 'revision', 'rev_user_text',
 418+ array(
 419+ 'rev_page' => $page->getID(),
 420+ $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0'
 421+ ),
 422+ __METHOD__,
 423+ array( 'LIMIT' => 20 )
 424+ );
 425+
 426+ if ( $res === false ) {
 427+ // This page has no revisions, which is very weird
 428+ return false;
 429+ }
 430+
 431+ $row = $dbw->fetchObject( $res );
 432+
 433+ if ( $row ) { // $row is false if the only contributor is hidden
 434+ $onlyAuthor = $row->rev_user_text;
 435+ // Try to find a second contributor
 436+ foreach ( $res as $row ) {
 437+ if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
 438+ $onlyAuthor = false;
 439+ break;
 440+ }
 441+ }
 442+ } else {
 443+ $onlyAuthor = false;
 444+ }
 445+
 446+ // Generate the summary with a '$1' placeholder
 447+ if ( $blank ) {
 448+ // The current revision is blank and the one before is also
 449+ // blank. It's just not our lucky day
 450+ $reason = wfMessage( 'exbeforeblank', '$1' )->inContentLanguage()->text();
 451+ } else {
 452+ if ( $onlyAuthor ) {
 453+ $reason = wfMessage( 'excontentauthor', '$1', $onlyAuthor )->inContentLanguage()->text();
 454+ } else {
 455+ $reason = wfMessage( 'excontent', '$1' )->inContentLanguage()->text();
 456+ }
 457+ }
 458+
 459+ if ( $reason == '-' ) {
 460+ // Allow these UI messages to be blanked out cleanly
 461+ return '';
 462+ }
 463+
 464+ // Replace newlines with spaces to prevent uglyness
 465+ $contents = preg_replace( "/[\n\r]/", ' ', $contents );
 466+ // Calculate the maximum number of chars to get
 467+ // Max content length = max comment length - length of the comment (excl. $1)
 468+ $maxLength = 255 - ( strlen( $reason ) - 2 );
 469+ $contents = $wgContLang->truncate( $contents, $maxLength );
 470+ // Remove possible unfinished links
 471+ $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
 472+ // Now replace the '$1' placeholder
 473+ $reason = str_replace( '$1', $contents, $reason );
 474+
 475+ return $reason;
 476+ }
 477+}
Property changes on: trunk/phase3/includes/actions/DeleteAction.php
___________________________________________________________________
Added: svn:eol-style
1478 + native
Index: trunk/phase3/includes/actions/PurgeAction.php
@@ -0,0 +1,93 @@
 2+<?php
 3+/**
 4+ * Formats credits for articles
 5+ *
 6+ * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 21+ *
 22+ * @file
 23+ * @ingroup Actions
 24+ * @author <evan@wikitravel.org>
 25+ */
 26+
 27+class PurgeAction extends FormAction {
 28+
 29+ public function getName(){
 30+ return 'purge';
 31+ }
 32+
 33+ public function getRestriction(){
 34+ return null;
 35+ }
 36+
 37+ public function requiresUnblock(){
 38+ return false;
 39+ }
 40+
 41+ public function getDescription(){
 42+ return '';
 43+ }
 44+
 45+ /**
 46+ * Just get an empty form with a single submit button
 47+ * @return array
 48+ */
 49+ protected function getFormFields(){
 50+ return array();
 51+ }
 52+
 53+ public function onSubmit( $data ){
 54+ $this->page->doPurge();
 55+ return true;
 56+ }
 57+
 58+ /**
 59+ * purge is slightly wierd because it can be either formed or formless depending
 60+ * on user permissions
 61+ */
 62+ public function show(){
 63+ $this->setHeaders();
 64+
 65+ // This will throw exceptions if there's a problem
 66+ $this->checkCanExecute( $this->getUser() );
 67+
 68+ if( $this->getUser()->isAllowed( 'purge' ) ){
 69+ $this->onSubmit( array() );
 70+ $this->onSuccess();
 71+ } else {
 72+ $form = $this->getForm();
 73+ if( $form->show() ){
 74+ $this->onSuccess();
 75+ }
 76+ }
 77+ }
 78+
 79+ protected function alterForm( HTMLForm $form ){
 80+ $form->setSubmitText( wfMsg( 'confirm_purge_button' ) );
 81+ }
 82+
 83+ protected function preText(){
 84+ return wfMessage( 'confirm-purge-top' )->parse();
 85+ }
 86+
 87+ protected function postText(){
 88+ return wfMessage( 'confirm-purge-bottom' )->parse();
 89+ }
 90+
 91+ public function onSuccess(){
 92+ $this->getOutput()->redirect( $this->getTitle() );
 93+ }
 94+}
Property changes on: trunk/phase3/includes/actions/PurgeAction.php
___________________________________________________________________
Added: svn:eol-style
195 + native
Index: trunk/phase3/includes/actions/WatchAction.php
@@ -0,0 +1,86 @@
 2+<?php
 3+/**
 4+ * Performs the watch and unwatch actions on a page
 5+ *
 6+ * This program is free software; you can redistribute it and/or modify
 7+ * it under the terms of the GNU General Public License as published by
 8+ * the Free Software Foundation; either version 2 of the License, or
 9+ * (at your option) any later version.
 10+ *
 11+ * This program is distributed in the hope that it will be useful,
 12+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 14+ * GNU General Public License for more details.
 15+ *
 16+ * You should have received a copy of the GNU General Public License
 17+ * along with this program; if not, write to the Free Software
 18+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 19+ *
 20+ * @file
 21+ * @ingroup Actions
 22+ */
 23+
 24+class WatchAction extends FormlessAction {
 25+
 26+ public function getName(){
 27+ return 'watch';
 28+ }
 29+
 30+ public function getRestriction(){
 31+ return 'read';
 32+ }
 33+
 34+ public function requiresUnblock(){
 35+ return false;
 36+ }
 37+
 38+ protected function getDescription(){
 39+ return wfMsg( 'addedwatch' );
 40+ }
 41+
 42+ protected function checkCanExecute( User $user ){
 43+ if ( $user->isAnon() ) {
 44+ throw new ErrorPageError( 'watchnologin', 'watchnologintext' );
 45+ }
 46+ return parent::checkCanExecute( $user );
 47+ }
 48+
 49+ public function onView() {
 50+ wfProfileIn( __METHOD__ );
 51+
 52+ $user = $this->getUser();
 53+ if ( wfRunHooks( 'WatchArticle', array( &$user, &$this->page ) ) ) {
 54+ $this->getUser()->addWatch( $this->getTitle() );
 55+ wfRunHooks( 'WatchArticleComplete', array( &$user, &$this->page ) );
 56+ }
 57+
 58+ wfProfileOut( __METHOD__ );
 59+
 60+ return wfMessage( 'addedwatchtext', $this->getTitle()->getPrefixedText() )->parse();
 61+ }
 62+}
 63+
 64+class UnwatchAction extends WatchAction {
 65+
 66+ public function getName(){
 67+ return 'unwatch';
 68+ }
 69+
 70+ protected function getDescription(){
 71+ return wfMsg( 'removedwatch' );
 72+ }
 73+
 74+ public function onView() {
 75+ wfProfileIn( __METHOD__ );
 76+
 77+ $user = $this->getUser();
 78+ if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$this->page ) ) ) {
 79+ $this->getUser()->removeWatch( $this->getTitle() );
 80+ wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$this->page ) );
 81+ }
 82+
 83+ wfProfileOut( __METHOD__ );
 84+
 85+ return wfMessage( 'removedwatchtext', $this->getTitle()->getPrefixedText() )->parse();
 86+ }
 87+}
Property changes on: trunk/phase3/includes/actions/WatchAction.php
___________________________________________________________________
Added: svn:eol-style
188 + native
Index: trunk/phase3/includes/actions/CreditsAction.php
@@ -0,0 +1,237 @@
 2+<?php
 3+/**
 4+ * Formats credits for articles
 5+ *
 6+ * Copyright 2004, Evan Prodromou <evan@wikitravel.org>.
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 21+ *
 22+ * @file
 23+ * @ingroup Actions
 24+ * @author <evan@wikitravel.org>
 25+ */
 26+
 27+class CreditsAction extends FormlessAction {
 28+
 29+ public function getName(){
 30+ return 'credits';
 31+ }
 32+
 33+ public function getRestriction(){
 34+ return null;
 35+ }
 36+
 37+ /**
 38+ * This is largely cadged from PageHistory::history
 39+ */
 40+ public function onView() {
 41+ wfProfileIn( __METHOD__ );
 42+
 43+ if ( $this->page->getID() == 0 ) {
 44+ $s = wfMsg( 'nocredits' );
 45+ } else {
 46+ $s = $this->getCredits( -1 );
 47+ }
 48+
 49+ wfProfileOut( __METHOD__ );
 50+
 51+ return $s;
 52+ }
 53+
 54+ /**
 55+ * Get a list of contributors of $article
 56+ * @param $article Article object
 57+ * @param $cnt Int: maximum list of contributors to show
 58+ * @param $showIfMax Bool: whether to contributors if there more than $cnt
 59+ * @return String: html
 60+ */
 61+ protected function getCredits( $cnt, $showIfMax = true ) {
 62+ wfProfileIn( __METHOD__ );
 63+ $s = '';
 64+
 65+ if ( isset( $cnt ) && $cnt != 0 ) {
 66+ $s = self::getAuthor( $this->page );
 67+ if ( $cnt > 1 || $cnt < 0 ) {
 68+ $s .= ' ' . $this->getContributors( $cnt - 1, $showIfMax );
 69+ }
 70+ }
 71+
 72+ wfProfileOut( __METHOD__ );
 73+ return $s;
 74+ }
 75+
 76+ /**
 77+ * Get the last author with the last modification time
 78+ * @param $article Article object
 79+ */
 80+ protected static function getAuthor( Article $article ) {
 81+ global $wgLang;
 82+
 83+ $user = User::newFromId( $article->getUser() );
 84+
 85+ $timestamp = $article->getTimestamp();
 86+ if ( $timestamp ) {
 87+ $d = $wgLang->date( $article->getTimestamp(), true );
 88+ $t = $wgLang->time( $article->getTimestamp(), true );
 89+ } else {
 90+ $d = '';
 91+ $t = '';
 92+ }
 93+ return wfMsgExt( 'lastmodifiedatby', 'parsemag', $d, $t, self::userLink( $user ), $user->getName() );
 94+ }
 95+
 96+ /**
 97+ * Get a list of contributors of $article
 98+ * @param $article Article object
 99+ * @param $cnt Int: maximum list of contributors to show
 100+ * @param $showIfMax Bool: whether to contributors if there more than $cnt
 101+ * @return String: html
 102+ */
 103+ protected function getContributors( $cnt, $showIfMax ) {
 104+ global $wgLang, $wgHiddenPrefs;
 105+
 106+ $contributors = $this->page->getContributors();
 107+
 108+ $others_link = false;
 109+
 110+ # Hmm... too many to fit!
 111+ if ( $cnt > 0 && $contributors->count() > $cnt ) {
 112+ $others_link = $this->othersLink();
 113+ if ( !$showIfMax )
 114+ return wfMsgExt( 'othercontribs', 'parsemag', $others_link, $contributors->count() );
 115+ }
 116+
 117+ $real_names = array();
 118+ $user_names = array();
 119+ $anon_ips = array();
 120+
 121+ # Sift for real versus user names
 122+ foreach ( $contributors as $user ) {
 123+ $cnt--;
 124+ if ( $user->isLoggedIn() ) {
 125+ $link = self::link( $user );
 126+ if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
 127+ $real_names[] = $link;
 128+ } else {
 129+ $user_names[] = $link;
 130+ }
 131+ } else {
 132+ $anon_ips[] = self::link( $user );
 133+ }
 134+
 135+ if ( $cnt == 0 ) {
 136+ break;
 137+ }
 138+ }
 139+
 140+ if ( count( $real_names ) ) {
 141+ $real = $wgLang->listToText( $real_names );
 142+ } else {
 143+ $real = false;
 144+ }
 145+
 146+ # "ThisSite user(s) A, B and C"
 147+ if ( count( $user_names ) ) {
 148+ $user = wfMsgExt(
 149+ 'siteusers',
 150+ 'parsemag',
 151+ $wgLang->listToText( $user_names ), count( $user_names )
 152+ );
 153+ } else {
 154+ $user = false;
 155+ }
 156+
 157+ if ( count( $anon_ips ) ) {
 158+ $anon = wfMsgExt(
 159+ 'anonusers',
 160+ 'parsemag',
 161+ $wgLang->listToText( $anon_ips ), count( $anon_ips )
 162+ );
 163+ } else {
 164+ $anon = false;
 165+ }
 166+
 167+ # This is the big list, all mooshed together. We sift for blank strings
 168+ $fulllist = array();
 169+ foreach ( array( $real, $user, $anon, $others_link ) as $s ) {
 170+ if ( $s ) {
 171+ array_push( $fulllist, $s );
 172+ }
 173+ }
 174+
 175+ # Make the list into text...
 176+ $creds = $wgLang->listToText( $fulllist );
 177+
 178+ # "Based on work by ..."
 179+ return strlen( $creds )
 180+ ? wfMsgExt( 'othercontribs', 'parsemag', $creds, count( $fulllist ) )
 181+ : '';
 182+ }
 183+
 184+ /**
 185+ * Get a link to $user's user page
 186+ * @param $user User object
 187+ * @return String: html
 188+ */
 189+ protected static function link( User $user ) {
 190+ global $wgUser, $wgHiddenPrefs;
 191+ if ( !in_array( 'realname', $wgHiddenPrefs ) && !$user->isAnon() ) {
 192+ $real = $user->getRealName();
 193+ } else {
 194+ $real = false;
 195+ }
 196+
 197+ $page = $user->isAnon()
 198+ ? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
 199+ : $user->getUserPage();
 200+
 201+ return Linker::link( $page, htmlspecialchars( $real ? $real : $user->getName() ) );
 202+ }
 203+
 204+ /**
 205+ * Get a link to $user's user page
 206+ * @param $user User object
 207+ * @return String: html
 208+ */
 209+ protected static function userLink( User $user ) {
 210+ $link = self::link( $user );
 211+ if ( $user->isAnon() ) {
 212+ return wfMsgExt( 'anonuser', array( 'parseinline', 'replaceafter' ), $link );
 213+ } else {
 214+ global $wgHiddenPrefs;
 215+ if ( !in_array( 'realname', $wgHiddenPrefs ) && $user->getRealName() ) {
 216+ return $link;
 217+ } else {
 218+ return wfMsgExt( 'siteuser', 'parsemag', $link, $user->getName() );
 219+ }
 220+ }
 221+ }
 222+
 223+ /**
 224+ * Get a link to action=credits of $article page
 225+ * @param $article Article object
 226+ * @return String: html
 227+ */
 228+ protected function othersLink() {
 229+ global $wgUser;
 230+ return Linker::link(
 231+ $this->getTitle(),
 232+ wfMsgHtml( 'others' ),
 233+ array(),
 234+ array( 'action' => 'credits' ),
 235+ array( 'known' )
 236+ );
 237+ }
 238+}
Property changes on: trunk/phase3/includes/actions/CreditsAction.php
___________________________________________________________________
Added: svn:keywords
1239 + Author Date Id Revision
Added: svn:eol-style
2240 + native
Index: trunk/phase3/includes/api/ApiBase.php
@@ -645,9 +645,9 @@
646646
647647 $articleObj = new Article( $titleObj );
648648 if ( $value ) {
649 - $articleObj->doWatch();
 649+ Action::factory( 'watch', $articleObj )->execute();
650650 } else {
651 - $articleObj->doUnwatch();
 651+ Action::factory( 'unwatch', $articleObj )->execute();
652652 }
653653 }
654654
Index: trunk/phase3/includes/api/ApiWatch.php
@@ -59,11 +59,11 @@
6060 if ( $params['unwatch'] ) {
6161 $res['unwatched'] = '';
6262 $res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() );
63 - $success = $article->doUnwatch();
 63+ $success = Action::factory( 'unwatch', $article )->execute();
6464 } else {
6565 $res['watched'] = '';
6666 $res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() );
67 - $success = $article->doWatch();
 67+ $success = Action::factory( 'watch', $article )->execute();
6868 }
6969 if ( !$success ) {
7070 $this->dieUsageMsg( array( 'hookaborted' ) );
Index: trunk/phase3/includes/AutoLoader.php
@@ -14,6 +14,7 @@
1515
1616 $wgAutoloadLocalClasses = array(
1717 # Includes
 18+ 'Action' => 'includes/Action.php',
1819 'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
1920 'AjaxResponse' => 'includes/AjaxResponse.php',
2021 'AlphabeticPager' => 'includes/Pager.php',
@@ -51,7 +52,6 @@
5253 'ConfEditorToken' => 'includes/ConfEditor.php',
5354 'ConstantDependency' => 'includes/CacheDependency.php',
5455 'CreativeCommonsRdf' => 'includes/Metadata.php',
55 - 'Credits' => 'includes/Credits.php',
5656 'CSSJanus' => 'includes/libs/CSSJanus.php',
5757 'CSSMin' => 'includes/libs/CSSMin.php',
5858 'DependencyWrapper' => 'includes/CacheDependency.php',
@@ -274,6 +274,12 @@
275275 'ZhClient' => 'includes/ZhClient.php',
276276 'ZipDirectoryReader' => 'includes/ZipDirectoryReader.php',
277277
 278+ # includes/actions
 279+ 'CreditsAction' => 'includes/actions/CreditsAction.php',
 280+ 'PurgeAction' => 'includes/actions/PurgeAction.php',
 281+ 'UnwatchAction' => 'includes/actions/WatchAction.php',
 282+ 'WatchAction' => 'includes/actions/WatchAction.php',
 283+
278284 # includes/api
279285 'ApiBase' => 'includes/api/ApiBase.php',
280286 'ApiBlock' => 'includes/api/ApiBlock.php',
Index: trunk/phase3/includes/Wiki.php
@@ -471,9 +471,16 @@
472472 return;
473473 }
474474
475 - $action = $this->getAction();
 475+ $act = $this->getAction();
476476
477 - switch( $action ) {
 477+ $action = Action::factory( $this->getAction(), $article );
 478+ if( $action instanceof Action ){
 479+ $action->show();
 480+ wfProfileOut( __METHOD__ );
 481+ return;
 482+ }
 483+
 484+ switch( $act ) {
478485 case 'view':
479486 $this->context->output->setSquidMaxage( $this->getVal( 'SquidMaxage' ) );
480487 $article->view();
@@ -484,8 +491,6 @@
485492 $raw->view();
486493 wfProfileOut( __METHOD__ . '-raw' );
487494 break;
488 - case 'watch':
489 - case 'unwatch':
490495 case 'delete':
491496 case 'revert':
492497 case 'rollback':
@@ -495,8 +500,7 @@
496501 case 'markpatrolled':
497502 case 'render':
498503 case 'deletetrackback':
499 - case 'purge':
500 - $article->$action();
 504+ $article->$act();
501505 break;
502506 case 'print':
503507 $article->view();
@@ -517,9 +521,6 @@
518522 $rdf->show();
519523 }
520524 break;
521 - case 'credits':
522 - Credits::showPage( $article );
523 - break;
524525 case 'submit':
525526 if ( session_id() == '' ) {
526527 // Send a cookie so anons get talk message notifications
@@ -532,7 +533,7 @@
533534 $external = $this->context->request->getVal( 'externaledit' );
534535 $section = $this->context->request->getVal( 'section' );
535536 $oldid = $this->context->request->getVal( 'oldid' );
536 - if ( !$this->getVal( 'UseExternalEditor' ) || $action == 'submit' || $internal ||
 537+ if ( !$this->getVal( 'UseExternalEditor' ) || $act == 'submit' || $internal ||
537538 $section || $oldid || ( !$this->context->user->getOption( 'externaleditor' ) && !$external ) ) {
538539 $editor = new EditPage( $article );
539540 $editor->submit();
@@ -561,7 +562,7 @@
562563 $special->execute( '' );
563564 break;
564565 default:
565 - if ( wfRunHooks( 'UnknownAction', array( $action, $article ) ) ) {
 566+ if ( wfRunHooks( 'UnknownAction', array( $act, $article ) ) ) {
566567 $this->context->output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
567568 }
568569 }
Index: trunk/phase3/includes/FileDeleteForm.php
@@ -125,9 +125,9 @@
126126 if( $article->doDeleteArticle( $reason, $suppress, $id, false ) ) {
127127 global $wgRequest;
128128 if( $wgRequest->getCheck( 'wpWatch' ) && $wgUser->isLoggedIn() ) {
129 - $article->doWatch();
 129+ Action::factory( 'watch', $article )->execute();
130130 } elseif( $title->userIsWatching() ) {
131 - $article->doUnwatch();
 131+ Action::factory( 'unwatch', $article )->execute();
132132 }
133133 $status = $file->delete( $reason, $suppress );
134134 if( $status->ok ) {
Index: trunk/phase3/includes/DefaultSettings.php
@@ -27,12 +27,10 @@
2828 }
2929
3030 # Create a site configuration object. Not used for much in a default install
31 -if ( !defined( 'MW_PHP4' ) ) {
32 - if ( !defined( 'MW_COMPILED' ) ) {
33 - require_once( "$IP/includes/SiteConfiguration.php" );
34 - }
35 - $wgConf = new SiteConfiguration;
 31+if ( !defined( 'MW_COMPILED' ) ) {
 32+ require_once( "$IP/includes/SiteConfiguration.php" );
3633 }
 34+$wgConf = new SiteConfiguration;
3735 /** @endcond */
3836
3937 /** MediaWiki version number */
@@ -5024,6 +5022,38 @@
50255023 /** @} */ # end special pages }
50265024
50275025 /*************************************************************************//**
 5026+ * @name Actions
 5027+ * @{
 5028+ */
 5029+
 5030+/**
 5031+ * Array of allowed values for the title=foo&action=<action> parameter. Syntax is:
 5032+ * 'foo' => 'ClassName' Load the specified class which subclasses Action
 5033+ * 'foo' => true Load the class FooAction which subclasses Action
 5034+ * 'foo' => false The action is disabled; show an error message
 5035+ * Unsetting core actions will probably cause things to complain loudly.
 5036+ */
 5037+$wgActions = array(
 5038+ 'credits' => true,
 5039+ 'purge' => true,
 5040+ 'unwatch' => true,
 5041+ 'watch' => true,
 5042+);
 5043+
 5044+/**
 5045+ * Array of disabled article actions, e.g. view, edit, dublincore, delete, etc.
 5046+ * @deprecated since 1.18; just set $wgActions['action'] = false instead
 5047+ */
 5048+$wgDisabledActions = array();
 5049+
 5050+/**
 5051+ * Allow the "info" action, very inefficient at the moment
 5052+ */
 5053+$wgAllowPageInfo = false;
 5054+
 5055+/** @} */ # end actions }
 5056+
 5057+/*************************************************************************//**
50285058 * @name Robot (search engine crawler) policy
50295059 * See also $wgNoFollowLinks.
50305060 * @{
@@ -5288,18 +5318,10 @@
52895319 * @{
52905320 */
52915321
5292 -/** Allow the "info" action, very inefficient at the moment */
5293 -$wgAllowPageInfo = false;
5294 -
52955322 /** Name of the external diff engine to use */
52965323 $wgExternalDiffEngine = false;
52975324
52985325 /**
5299 - * Array of disabled article actions, e.g. view, edit, dublincore, delete, etc.
5300 - */
5301 -$wgDisabledActions = array();
5302 -
5303 -/**
53045326 * Disable redirects to special pages and interwiki redirects, which use a 302
53055327 * and have no "redirected from" link. Note this is only for articles with #Redirect
53065328 * in them. URL's containing a local interwiki prefix (or a non-canonical special
Index: trunk/phase3/includes/Action.php
@@ -0,0 +1,441 @@
 2+<?php
 3+/**
 4+ * Actions are things which can be done to pages (edit, delete, rollback, etc). They
 5+ * are distinct from Special Pages because an action must apply to exactly one page.
 6+ *
 7+ * To add an action in an extension, create a subclass of Action, and add the key to
 8+ * $wgActions. There is also the deprecated UnknownAction hook
 9+ *
 10+ *
 11+ * This program is free software; you can redistribute it and/or modify
 12+ * it under the terms of the GNU General Public License as published by
 13+ * the Free Software Foundation; either version 2 of the License, or
 14+ * (at your option) any later version.
 15+ *
 16+ * This program is distributed in the hope that it will be useful,
 17+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 19+ * GNU General Public License for more details.
 20+ *
 21+ * You should have received a copy of the GNU General Public License
 22+ * along with this program; if not, write to the Free Software
 23+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 24+ *
 25+ * @file
 26+ */
 27+abstract class Action {
 28+
 29+ // Page on which we're performing the action
 30+ // @var Article
 31+ protected $page;
 32+
 33+ // RequestContext if specified; otherwise we'll use the Context from the Page
 34+ // @var RequestContext
 35+ protected $context;
 36+
 37+ // The fields used to create the HTMLForm
 38+ // @var Array
 39+ protected $fields;
 40+
 41+ /**
 42+ * Get the Action subclass which should be used to handle this action, false if
 43+ * the action is disabled, or null if it's not recognised
 44+ * @param $action String
 45+ * @return bool|null|string
 46+ */
 47+ private final static function getClass( $action ){
 48+ global $wgActions;
 49+ $action = strtolower( $action );
 50+
 51+ if( !isset( $wgActions[$action] ) ){
 52+ return null;
 53+ }
 54+
 55+ if( $wgActions[$action] === false ){
 56+ return false;
 57+ }
 58+
 59+ elseif( $wgActions[$action] === true ){
 60+ return ucfirst( $action ) . 'Action';
 61+ }
 62+
 63+ else {
 64+ return $wgActions[$action];
 65+ }
 66+ }
 67+
 68+ /**
 69+ * Get an appropriate Action subclass for the given action
 70+ * @param $action String
 71+ * @param $page Article
 72+ * @return Action|false|null false if the action is disabled, null
 73+ * if it is not recognised
 74+ */
 75+ public final static function factory( $action, Article $page ){
 76+ $class = self::getClass( $action );
 77+ if( $class ){
 78+ $obj = new $class( $page );
 79+ return $obj;
 80+ }
 81+ return $class;
 82+ }
 83+
 84+ /**
 85+ * Check if a given action is recognised, even if it's disabled
 86+ *
 87+ * @param $name String: name of an action
 88+ * @return Bool
 89+ */
 90+ public final static function exists( $name ) {
 91+ return self::getClass( $name ) !== null;
 92+ }
 93+
 94+ /**
 95+ * Get the RequestContext in use here
 96+ * @return RequestContext
 97+ */
 98+ protected final function getContext(){
 99+ if( $this->context instanceof RequestContext ){
 100+ return $this->context;
 101+ }
 102+ return $this->page->getContext();
 103+ }
 104+
 105+ /**
 106+ * Get the WebRequest being used for this instance
 107+ *
 108+ * @return WebRequest
 109+ */
 110+ protected final function getRequest() {
 111+ return $this->getContext()->request;
 112+ }
 113+
 114+ /**
 115+ * Get the OutputPage being used for this instance
 116+ *
 117+ * @return OutputPage
 118+ */
 119+ protected final function getOutput() {
 120+ return $this->getContext()->output;
 121+ }
 122+
 123+ /**
 124+ * Shortcut to get the User being used for this instance
 125+ *
 126+ * @return User
 127+ */
 128+ protected final function getUser() {
 129+ return $this->getContext()->user;
 130+ }
 131+
 132+ /**
 133+ * Shortcut to get the Skin being used for this instance
 134+ *
 135+ * @return Skin
 136+ */
 137+ protected final function getSkin() {
 138+ return $this->getContext()->skin;
 139+ }
 140+
 141+ /**
 142+ * Shortcut to get the Title object from the page
 143+ * @return Title
 144+ */
 145+ protected final function getTitle(){
 146+ return $this->page->getTitle();
 147+ }
 148+
 149+ /**
 150+ * Protected constructor: use Action::factory( $action, $page ) to actually build
 151+ * these things in the real world
 152+ * @param Article $page
 153+ */
 154+ protected function __construct( Article $page ){
 155+ $this->page = $page;
 156+ }
 157+
 158+ /**
 159+ * Return the name of the action this object responds to
 160+ * @return String lowercase
 161+ */
 162+ public abstract function getName();
 163+
 164+ /**
 165+ * Get the permission required to perform this action. Often, but not always,
 166+ * the same as the action name
 167+ */
 168+ public abstract function getRestriction();
 169+
 170+ /**
 171+ * Checks if the given user (identified by an object) can perform this action. Can be
 172+ * overridden by sub-classes with more complicated permissions schemes. Failures here
 173+ * must throw subclasses of ErrorPageError
 174+ *
 175+ * @param $user User: the user to check, or null to use the context user
 176+ * @throws ErrorPageError
 177+ */
 178+ protected function checkCanExecute( User $user ) {
 179+ if( $this->requiresWrite() && wfReadOnly() ){
 180+ throw new ReadOnlyError();
 181+ }
 182+
 183+ if( $this->getRestriction() !== null && !$user->isAllowed( $this->getRestriction() ) ){
 184+ throw new PermissionsError( $this->getRestriction() );
 185+ }
 186+
 187+ if( $this->requiresUnblock() && $user->isBlocked() ){
 188+ $block = $user->mBlock;
 189+ throw new UserBlockedError( $block );
 190+ }
 191+ }
 192+
 193+ /**
 194+ * Whether this action requires the wiki not to be locked
 195+ * @return Bool
 196+ */
 197+ public function requiresWrite(){
 198+ return true;
 199+ }
 200+
 201+ /**
 202+ * Whether this action can still be executed by a blocked user
 203+ * @return Bool
 204+ */
 205+ public function requiresUnblock(){
 206+ return true;
 207+ }
 208+
 209+ /**
 210+ * Set output headers for noindexing etc. This function will not be called through
 211+ * the execute() entry point, so only put UI-related stuff in here.
 212+ */
 213+ protected function setHeaders() {
 214+ $out = $this->getOutput();
 215+ $out->setRobotPolicy( "noindex,nofollow" );
 216+ $out->setPageTitle( $this->getTitle()->getPrefixedText() );
 217+ $this->getOutput()->setSubtitle( $this->getDescription() );
 218+ $out->setArticleRelated( true );
 219+ }
 220+
 221+ /**
 222+ * Returns the name that goes in the \<h1\> page title
 223+ *
 224+ * Derived classes can override this, but usually it is easier to keep the
 225+ * default behaviour. Messages can be added at run-time, see
 226+ * MessageCache.php.
 227+ *
 228+ * @return String
 229+ */
 230+ protected function getDescription() {
 231+ return wfMsg( strtolower( $this->getName() ) );
 232+ }
 233+
 234+ /**
 235+ * The main action entry point. Do all output for display and send it to the context
 236+ * output. Do not use globals $wgOut, $wgRequest, etc, in implementations; use
 237+ * $this->getOutput(), etc.
 238+ * @throws ErrorPageError
 239+ */
 240+ public abstract function show();
 241+
 242+ /**
 243+ * Execute the action in a silent fashion: do not display anything or release any errors.
 244+ * @param $data Array values that would normally be in the POST request
 245+ * @param $captureErrors Bool whether to catch exceptions and just return false
 246+ * @return Bool whether execution was successful
 247+ */
 248+ public abstract function execute();
 249+}
 250+
 251+abstract class FormAction extends Action {
 252+
 253+ /**
 254+ * Get an HTMLForm descriptor array
 255+ * @return Array
 256+ */
 257+ protected abstract function getFormFields();
 258+
 259+ /**
 260+ * Add pre- or post-text to the form
 261+ * @return String HTML which will be sent to $form->addPreText()
 262+ */
 263+ protected function preText(){ return ''; }
 264+ protected function postText(){ return ''; }
 265+
 266+ /**
 267+ * Play with the HTMLForm if you need to more substantially
 268+ * @param &$form HTMLForm
 269+ */
 270+ protected function alterForm( HTMLForm &$form ){}
 271+
 272+ /**
 273+ * Get the HTMLForm to control behaviour
 274+ * @return HTMLForm|null
 275+ */
 276+ protected function getForm(){
 277+ $this->fields = $this->getFormFields();
 278+
 279+ // Give hooks a chance to alter the form, adding extra fields or text etc
 280+ wfRunHooks( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
 281+
 282+ $form = new HTMLForm( $this->fields, $this->getContext() );
 283+ $form->setSubmitCallback( array( $this, 'onSubmit' ) );
 284+ $form->addHiddenField( 'action', $this->getName() );
 285+
 286+ $form->addPreText( $this->preText() );
 287+ $form->addPostText( $this->postText() );
 288+ $this->alterForm( $form );
 289+
 290+ // Give hooks a chance to alter the form, adding extra fields or text etc
 291+ wfRunHooks( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
 292+
 293+ return $form;
 294+ }
 295+
 296+ /**
 297+ * Process the form on POST submission. If you return false from getFormFields(),
 298+ * this will obviously never be reached. If you don't want to do anything with the
 299+ * form, just return false here
 300+ * @param $data Array
 301+ * @return Bool|Array true for success, false for didn't-try, array of errors on failure
 302+ */
 303+ public abstract function onSubmit( $data );
 304+
 305+ /**
 306+ * Do something exciting on successful processing of the form. This might be to show
 307+ * a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
 308+ * protect, etc).
 309+ */
 310+ public abstract function onSuccess();
 311+
 312+ /**
 313+ * The basic pattern for actions is to display some sort of HTMLForm UI, maybe with
 314+ * some stuff underneath (history etc); to do some processing on submission of that
 315+ * form (delete, protect, etc) and to do something exciting on 'success', be that
 316+ * display something new or redirect to somewhere. Some actions have more exotic
 317+ * behaviour, but that's what subclassing is for :D
 318+ */
 319+ public function show(){
 320+ $this->setHeaders();
 321+
 322+ // This will throw exceptions if there's a problem
 323+ $this->checkCanExecute( $this->getUser() );
 324+
 325+ $form = $this->getForm();
 326+ if( $form->show() ){
 327+ $this->onSuccess();
 328+ }
 329+ }
 330+
 331+ /**
 332+ * @see Action::execute()
 333+ * @throws ErrorPageError
 334+ * @param array|null $data
 335+ * @param bool $captureErrors
 336+ * @return bool
 337+ */
 338+ public function execute( array $data = null, $captureErrors = true ){
 339+ try {
 340+ // Set a new context so output doesn't leak.
 341+ $this->context = clone $this->page->getContext();
 342+
 343+ // This will throw exceptions if there's a problem
 344+ $this->checkCanExecute( $this->getUser() );
 345+
 346+ $fields = array();
 347+ foreach( $this->fields as $key => $params ){
 348+ if( isset( $data[$key] ) ){
 349+ $fields[$key] = $data[$key];
 350+ } elseif( isset( $params['default'] ) ) {
 351+ $fields[$key] = $params['default'];
 352+ } else {
 353+ $fields[$key] = null;
 354+ }
 355+ }
 356+ $status = $this->onSubmit( $fields );
 357+ if( $status === true ){
 358+ // This might do permanent stuff
 359+ $this->onSuccess();
 360+ return true;
 361+ } else {
 362+ return false;
 363+ }
 364+ }
 365+ catch ( ErrorPageError $e ){
 366+ if( $captureErrors ){
 367+ return false;
 368+ } else {
 369+ throw $e;
 370+ }
 371+ }
 372+ }
 373+}
 374+
 375+/**
 376+ * Actions generally fall into two groups: the show-a-form-then-do-something-with-the-input
 377+ * format (protect, delete, move, etc), and the just-do-something format (watch, rollback,
 378+ * patrol, etc).
 379+ */
 380+abstract class FormlessAction extends Action {
 381+
 382+ /**
 383+ * Show something on GET request.
 384+ * @return String|null will be added to the HTMLForm if present, or just added to the
 385+ * output if not. Return null to not add anything
 386+ */
 387+ public abstract function onView();
 388+
 389+ /**
 390+ * We don't want an HTMLForm
 391+ */
 392+ protected function getFormFields(){
 393+ return false;
 394+ }
 395+
 396+ public function onSubmit( $data ){
 397+ return false;
 398+ }
 399+
 400+ public function onSuccess(){
 401+ return false;
 402+ }
 403+
 404+ public function show(){
 405+ $this->setHeaders();
 406+
 407+ // This will throw exceptions if there's a problem
 408+ $this->checkCanExecute( $this->getUser() );
 409+
 410+ $this->getOutput()->addHTML( $this->onView() );
 411+ }
 412+
 413+ /**
 414+ * Execute the action silently, not giving any output. Since these actions don't have
 415+ * forms, they probably won't have any data, but some (eg rollback) may do
 416+ * @param $data Array values that would normally be in the GET request
 417+ * @param $captureErrors Bool whether to catch exceptions and just return false
 418+ * @return Bool whether execution was successful
 419+ */
 420+ public function execute( array $data = null, $captureErrors = true){
 421+ try {
 422+ // Set a new context so output doesn't leak.
 423+ $this->context = clone $this->page->getContext();
 424+ if( is_array( $data ) ){
 425+ $this->context->setRequest( new FauxRequest( $data, false ) );
 426+ }
 427+
 428+ // This will throw exceptions if there's a problem
 429+ $this->checkCanExecute( $this->getUser() );
 430+
 431+ $this->onView();
 432+ return true;
 433+ }
 434+ catch ( ErrorPageError $e ){
 435+ if( $captureErrors ){
 436+ return false;
 437+ } else {
 438+ throw $e;
 439+ }
 440+ }
 441+ }
 442+}
\ No newline at end of file
Property changes on: trunk/phase3/includes/Action.php
___________________________________________________________________
Added: svn:eol-style
1443 + native

Follow-up revisions

RevisionCommit summaryAuthorDate
r86044Follow-up r 86041 per CR and IRC:...happy-melon12:17, 14 April 2011
r86143Follow-up r86041: fix redirect in PurgeAction, and pass query parameters thro...happy-melon22:11, 15 April 2011
r87642Autoloader entries for r86041.happy-melon15:30, 7 May 2011
r87933Fix for r86041: when the user wants to unwatch the article, really unwatch it ;)ialex20:39, 12 May 2011
r89547Use tokens where needed for WatchAction (as of r89545)...krinkle00:32, 6 June 2011
r90098Follow-up to r86041 and r89569 - re-added backwards compatibility with MW < 1.18yaron23:24, 14 June 2011
r103471Apply cryptocoryne's patch from Bug 32454 - ArticlePurge hook is broken after...mah16:00, 17 November 2011
r110719[Actions] Update usage of wgDisabledActions to check wgActions instead...krinkle13:30, 5 February 2012

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r86001New infrastructure for actions, as discussed on wikitech-l. Fairly huge commit....happy-melon23:04, 13 April 2011

Comments

#Comment by Bryan (talk | contribs)   11:14, 14 April 2011

You're awesome. I think you have cured the nightmares that many people get when they open Article.php.

A few remarks:

+       /**
+        * 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() ) );
+       }

I think you should return a wfMessage() instead of wfMsg(). This allows callers to determine themselves how to represent the message. For example, the API wants to return the plaintext, possibly parsemag'ed version of the message, while the regular UI wants a HTML representation.


Something that I don't like about this design, is the fact that UI-generation and backend-actions are tightly coupled, by means of the Form(less)Action class which derives from Action. I would personally decouple them something like this:

class Action {}
class ProtectAction extends Action {
[...]
}

class FormAction {
[...]
}

$form = new FormAction;
$form->setAction( 'ProtectAction' );
$form->show();

class ApiForAction {
[...]
}

$api = new ApiAction;
$api->setAction( 'ProtectAction' );
$api->execute();

(I haven't though very well about it, and it might be overzealous abstraction, but I'd at least look into this way)

#Comment by Aaron Schulz (talk | contribs)   14:00, 14 April 2011

I'd agree about separating UI from action logic here. I ended up doing that for FlaggedRevs actions a good while ago so that the GUI and API could share the logic.

#Comment by IAlex (talk | contribs)   12:08, 14 April 2011
  • This broke inline credits: Fatal error: Class 'Credits' not found in includes/SkinTemplate.php on line 385 (there's a similar call in SkinLegacy)
  • Action class is missing an accessor for the user language object (i.e. replacement of $wgLang)
  • Asking people to not use globals $wgOut, $wgRequest, etc and in the meantime using wfMessage() or other wfMsg*() functions is inconsistent, since wfMessage() is depending on $wgLang, $wgOut and $wgTitle (depending on how your are using it).
#Comment by Happy-melon (talk | contribs)   12:21, 14 April 2011

"Incomplete", maybe, but not inconsistent. The point of not accessing the globals (apart from encouraging our drive away from globals full stop) is so that you can capture the output of the action and discard it if you want.

#Comment by IAlex (talk | contribs)   14:13, 14 April 2011

A possiblity is also to run this internally (as the parser does when including special pages), in which case you need to have the correct context.

Also, for the ActionModifyFormFields and the ActionBeforeFormDisplay hook, wouldn't it be better to the pass the Action object itself and add an accessor for the Article object instead of passing only its name and the Article object?

#Comment by Bawolff (talk | contribs)   22:01, 14 April 2011

This does look much nicer :)

Unfortunately it breaks action=purge, because it tries to redirect to $this->getTitle() instead of $this->getTitle()->getFullUrl() (works sometimes due to relative url, but breaks horribly if the page is not in main namespace since colon is un-escaped, or if person is not using short urls, etc).

Also we'd probably want to preserve the query parameters there (except possibly the action=purge parameter) if for example someone was purging a category with the frompage=foo parameter, etc. I'm not really sure if there's actually much benefit of redirecting after the purge...

#Comment by Happy-melon (talk | contribs)   10:24, 15 April 2011

It redirects because it otherwise doesn't know what else to do; since things are now properly separated "viewing the page" on action=purge doesn't make sense. I suppose it could call Action::factory( 'view', $this->page )->show() internally, but that seems rather kludgish.

#Comment by Bawolff (talk | contribs)   16:31, 15 April 2011

I suppose in many ways redirecting (providing it redirects to the right place) does make sense. After all purging is the act of getting rid of the old version, which is quite separate from viewing. It could potentially cause confusion to users who expect to see the ?action=purge, but they'd get over it pretty quickly.

#Comment by Happy-melon (talk | contribs)   16:59, 15 April 2011

I would hope that they'd quickly see, or at least subconsciously accept, the similarity in behaviour between action=purge, which redirects back to action=view, and actions like protect, edit, etc, which do the same thing (and have for ever). What does not make semantic sense is to be seeing the view of an article on an action which is not view.

#Comment by Nikerabbit (talk | contribs)   17:03, 15 April 2011

It could help if the redirection actually worked :/

#Comment by Catrope (talk | contribs)   09:31, 15 April 2011

Provisionally tagging revert1.18, per Happy-melon this is 1.19 material.

#Comment by Duplicatebug (talk | contribs)   20:08, 20 April 2011

Please have a look at r86183 (Article::doDeleteArticle vs. DeleteAction::doDeleteArticle) and apply it also to DeleteAction. Thanks.

#Comment by Happy-melon (talk | contribs)   20:11, 20 April 2011

DeleteAction is not implemented; the file was not deleted in r86009, not sure why.

#Comment by Platonides (talk | contribs)   22:41, 3 May 2011

You are freely using FormAction and FormlessAction but they aren't registered in the autoloader.

#Comment by Happy-melon (talk | contribs)   23:14, 3 May 2011

There aren't fatals as a result because Action::factory() loads Action.php, but you're right that they should have entries regardless.

#Comment by Happy-melon (talk | contribs)   15:31, 7 May 2011

Added in r87642.

#Comment by 😂 (talk | contribs)   23:18, 3 May 2011

I don't like introducing a new global for this. Move it into the class.

#Comment by Happy-melon (talk | contribs)   07:23, 4 May 2011

What global? $wgActions is a config setting.

#Comment by 😂 (talk | contribs)   16:41, 7 June 2011

We don't need it for core, and there's already MediaWikiPerformAction and UnknownAction hooks to handle things in extensions.

#Comment by Tim Starling (talk | contribs)   01:54, 31 May 2011

Why did you make action=purge require an edit token? And why is no error message shown when the edit token is incorrect?

#Comment by Happy-melon (talk | contribs)   08:54, 31 May 2011

Although it's something of a side effect, I think it's beneficial; we've recently introduced tokens for other minor write actions like <t>markautopatrolled (bug 24418) and watch/unwatch (bug 27655). It not displaying an invalid token error is a fault of the underlying HTMLForm

#Comment by Krinkle (talk | contribs)   00:26, 6 June 2011

In /trunk/extensions/MetavidWiki/includes/articlepages/MV_DataPage.php a call to doUnwatch() was replaced with 'watch'.

Template:ViewVC

#Comment by Happy-melon (talk | contribs)   09:38, 7 June 2011

Was fixed as part of r89547.

#Comment by Happy-melon (talk | contribs)   09:40, 7 June 2011

Removing revert1.18; as this is now the foundation on which the token-needed-for-watch breaking change is implemented.

#Comment by Brion VIBBER (talk | contribs)   21:39, 21 June 2011

Purge seems to have started redirecting, removing the 'action=purge' parameter from the URL. This is something of a drag when testing, as you can no longer reload to re-purge, but have to manually re-add it to the URL bar every time.

#Comment by Aaron Schulz (talk | contribs)   21:41, 21 June 2011

I've experience that annoyance too.

#Comment by Brion VIBBER (talk | contribs)   21:51, 21 June 2011

Per notes above though, it's true that there have been problems with using action=purge as an alternate view, sometimes confusing JS etc. So actually I'll consider this ok for now (but if we find a better solution, spiff!)

#Comment by Aaron Schulz (talk | contribs)   21:51, 21 June 2011

For sure, it's acceptable.

#Comment by Tim Starling (talk | contribs)   23:12, 21 June 2011

You can always use curl if you're testing caching, or preview if you're testing parsing.

#Comment by Krinkle (talk | contribs)   13:34, 5 February 2012

$wgDisabledActions was deprecated in this commit in favor of $wgActions , and in Setup.php the old is checked and if it has any values still, it is transformed and appended to the new one. This means that wgDisabledActions is only a subset of wgActions (or even empty).

MediaWiki::getAction was still only checking wgDisabledActions (code). In the mean time this function was refactored and moved into the Action class. I've fixed the usage of wgDisabledActions in r110719.

Status & tagging log