r86001 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r86000‎ | r86001 | r86002 >
Date:23:04, 13 April 2011
Author:happy-melon
Status:reverted (Comments)
Tags:
Comment:
New infrastructure for actions, as discussed on wikitech-l. Fairly huge commit.
* Actions come in two flavours: the show-a-form-then-do-something-with-the-result (delete, protect, edit, etc) and the just-do-something (watch, rollback, patrol, etc). Create abstract base classes Action and FormlessAction to support these two cases. HTMLForm is an integral part of the form-based structure.
* Look mum, no globals! :D Fully context-based.
* Implement watch/unwatch, credits and delete actions in the new system as proof-of-concept. This also gives the delete frontend a much-needed overhaul.
* Stub out the newly-deprecated functions from Article.php. This already reduces its linecount by about 15%, and there are plenty more actions still to do.
* Centralising actions like this is going to render a lot of hooks type-incompatible. There's simply nowhere you can put the ArticleConfirmDelete hook, for instance, where it can be passed an OutputPage as the second parameter. On the other hand, we can implement new hooks like ActionModifyFormFields and ActionBeforeFormDisplay, which can do much prettier stuff to the forms, like adding extra fields the 'right' way. Update LiquidThreads to use these new hooks where appropriate.
Modified paths:
  • /trunk/extensions/LiquidThreads/LiquidThreads.php (modified) (history)
  • /trunk/extensions/LiquidThreads/classes/DeletionController.php (modified) (history)
  • /trunk/extensions/LiquidThreads/classes/Thread.php (modified) (history)
  • /trunk/extensions/MetavidWiki/includes/articlepages/MV_DataPage.php (modified) (history)
  • /trunk/extensions/MultilingualLiquidThreads/LiquidThreads/LiquidThreads.php (modified) (history)
  • /trunk/extensions/MultilingualLiquidThreads/LiquidThreads/classes/DeletionController.php (modified) (history)
  • /trunk/extensions/MultilingualLiquidThreads/LiquidThreads/classes/Thread.php (modified) (history)
  • /trunk/extensions/PureWikiDeletion/PureWikiDeletion.hooks.php (modified) (history)
  • /trunk/extensions/PureWikiDeletion/PureWikiDeletion.php (modified) (history)
  • /trunk/extensions/ReplaceText/ReplaceTextJob.php (modified) (history)
  • /trunk/phase3/docs/hooks.txt (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/CreditsAction.php (added) (history)
  • /trunk/phase3/includes/actions/DeleteAction.php (added) (history)
  • /trunk/phase3/includes/actions/WatchAction.php (added) (history)
  • /trunk/phase3/includes/api/ApiBase.php (modified) (history)
  • /trunk/phase3/includes/api/ApiDelete.php (modified) (history)
  • /trunk/phase3/includes/api/ApiWatch.php (modified) (history)
  • /trunk/phase3/includes/specials/SpecialMovepage.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)

Diff [purge]

Index: trunk/phase3/docs/hooks.txt
@@ -264,6 +264,17 @@
265265 $user: the User object about to be created (read-only, incomplete)
266266 $message: out parameter: error message to display on abort
267267
 268+'ActionBeforeFormDisplay': Modify the form shown for an action (added 1.18)
 269+$action: String
 270+$form: HTMLForm
 271+$page: Article
 272+
 273+'ActionModifyFormFields': Modify the descriptor array which will be used to create an
 274+action form
 275+$action: String
 276+$fields: Array
 277+$page: Article
 278+
268279 'AddNewAccount': after a user account is created
269280 $user: the User object that was created. (Parameter added in 1.7)
270281 $byEmail: true when account was created "by email" (added in 1.12)
@@ -384,12 +395,6 @@
385396 $article: the article (object) being loaded from the database
386397 $content: the content (string) of the article
387398
388 -'ArticleConfirmDelete': before writing the confirmation form for article
389 - deletion
390 -$article: the article (object) being deleted
391 -$output: the OutputPage object ($wgOut)
392 -&$reason: the reason (string) the article is being deleted
393 -
394399 'ArticleContentOnDiff': before showing the article content below a diff.
395400 Use this to change the content in this area or how it is loaded.
396401 $diffEngine: the DifferenceEngine
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
@@ -2345,27 +2345,10 @@
23462346
23472347 /**
23482348 * User-interface handler for the "watch" action
 2349+ * @deprecated since 1.18
23492350 */
23502351 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() );
 2352+ Action::factory( 'watch', $this )->show();
23702353 }
23712354
23722355 /**
@@ -2374,64 +2357,27 @@
23752358 * This is safe to be called multiple times
23762359 *
23772360 * @return bool true on successful watch operation
 2361+ * @deprecated since 1.18
23782362 */
23792363 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;
 2364+ return Action::factory( 'watch', $this )->execute();
23922365 }
23932366
23942367 /**
23952368 * User interface handler for the "unwatch" action.
 2369+ * @deprecated since 1.18
23962370 */
23972371 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() );
 2372+ Action::factory( 'unwatch', $this )->show();
24172373 }
24182374
24192375 /**
24202376 * Stop watching a page
24212377 * @return bool true on successful unwatch
 2378+ * @deprecated since 1.18
24222379 */
24232380 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;
 2381+ return Action::factory( 'unwatch', $this )->execute();
24362382 }
24372383
24382384 /**
@@ -2663,229 +2609,28 @@
26642610 * @param &$hasHistory Boolean: whether the page has a history
26652611 * @return mixed String containing deletion reason or empty string, or boolean false
26662612 * if no revision occurred
 2613+ * @deprecated since 1.18
26672614 */
26682615 public function generateReason( &$hasHistory ) {
2669 - global $wgContLang;
2670 -
2671 - $dbw = wfGetDB( DB_MASTER );
2672 - // Get the last revision
2673 - $rev = Revision::newFromTitle( $this->mTitle );
2674 -
2675 - if ( is_null( $rev ) ) {
2676 - return false;
2677 - }
2678 -
2679 - // Get the article's contents
2680 - $contents = $rev->getText();
2681 - $blank = false;
2682 -
2683 - // If the page is blank, use the text from the previous revision,
2684 - // which can only be blank if there's a move/import/protect dummy revision involved
2685 - if ( $contents == '' ) {
2686 - $prev = $rev->getPrevious();
2687 -
2688 - if ( $prev ) {
2689 - $contents = $prev->getText();
2690 - $blank = true;
2691 - }
2692 - }
2693 -
2694 - // Find out if there was only one contributor
2695 - // Only scan the last 20 revisions
2696 - $res = $dbw->select( 'revision', 'rev_user_text',
2697 - array( 'rev_page' => $this->getID(), $dbw->bitAnd( 'rev_deleted', Revision::DELETED_USER ) . ' = 0' ),
2698 - __METHOD__,
2699 - array( 'LIMIT' => 20 )
2700 - );
2701 -
2702 - if ( $res === false ) {
2703 - // This page has no revisions, which is very weird
2704 - return false;
2705 - }
2706 -
2707 - $hasHistory = ( $res->numRows() > 1 );
2708 - $row = $dbw->fetchObject( $res );
2709 -
2710 - if ( $row ) { // $row is false if the only contributor is hidden
2711 - $onlyAuthor = $row->rev_user_text;
2712 - // Try to find a second contributor
2713 - foreach ( $res as $row ) {
2714 - if ( $row->rev_user_text != $onlyAuthor ) { // Bug 22999
2715 - $onlyAuthor = false;
2716 - break;
2717 - }
2718 - }
2719 - } else {
2720 - $onlyAuthor = false;
2721 - }
2722 -
2723 - // Generate the summary with a '$1' placeholder
2724 - if ( $blank ) {
2725 - // The current revision is blank and the one before is also
2726 - // blank. It's just not our lucky day
2727 - $reason = wfMsgForContent( 'exbeforeblank', '$1' );
2728 - } else {
2729 - if ( $onlyAuthor ) {
2730 - $reason = wfMsgForContent( 'excontentauthor', '$1', $onlyAuthor );
2731 - } else {
2732 - $reason = wfMsgForContent( 'excontent', '$1' );
2733 - }
2734 - }
2735 -
2736 - if ( $reason == '-' ) {
2737 - // Allow these UI messages to be blanked out cleanly
2738 - return '';
2739 - }
2740 -
2741 - // Replace newlines with spaces to prevent uglyness
2742 - $contents = preg_replace( "/[\n\r]/", ' ', $contents );
2743 - // Calculate the maximum amount of chars to get
2744 - // Max content length = max comment length - length of the comment (excl. $1)
2745 - $maxLength = 255 - ( strlen( $reason ) - 2 );
2746 - $contents = $wgContLang->truncate( $contents, $maxLength );
2747 - // Remove possible unfinished links
2748 - $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
2749 - // Now replace the '$1' placeholder
2750 - $reason = str_replace( '$1', $contents, $reason );
2751 -
2752 - return $reason;
 2616+ return DeleteAction::getAutoReason( $this );
27532617 }
27542618
27552619
27562620 /*
27572621 * UI entry point for page deletion
 2622+ * @deprecated since 1.18
27582623 */
27592624 public function delete() {
2760 - global $wgOut, $wgRequest;
2761 -
2762 - $confirm = $wgRequest->wasPosted() &&
2763 - $wgOut->getUser()->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
2764 -
2765 - $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
2766 - $this->DeleteReason = $wgRequest->getText( 'wpReason' );
2767 -
2768 - $reason = $this->DeleteReasonList;
2769 -
2770 - if ( $reason != 'other' && $this->DeleteReason != '' ) {
2771 - // Entry from drop down menu + additional comment
2772 - $reason .= wfMsgForContent( 'colon-separator' ) . $this->DeleteReason;
2773 - } elseif ( $reason == 'other' ) {
2774 - $reason = $this->DeleteReason;
2775 - }
2776 -
2777 - # Flag to hide all contents of the archived revisions
2778 - $suppress = $wgRequest->getVal( 'wpSuppress' ) && $wgOut->getUser()->isAllowed( 'suppressrevision' );
2779 -
2780 - # This code desperately needs to be totally rewritten
2781 -
2782 - # Read-only check...
2783 - if ( wfReadOnly() ) {
2784 - $wgOut->readOnlyPage();
2785 -
2786 - return;
2787 - }
2788 -
2789 - # Check permissions
2790 - $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgOut->getUser() );
2791 -
2792 - if ( count( $permission_errors ) > 0 ) {
2793 - $wgOut->showPermissionsErrorPage( $permission_errors );
2794 -
2795 - return;
2796 - }
2797 -
2798 - $wgOut->setPagetitle( wfMsg( 'delete-confirm', $this->mTitle->getPrefixedText() ) );
2799 -
2800 - # Better double-check that it hasn't been deleted yet!
2801 - $dbw = wfGetDB( DB_MASTER );
2802 - $conds = $this->mTitle->pageCond();
2803 - $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ );
2804 - if ( $latest === false ) {
2805 - $wgOut->showFatalError(
2806 - Html::rawElement(
2807 - 'div',
2808 - array( 'class' => 'error mw-error-cannotdelete' ),
2809 - wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
2810 - )
2811 - );
2812 - $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
2813 - LogEventsList::showLogExtract(
2814 - $wgOut,
2815 - 'delete',
2816 - $this->mTitle->getPrefixedText()
2817 - );
2818 -
2819 - return;
2820 - }
2821 -
2822 - # Hack for big sites
2823 - $bigHistory = $this->isBigDeletion();
2824 - if ( $bigHistory && !$this->mTitle->userCan( 'bigdelete' ) ) {
2825 - global $wgLang, $wgDeleteRevisionsLimit;
2826 -
2827 - $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
2828 - array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
2829 -
2830 - return;
2831 - }
2832 -
2833 - if ( $confirm ) {
2834 - $this->doDelete( $reason, $suppress );
2835 -
2836 - if ( $wgRequest->getCheck( 'wpWatch' ) && $wgOut->getUser()->isLoggedIn() ) {
2837 - $this->doWatch();
2838 - } elseif ( $this->mTitle->userIsWatching() ) {
2839 - $this->doUnwatch();
2840 - }
2841 -
2842 - return;
2843 - }
2844 -
2845 - // Generate deletion reason
2846 - $hasHistory = false;
2847 - if ( !$reason ) {
2848 - $reason = $this->generateReason( $hasHistory );
2849 - }
2850 -
2851 - // If the page has a history, insert a warning
2852 - if ( $hasHistory && !$confirm ) {
2853 - global $wgLang;
2854 -
2855 - $skin = $wgOut->getSkin();
2856 - $revisions = $this->estimateRevisionCount();
2857 - //FIXME: lego
2858 - $wgOut->addHTML( '<strong class="mw-delete-warning-revisions">' .
2859 - wfMsgExt( 'historywarning', array( 'parseinline' ), $wgLang->formatNum( $revisions ) ) .
2860 - wfMsgHtml( 'word-separator' ) . $skin->link( $this->mTitle,
2861 - wfMsgHtml( 'history' ),
2862 - array( 'rel' => 'archives' ),
2863 - array( 'action' => 'history' ) ) .
2864 - '</strong>'
2865 - );
2866 -
2867 - if ( $bigHistory ) {
2868 - global $wgDeleteRevisionsLimit;
2869 - $wgOut->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
2870 - array( 'delete-warning-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
2871 - }
2872 - }
2873 -
2874 - return $this->confirmDelete( $reason );
 2625+ return Action::factory( 'delete', $this )->show();
28752626 }
28762627
28772628 /**
28782629 * @return bool whether or not the page surpasses $wgDeleteRevisionsLimit revisions
 2630+ * @deprecated since 1.18
28792631 */
28802632 public function isBigDeletion() {
28812633 global $wgDeleteRevisionsLimit;
2882 -
2883 - if ( $wgDeleteRevisionsLimit ) {
2884 - $revCount = $this->estimateRevisionCount();
2885 -
2886 - return $revCount > $wgDeleteRevisionsLimit;
2887 - }
2888 -
2889 - return false;
 2634+ return $wgDeleteRevisionsLimit && $this->estimateRevisionCount() > $wgDeleteRevisionsLimit;
28902635 }
28912636
28922637 /**
@@ -2954,150 +2699,19 @@
29552700 }
29562701
29572702 /**
2958 - * Output deletion confirmation dialog
2959 - * FIXME: Move to another file?
2960 - * @param $reason String: prefilled reason
2961 - */
2962 - public function confirmDelete( $reason ) {
2963 - global $wgOut;
2964 -
2965 - wfDebug( "Article::confirmDelete\n" );
2966 -
2967 - $deleteBackLink = $wgOut->getSkin()->linkKnown( $this->mTitle );
2968 - $wgOut->setSubtitle( wfMsgHtml( 'delete-backlink', $deleteBackLink ) );
2969 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
2970 - $wgOut->addWikiMsg( 'confirmdeletetext' );
2971 -
2972 - wfRunHooks( 'ArticleConfirmDelete', array( $this, $wgOut, &$reason ) );
2973 -
2974 - if ( $wgOut->getUser()->isAllowed( 'suppressrevision' ) ) {
2975 - $suppress = "<tr id=\"wpDeleteSuppressRow\">
2976 - <td></td>
2977 - <td class='mw-input'><strong>" .
2978 - Xml::checkLabel( wfMsg( 'revdelete-suppress' ),
2979 - 'wpSuppress', 'wpSuppress', false, array( 'tabindex' => '4' ) ) .
2980 - "</strong></td>
2981 - </tr>";
2982 - } else {
2983 - $suppress = '';
2984 - }
2985 - $checkWatch = $wgOut->getUser()->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching();
2986 -
2987 - $form = Xml::openElement( 'form', array( 'method' => 'post',
2988 - 'action' => $this->mTitle->getLocalURL( 'action=delete' ), 'id' => 'deleteconfirm' ) ) .
2989 - Xml::openElement( 'fieldset', array( 'id' => 'mw-delete-table' ) ) .
2990 - Xml::tags( 'legend', null, wfMsgExt( 'delete-legend', array( 'parsemag', 'escapenoentities' ) ) ) .
2991 - Xml::openElement( 'table', array( 'id' => 'mw-deleteconfirm-table' ) ) .
2992 - "<tr id=\"wpDeleteReasonListRow\">
2993 - <td class='mw-label'>" .
2994 - Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' ) .
2995 - "</td>
2996 - <td class='mw-input'>" .
2997 - Xml::listDropDown( 'wpDeleteReasonList',
2998 - wfMsgForContent( 'deletereason-dropdown' ),
2999 - wfMsgForContent( 'deletereasonotherlist' ), '', 'wpReasonDropDown', 1 ) .
3000 - "</td>
3001 - </tr>
3002 - <tr id=\"wpDeleteReasonRow\">
3003 - <td class='mw-label'>" .
3004 - Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' ) .
3005 - "</td>
3006 - <td class='mw-input'>" .
3007 - Html::input( 'wpReason', $reason, 'text', array(
3008 - 'size' => '60',
3009 - 'maxlength' => '255',
3010 - 'tabindex' => '2',
3011 - 'id' => 'wpReason',
3012 - 'autofocus'
3013 - ) ) .
3014 - "</td>
3015 - </tr>";
3016 -
3017 - # Disallow watching if user is not logged in
3018 - if ( $wgOut->getUser()->isLoggedIn() ) {
3019 - $form .= "
3020 - <tr>
3021 - <td></td>
3022 - <td class='mw-input'>" .
3023 - Xml::checkLabel( wfMsg( 'watchthis' ),
3024 - 'wpWatch', 'wpWatch', $checkWatch, array( 'tabindex' => '3' ) ) .
3025 - "</td>
3026 - </tr>";
3027 - }
3028 -
3029 - $form .= "
3030 - $suppress
3031 - <tr>
3032 - <td></td>
3033 - <td class='mw-submit'>" .
3034 - Xml::submitButton( wfMsg( 'deletepage' ),
3035 - array( 'name' => 'wpConfirmB', 'id' => 'wpConfirmB', 'tabindex' => '5' ) ) .
3036 - "</td>
3037 - </tr>" .
3038 - Xml::closeElement( 'table' ) .
3039 - Xml::closeElement( 'fieldset' ) .
3040 - Html::hidden( 'wpEditToken', $wgOut->getUser()->editToken() ) .
3041 - Xml::closeElement( 'form' );
3042 -
3043 - if ( $wgOut->getUser()->isAllowed( 'editinterface' ) ) {
3044 - $skin = $wgOut->getSkin();
3045 - $title = Title::makeTitle( NS_MEDIAWIKI, 'Deletereason-dropdown' );
3046 - $link = $skin->link(
3047 - $title,
3048 - wfMsgHtml( 'delete-edit-reasonlist' ),
3049 - array(),
3050 - array( 'action' => 'edit' )
3051 - );
3052 - $form .= '<p class="mw-delete-editreasons">' . $link . '</p>';
3053 - }
3054 -
3055 - $wgOut->addHTML( $form );
3056 - $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
3057 - LogEventsList::showLogExtract( $wgOut, 'delete',
3058 - $this->mTitle->getPrefixedText()
3059 - );
3060 - }
3061 -
3062 - /**
30632703 * Perform a deletion and output success or failure messages
 2704+ * @deprecated since 1.18
30642705 */
30652706 public function doDelete( $reason, $suppress = false ) {
3066 - global $wgOut;
3067 -
3068 - $id = $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
3069 -
3070 - $error = '';
3071 - if ( $this->doDeleteArticle( $reason, $suppress, $id, $error ) ) {
3072 - $deleted = $this->mTitle->getPrefixedText();
3073 -
3074 - $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
3075 - $wgOut->setRobotPolicy( 'noindex,nofollow' );
3076 -
3077 - $loglink = '[[Special:Log/delete|' . wfMsgNoTrans( 'deletionlog' ) . ']]';
3078 -
3079 - $wgOut->addWikiMsg( 'deletedtext', $deleted, $loglink );
3080 - $wgOut->returnToMain( false );
3081 - } else {
3082 - if ( $error == '' ) {
3083 - $wgOut->showFatalError(
3084 - Html::rawElement(
3085 - 'div',
3086 - array( 'class' => 'error mw-error-cannotdelete' ),
3087 - wfMsgExt( 'cannotdelete', array( 'parse' ), $this->mTitle->getPrefixedText() )
3088 - )
3089 - );
3090 -
3091 - $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) );
3092 -
3093 - LogEventsList::showLogExtract(
3094 - $wgOut,
3095 - 'delete',
3096 - $this->mTitle->getPrefixedText()
3097 - );
3098 - } else {
3099 - $wgOut->showFatalError( $error );
3100 - }
3101 - }
 2707+ return DeleteAction::doDeleteArticle(
 2708+ $this,
 2709+ $this->getContext(),
 2710+ array(
 2711+ 'Suppress' => $suppress !== false,
 2712+ 'Reason' => $reason,
 2713+ ),
 2714+ true
 2715+ );
31022716 }
31032717
31042718 /**
@@ -3113,143 +2727,19 @@
31142728 * @param $id int article ID
31152729 * @param $commit boolean defaults to true, triggers transaction end
31162730 * @return boolean true if successful
 2731+ *
 2732+ * @deprecated since 1.18
31172733 */
31182734 public function doDeleteArticle( $reason, $suppress = false, $id = 0, $commit = true, &$error = '' ) {
3119 - global $wgDeferredUpdateList, $wgUseTrackbacks;
3120 - global $wgUser;
3121 -
3122 - wfDebug( __METHOD__ . "\n" );
3123 -
3124 - if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$wgUser, &$reason, &$error ) ) ) {
3125 - return false;
3126 - }
3127 - $dbw = wfGetDB( DB_MASTER );
3128 - $t = $this->mTitle->getDBkey();
3129 - $id = $id ? $id : $this->mTitle->getArticleID( Title::GAID_FOR_UPDATE );
3130 -
3131 - if ( $t === '' || $id == 0 ) {
3132 - return false;
3133 - }
3134 -
3135 - $u = new SiteStatsUpdate( 0, 1, - (int)$this->isCountable( $this->getRawText() ), -1 );
3136 - array_push( $wgDeferredUpdateList, $u );
3137 -
3138 - // Bitfields to further suppress the content
3139 - if ( $suppress ) {
3140 - $bitfield = 0;
3141 - // This should be 15...
3142 - $bitfield |= Revision::DELETED_TEXT;
3143 - $bitfield |= Revision::DELETED_COMMENT;
3144 - $bitfield |= Revision::DELETED_USER;
3145 - $bitfield |= Revision::DELETED_RESTRICTED;
3146 - } else {
3147 - $bitfield = 'rev_deleted';
3148 - }
3149 -
3150 - $dbw->begin();
3151 - // For now, shunt the revision data into the archive table.
3152 - // Text is *not* removed from the text table; bulk storage
3153 - // is left intact to avoid breaking block-compression or
3154 - // immutable storage schemes.
3155 - //
3156 - // For backwards compatibility, note that some older archive
3157 - // table entries will have ar_text and ar_flags fields still.
3158 - //
3159 - // In the future, we may keep revisions and mark them with
3160 - // the rev_deleted field, which is reserved for this purpose.
3161 - $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
 2735+ return DeleteAction::doDeleteArticle(
 2736+ $this,
 2737+ $this->getContext(),
31622738 array(
3163 - 'ar_namespace' => 'page_namespace',
3164 - 'ar_title' => 'page_title',
3165 - 'ar_comment' => 'rev_comment',
3166 - 'ar_user' => 'rev_user',
3167 - 'ar_user_text' => 'rev_user_text',
3168 - 'ar_timestamp' => 'rev_timestamp',
3169 - 'ar_minor_edit' => 'rev_minor_edit',
3170 - 'ar_rev_id' => 'rev_id',
3171 - 'ar_text_id' => 'rev_text_id',
3172 - 'ar_text' => '\'\'', // Be explicit to appease
3173 - 'ar_flags' => '\'\'', // MySQL's "strict mode"...
3174 - 'ar_len' => 'rev_len',
3175 - 'ar_page_id' => 'page_id',
3176 - 'ar_deleted' => $bitfield
3177 - ), array(
3178 - 'page_id' => $id,
3179 - 'page_id = rev_page'
3180 - ), __METHOD__
 2739+ 'Suppress' => $suppress !== false,
 2740+ 'Reason' => $reason,
 2741+ ),
 2742+ $commit
31812743 );
3182 -
3183 - # Delete restrictions for it
3184 - $dbw->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
3185 -
3186 - # Now that it's safely backed up, delete it
3187 - $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
3188 - $ok = ( $dbw->affectedRows() > 0 ); // getArticleId() uses slave, could be laggy
3189 -
3190 - if ( !$ok ) {
3191 - $dbw->rollback();
3192 - return false;
3193 - }
3194 -
3195 - # Fix category table counts
3196 - $cats = array();
3197 - $res = $dbw->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
3198 -
3199 - foreach ( $res as $row ) {
3200 - $cats [] = $row->cl_to;
3201 - }
3202 -
3203 - $this->updateCategoryCounts( array(), $cats );
3204 -
3205 - # If using cascading deletes, we can skip some explicit deletes
3206 - if ( !$dbw->cascadingDeletes() ) {
3207 - $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
3208 -
3209 - if ( $wgUseTrackbacks )
3210 - $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ );
3211 -
3212 - # Delete outgoing links
3213 - $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) );
3214 - $dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
3215 - $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
3216 - $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) );
3217 - $dbw->delete( 'externallinks', array( 'el_from' => $id ) );
3218 - $dbw->delete( 'langlinks', array( 'll_from' => $id ) );
3219 - $dbw->delete( 'redirect', array( 'rd_from' => $id ) );
3220 - }
3221 -
3222 - # If using cleanup triggers, we can skip some manual deletes
3223 - if ( !$dbw->cleanupTriggers() ) {
3224 - # Clean up recentchanges entries...
3225 - $dbw->delete( 'recentchanges',
3226 - array( 'rc_type != ' . RC_LOG,
3227 - 'rc_namespace' => $this->mTitle->getNamespace(),
3228 - 'rc_title' => $this->mTitle->getDBkey() ),
3229 - __METHOD__ );
3230 - $dbw->delete( 'recentchanges',
3231 - array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
3232 - __METHOD__ );
3233 - }
3234 -
3235 - # Clear caches
3236 - Article::onArticleDelete( $this->mTitle );
3237 -
3238 - # Clear the cached article id so the interface doesn't act like we exist
3239 - $this->mTitle->resetArticleID( 0 );
3240 -
3241 - # Log the deletion, if the page was suppressed, log it at Oversight instead
3242 - $logtype = $suppress ? 'suppress' : 'delete';
3243 - $log = new LogPage( $logtype );
3244 -
3245 - # Make sure logging got through
3246 - $log->addEntry( 'delete', $this->mTitle, $reason, array() );
3247 -
3248 - if ( $commit ) {
3249 - $dbw->commit();
3250 - }
3251 -
3252 - wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$wgUser, $reason, $id ) );
3253 - return true;
32542744 }
32552745
32562746 /**
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/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/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/WatchAction.php
@@ -0,0 +1,82 @@
 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+ protected function getDescription(){
 35+ return wfMsg( 'addedwatch' );
 36+ }
 37+
 38+ protected function checkCanExecute( User $user ){
 39+ if ( $user->isAnon() ) {
 40+ throw new ErrorPageError( 'watchnologin', 'watchnologintext' );
 41+ }
 42+ return parent::checkCanExecute( $user );
 43+ }
 44+
 45+ public function onView() {
 46+ wfProfileIn( __METHOD__ );
 47+
 48+ $user = $this->getUser();
 49+ if ( wfRunHooks( 'WatchArticle', array( &$user, &$this->page ) ) ) {
 50+ $this->getUser()->addWatch( $this->getTitle() );
 51+ wfRunHooks( 'WatchArticleComplete', array( &$user, &$this->page ) );
 52+ }
 53+
 54+ wfProfileOut( __METHOD__ );
 55+
 56+ return wfMessage( 'addedwatchtext', $this->getTitle()->getPrefixedText() )->parse();
 57+ }
 58+}
 59+
 60+class UnwatchAction extends WatchAction {
 61+
 62+ public function getName(){
 63+ return 'unwatch';
 64+ }
 65+
 66+ protected function getDescription(){
 67+ return wfMsg( 'removedwatch' );
 68+ }
 69+
 70+ public function onView() {
 71+ wfProfileIn( __METHOD__ );
 72+
 73+ $user = $this->getUser();
 74+ if ( wfRunHooks( 'UnwatchArticle', array( &$user, &$this->page ) ) ) {
 75+ $this->getUser()->removeWatch( $this->getTitle() );
 76+ wfRunHooks( 'UnwatchArticleComplete', array( &$user, &$this->page ) );
 77+ }
 78+
 79+ wfProfileOut( __METHOD__ );
 80+
 81+ return wfMessage( 'removedwatchtext', $this->getTitle()->getPrefixedText() )->parse();
 82+ }
 83+}
Property changes on: trunk/phase3/includes/actions/WatchAction.php
___________________________________________________________________
Added: svn:eol-style
184 + native
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/api/ApiDelete.php
@@ -123,11 +123,6 @@
124124 * @return Title::getUserPermissionsErrors()-like array
125125 */
126126 public static function delete( &$article, $token, &$reason = null ) {
127 - global $wgUser;
128 - if ( $article->isBigDeletion() && !$wgUser->isAllowed( 'bigdelete' ) ) {
129 - global $wgDeleteRevisionsLimit;
130 - return array( array( 'delete-toobig', $wgDeleteRevisionsLimit ) );
131 - }
132127 $title = $article->getTitle();
133128 $errors = self::getPermissionsError( $title, $token );
134129 if ( count( $errors ) ) {
@@ -136,22 +131,29 @@
137132
138133 // Auto-generate a summary, if necessary
139134 if ( is_null( $reason ) ) {
140 - // Need to pass a throwaway variable because generateReason expects
141 - // a reference
142 - $hasHistory = false;
143 - $reason = $article->generateReason( $hasHistory );
 135+ $reason = DeleteAction::getAutoReason( $article );
144136 if ( $reason === false ) {
145137 return array( array( 'cannotdelete' ) );
146138 }
147139 }
148140
149 - $error = '';
150 - // Luckily, Article.php provides a reusable delete function that does the hard work for us
151 - if ( $article->doDeleteArticle( $reason, false, 0, true, $error ) ) {
152 - return array();
153 - } else {
154 - return array( array( 'cannotdelete', $article->mTitle->getPrefixedText() ) );
 141+ $action = Action::factory( 'delete', $article );
 142+ $data = array(
 143+ 'Reason' => $reason,
 144+ 'Suppress' => false, // The thought of people doing this through the API is scary...
 145+ );
 146+
 147+ try {
 148+ $action->execute( $data, false );
155149 }
 150+ catch ( ErrorPageError $e ){
 151+ if( $e->msg == 'delete-toobig' ){
 152+ global $wgDeleteRevisionsLimit;
 153+ return array( array( 'delete-toobig', $wgDeleteRevisionsLimit ) );
 154+ } else {
 155+ array( array( 'cannotdelete', $article->mTitle->getPrefixedText() ) );
 156+ }
 157+ }
156158 }
157159
158160 /**
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/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+ 'DeleteAction' => 'includes/actions/DeleteAction.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,9 +491,6 @@
485492 $raw->view();
486493 wfProfileOut( __METHOD__ . '-raw' );
487494 break;
488 - case 'watch':
489 - case 'unwatch':
490 - case 'delete':
491495 case 'revert':
492496 case 'rollback':
493497 case 'protect':
@@ -496,7 +500,7 @@
497501 case 'render':
498502 case 'deletetrackback':
499503 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+ 'delete' => 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/specials/SpecialMovepage.php
@@ -359,8 +359,11 @@
360360 $article = new Article( $nt );
361361
362362 # Disallow deletions of big articles
363 - $bigHistory = $article->isBigDeletion();
364 - if( $bigHistory && !$nt->userCan( 'bigdelete' ) ) {
 363+ global $wgDeleteRevisionsLimit;
 364+ if ( $wgDeleteRevisionsLimit
 365+ && $this->estimateRevisionCount() > $wgDeleteRevisionsLimit
 366+ && !$nt->userCan( 'bigdelete' ) )
 367+ {
365368 global $wgDeleteRevisionsLimit;
366369 $this->showForm( array('delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) ) );
367370 return;
@@ -373,7 +376,10 @@
374377 }
375378
376379 // This may output an error message and exit
377 - $article->doDelete( wfMsgForContent( 'delete_and_move_reason' ) );
 380+ Action::factory( 'delete', $article )->execute(
 381+ array( 'Reason' => wfMsgForContent( 'delete_and_move_reason' ) ),
 382+ false // Do not capture exceptions
 383+ );
378384 }
379385
380386 # don't allow moving to pages with # in
Index: trunk/phase3/includes/Action.php
@@ -0,0 +1,440 @@
 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 null;
 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 skin 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 basic pattern for actions is to display some sort of HTMLForm UI, maybe with
 236+ * some stuff underneath (history etc); to do some processing on submission of that
 237+ * form (delete, protect, etc) and to do something exciting on 'success', be that
 238+ * display something new or redirect to somewhere. Some actions have more exotic
 239+ * behaviour, but that's what subclassing is for :D
 240+ */
 241+ public function show(){
 242+ $this->setHeaders();
 243+
 244+ // This will throw exceptions if there's a problem
 245+ $this->checkCanExecute( $this->getUser() );
 246+
 247+ $form = $this->getForm();
 248+ if( $form instanceof HTMLForm ){
 249+ if( $form->show() ){
 250+ $this->onSuccess();
 251+ }
 252+ } else {
 253+ // You're using the wrong type of Action
 254+ throw new MWException( "Action::getFormFields() must produce a form. Use GetAction if you don't want one." );
 255+ }
 256+ }
 257+
 258+ /**
 259+ * Execute the action in a silent fashion: do not display anything or release any errors.
 260+ * @param $data Array values that would normally be in the POST request
 261+ * @param $captureErrors Bool whether to catch exceptions and just return false
 262+ * @return Bool whether execution was successful
 263+ */
 264+ public function execute( array $data = null, $captureErrors = true ){
 265+ try {
 266+ // Set a new context so output doesn't leak.
 267+ $this->context = clone $this->page->getContext();
 268+
 269+ // This will throw exceptions if there's a problem
 270+ $this->checkCanExecute( $this->getUser() );
 271+
 272+ $form = $this->getForm();
 273+ if( $form instanceof HTMLForm ){
 274+ // Great, so there's a form. Ignore it and go straight to the submission callback
 275+ $fields = array();
 276+ foreach( $this->fields as $key => $params ){
 277+ if( isset( $data[$key] ) ){
 278+ $fields[$key] = $data[$key];
 279+ } elseif( isset( $params['default'] ) ) {
 280+ $fields[$key] = $params['default'];
 281+ } else {
 282+ $fields[$key] = null;
 283+ }
 284+ }
 285+ $status = $this->onSubmit( $fields );
 286+ if( $status === true ){
 287+ // This might do permanent stuff
 288+ $this->onSuccess();
 289+ return true;
 290+ } else {
 291+ return false;
 292+ }
 293+ } else {
 294+ // You're using the wrong type of Action
 295+ throw new MWException( "Action::getFormFields() must produce a form. Use GetAction if you don't want one." );
 296+ }
 297+ }
 298+ catch ( ErrorPageError $e ){
 299+ if( $captureErrors ){
 300+ return false;
 301+ } else {
 302+ throw $e;
 303+ }
 304+ }
 305+ }
 306+
 307+ /**
 308+ * Get an HTMLForm descriptor array, or false if you don't want a form
 309+ * @return Array
 310+ */
 311+ protected abstract function getFormFields();
 312+
 313+ /**
 314+ * Add pre- or post-text to the form
 315+ * @return String
 316+ */
 317+ protected function preText(){ return ''; }
 318+ protected function postText(){ return ''; }
 319+
 320+ /**
 321+ * Play with the HTMLForm if you need to more substantially
 322+ * @param &$form HTMLForm
 323+ */
 324+ protected function alterForm( HTMLForm &$form ){}
 325+
 326+ /**
 327+ * Get the HTMLForm to control behaviour
 328+ * @return HTMLForm|null
 329+ */
 330+ protected function getForm(){
 331+ $this->fields = $this->getFormFields();
 332+
 333+ // Give hooks a chance to alter the form, adding extra fields or text etc
 334+ wfRunHooks( 'ActionModifyFormFields', array( $this->getName(), &$this->fields, $this->page ) );
 335+
 336+ if( $this->fields === false ){
 337+ return null;
 338+ }
 339+
 340+ $form = new HTMLForm( $this->fields, $this->getContext() );
 341+ $form->setSubmitCallback( array( $this, 'onSubmit' ) );
 342+ $form->addHiddenField( 'action', $this->getName() );
 343+
 344+ $form->addPreText( $this->preText() );
 345+ $form->addPostText( $this->postText() );
 346+ $this->alterForm( $form );
 347+
 348+ // Give hooks a chance to alter the form, adding extra fields or text etc
 349+ wfRunHooks( 'ActionBeforeFormDisplay', array( $this->getName(), &$form, $this->page ) );
 350+
 351+ return $form;
 352+ }
 353+
 354+ /**
 355+ * Process the form on POST submission. If you return false from getFormFields(),
 356+ * this will obviously never be reached. If you don't want to do anything with the
 357+ * form, just return false here
 358+ * @param $data Array
 359+ * @return Bool|Array true for success, false for didn't-try, array of errors on failure
 360+ */
 361+ public abstract function onSubmit( $data );
 362+
 363+ /**
 364+ * Do something exciting on successful processing of the form. This might be to show
 365+ * a confirmation message (watch, rollback, etc) or to redirect somewhere else (edit,
 366+ * protect, etc).
 367+ */
 368+ public abstract function onSuccess();
 369+
 370+}
 371+
 372+/**
 373+ * Actions generally fall into two groups: the show-a-form-then-do-something-with-the-input
 374+ * format (protect, delete, move, etc), and the just-do-something format (watch, rollback,
 375+ * patrol, etc).
 376+ */
 377+abstract class FormlessAction extends Action {
 378+
 379+ /**
 380+ * Show something on GET request. This is displayed as the postText() of the HTMLForm
 381+ * if there is one; you can always use alterForm() to add pre text if you need it. If
 382+ * you call addPostText() from alterForm() as well as overriding this function, you
 383+ * might get strange ordering.
 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+ $this->getOutput()->addHTML( $this->onView() );
 410+ }
 411+
 412+ /**
 413+ * Execute the action silently, not giving any output. Since these actions don't have
 414+ * forms, they probably won't have any data, but some (eg rollback) may do
 415+ * @param $data Array values that would normally be in the GET request
 416+ * @param $captureErrors Bool whether to catch exceptions and just return false
 417+ * @return Bool whether execution was successful
 418+ */
 419+ public function execute( array $data = null, $captureErrors = true){
 420+ try {
 421+ // Set a new context so output doesn't leak.
 422+ $this->context = clone $this->page->getContext();
 423+ if( is_array( $data ) ){
 424+ $this->context->setRequest( new FauxRequest( $data, false ) );
 425+ }
 426+
 427+ // This will throw exceptions if there's a problem
 428+ $this->checkCanExecute( $this->getUser() );
 429+
 430+ $this->onView();
 431+ return true;
 432+ }
 433+ catch ( ErrorPageError $e ){
 434+ if( $captureErrors ){
 435+ return false;
 436+ } else {
 437+ throw $e;
 438+ }
 439+ }
 440+ }
 441+}
\ No newline at end of file
Property changes on: trunk/phase3/includes/Action.php
___________________________________________________________________
Added: svn:eol-style
1442 + native
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -998,8 +998,9 @@
999999 'unexpected' => 'Unexpected value: "$1"="$2".',
10001000 'formerror' => 'Error: could not submit form',
10011001 'badarticleerror' => 'This action cannot be performed on this page.',
1002 -'cannotdelete' => 'The page or file "$1" could not be deleted.
1003 -It may have already been deleted by someone else.',
 1002+'cannotdelete' => 'The page or file "$1" could not be deleted. It may have already been deleted by someone else. The deletion log is provided below for convenience.
 1003+
 1004+$2',
10041005 'badtitle' => 'Bad title',
10051006 'badtitletext' => 'The requested page title was invalid, empty, or an incorrectly linked inter-language or inter-wiki title.
10061007 It may contain one or more characters which cannot be used in titles.',
@@ -2783,7 +2784,7 @@
27842785 'delete-confirm' => 'Delete "$1"',
27852786 'delete-backlink' => '← $1', # only translate this message to other languages if you have to change it
27862787 'delete-legend' => 'Delete',
2787 -'historywarning' => "'''Warning:''' The page you are about to delete has a history with approximately $1 {{PLURAL:$1|revision|revisions}}:",
 2788+'historywarning' => "'''Warning:''' The page you are about to delete has a $2 with approximately $1 {{PLURAL:$1|revision|revisions}}:",
27882789 'confirmdeletetext' => 'You are about to delete a page along with all of its history.
27892790 Please confirm that you intend to do this, that you understand the consequences, and that you are doing this in accordance with [[{{MediaWiki:Policy-url}}|the policy]].',
27902791 'actioncomplete' => 'Action complete',
Index: trunk/extensions/LiquidThreads/LiquidThreads.php
@@ -118,7 +118,7 @@
119119 $wgHooks['ArticleDeleteComplete'][] = 'LqtDeletionController::onArticleDeleteComplete';
120120 $wgHooks['ArticleRevisionUndeleted'][] = 'LqtDeletionController::onArticleRevisionUndeleted';
121121 $wgHooks['ArticleUndelete'][] = 'LqtDeletionController::onArticleUndelete';
122 -$wgHooks['ArticleConfirmDelete'][] = 'LqtDeletionController::onArticleConfirmDelete';
 122+$wgHooks['ActionBeforeFormDisplay'][] = 'LqtDeletionController::onActionBeforeFormDisplay';
123123 $wgHooks['ArticleDelete'][] = 'LqtDeletionController::onArticleDelete';
124124
125125 // Moving
Index: trunk/extensions/LiquidThreads/classes/DeletionController.php
@@ -94,22 +94,27 @@
9595 return true;
9696 }
9797
98 - static function onArticleConfirmDelete( $article, $out, &$reason ) {
99 - if ( $article->getTitle()->getNamespace() != NS_LQT_THREAD ) {
 98+ static function onActionBeforeFormDisplay( $action, HTMLForm &$form, $page ) {
 99+ if( $action != 'delete' ){
100100 return true;
101101 }
102102
103 - $thread = Threads::withRoot( $article );
 103+ if ( $page->getTitle()->getNamespace() != NS_LQT_THREAD ) {
 104+ return true;
 105+ }
104106
 107+ $thread = Threads::withRoot( $page );
 108+
105109 if ( !$thread ) {
106110 return true;
107111 }
108112
109113 if ( $thread->isTopmostThread() && count( $thread->replies() ) ) {
110 - $out->wrapWikiMsg(
111 - '<strong>$1</strong>',
112 - 'lqt-delete-parent-warning'
113 - );
 114+ $form->addHeaderText( Html::rawElement(
 115+ 'strong',
 116+ array(),
 117+ wfMessage( 'lqt-delete-parent-warning' )->parse()
 118+ ) );
114119 }
115120
116121 return true;
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/MultilingualLiquidThreads/LiquidThreads/LiquidThreads.php
@@ -70,7 +70,7 @@
7171 $wgHooks['ArticleDeleteComplete'][] = 'LqtDeletionController::onArticleDeleteComplete';
7272 $wgHooks['ArticleRevisionUndeleted'][] = 'LqtDeletionController::onArticleRevisionUndeleted';
7373 $wgHooks['ArticleUndelete'][] = 'LqtDeletionController::onArticleUndelete';
74 -$wgHooks['ArticleConfirmDelete'][] = 'LqtDeletionController::onArticleConfirmDelete';
 74+$wgHooks['ActionBeforeFormDisplay'][] = 'LqtDeletionController::onActionBeforeFormDisplay';
7575 $wgHooks['ArticleDelete'][] = 'LqtDeletionController::onArticleDelete';
7676
7777 // Moving
Index: trunk/extensions/MultilingualLiquidThreads/LiquidThreads/classes/DeletionController.php
@@ -93,20 +93,25 @@
9494 return true;
9595 }
9696
97 - static function onArticleConfirmDelete( $article, $out, &$reason ) {
98 - if ( $article->getTitle()->getNamespace() != NS_LQT_THREAD ) {
 97+ static function onActionBeforeFormDisplay( $action, HTMLForm &$form, $page ) {
 98+ if( $action != 'delete' ){
9999 return true;
100100 }
101101
102 - $thread = Threads::withRoot( $article );
 102+ if ( $page->getTitle()->getNamespace() != NS_LQT_THREAD ) {
 103+ return true;
 104+ }
103105
 106+ $thread = Threads::withRoot( $page );
 107+
104108 if ( !$thread ) return true;
105109
106110 if ( $thread->isTopmostThread() && count( $thread->replies() ) ) {
107 - $out->wrapWikiMsg(
108 - '<strong>$1</strong>',
109 - 'lqt-delete-parent-warning'
110 - );
 111+ $form->addHeaderText( Html::rawElement(
 112+ 'strong',
 113+ array(),
 114+ wfMessage( 'lqt-delete-parent-warning' )->parse()
 115+ ) );
111116 }
112117
113118 return true;
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/PureWikiDeletion/PureWikiDeletion.php
@@ -104,8 +104,7 @@
105105 return false;
106106 }
107107 if ( $summary == wfMsgForContent( 'autosumm-blank' ) ) {
108 - $hasHistory = false;
109 - $summary = $article->generateReason( $hasHistory );
 108+ $summary = DeleteAction::getAutoReason( $article );
110109 }
111110 } else {
112111 $dbr = wfGetDB( DB_SLAVE );
Index: trunk/extensions/PureWikiDeletion/PureWikiDeletion.hooks.php
@@ -55,8 +55,7 @@
5656 $blankRevId = $revision->getId();
5757 if ( $text == "" ) {
5858 if ( $summary == wfMsgForContent( 'autosumm-blank' ) ) {
59 - $hasHistory = false;
60 - $summary = $article->generateReason( $hasHistory );
 59+ $summary = DeleteAction::getAutoReason( $article );
6160 }
6261 $dbw = wfGetDB( DB_MASTER );
6362 $blank_row = array(
Index: trunk/extensions/MetavidWiki/includes/articlepages/MV_DataPage.php
@@ -61,18 +61,18 @@
6262 }
6363
6464 if ( $confirm ) {
65 - $this->doDelete( $reason );
 65+ $this->doDeleteArticle( $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 }
7373
7474 // Generate deletion reason
7575 $hasHistory = false;
76 - $reason = $this->generateReason( $hasHistory );
 76+ $reason = DeleteAction::getAutoReason( $this );
7777
7878 // If the page has a history, insert a warning
7979 if ( $hasHistory && !$confirm ) {
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 {

Follow-up revisions

RevisionCommit summaryAuthorDate
r86009Revert r86001: Brion says it's too scary :D will recommit in pieceshappy-melon23:36, 13 April 2011
r86041r86001, now with less scariness :P I took out the delete action and did purg...happy-melon10:38, 14 April 2011
r86198Fix missing abstract methods, logging against r86001reedy11:25, 16 April 2011

Comments

#Comment by Siebrand (talk | contribs)   23:24, 13 April 2011

Please fix the out-of-context reuse of the message key 'history' in 'historywarning'. That needs its own key (suggestion: 'historywarning-history'). But why not just use "[$2 history]"? This would make L10n a lot easier and clear. Patchwork messages are bad. See Localization#Avoid_patchwork_messages.

Status & tagging log