r20777 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r20776‎ | r20777 | r20778 >
Date:16:17, 28 March 2007
Author:jhb
Status:old
Tags:
Comment:
moving the files into a seperate folder
Modified paths:
  • /branches/jhb/phase3/extensions/FlaggedRevs (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs.php (deleted) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs.sql (deleted) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevs.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevs.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevs.sql (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevs.sql (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.body.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.body.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.i18n.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.i18n.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/Makevalidate.class.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/Makevalidate.class.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/Makevalidate.i18n.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/Makevalidate.i18n.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/SpecialMakevalidate.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/SpecialMakevalidate.php (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/specs.txt (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevs/specs.txt (added) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevsPage.body.php (deleted) (history)
  • /branches/jhb/phase3/extensions/FlaggedRevsPage.i18n.php (deleted) (history)
  • /branches/jhb/phase3/extensions/Makevalidate.class.php (deleted) (history)
  • /branches/jhb/phase3/extensions/Makevalidate.i18n.php (deleted) (history)
  • /branches/jhb/phase3/extensions/SpecialMakevalidate.php (deleted) (history)
  • /branches/jhb/phase3/extensions/specs.txt (deleted) (history)

Diff [purge]

Index: branches/jhb/phase3/extensions/Makevalidate.class.php
@@ -1,172 +0,0 @@
2 -<?php
3 -
4 -global $IP;
5 -require_once( "$IP/includes/LogPage.php" );
6 -require_once( "$IP/includes/SpecialLog.php" );
7 -
8 -class MakeValidate extends SpecialPage {
9 -
10 - var $target = '';
11 -
12 - /**
13 - * Constructor
14 - */
15 - function MakeValidate() {
16 - SpecialPage::SpecialPage( 'Makevalidate', 'makevalidate' );
17 - }
18 -
19 - /**
20 - * Main execution function
21 - * @param $par Parameters passed to the page
22 - */
23 - function execute( $par ) {
24 - global $wgRequest, $wgOut, $wgmakevalidatePrivileged, $wgUser;
25 -
26 - if( !$wgUser->isAllowed( 'makevalidate' ) ) {
27 - $wgOut->permissionRequired( 'makevalidate' );
28 - return;
29 - }
30 -
31 - $this->setHeaders();
32 -
33 - $this->target = $par
34 - ? $par
35 - : $wgRequest->getText( 'username', '' );
36 -
37 - $wgOut->addWikiText( wfMsgNoTrans( 'makevalidate-header' ) );
38 - $wgOut->addHtml( $this->makeSearchForm() );
39 -
40 - if( $this->target != '' ) {
41 - $wgOut->addHtml( wfElement( 'p', NULL, NULL ) );
42 - $user = User::newFromName( $this->target );
43 - if( is_object( $user ) && !is_null( $user ) ) {
44 - global $wgVersion;
45 - if( version_compare( $wgVersion, '1.9alpha' ) < 0 ) {
46 - $user->loadFromDatabase();
47 - } else {
48 - $user->load();
49 - }
50 - # Valid username, check existence
51 - if( $user->getID() ) {
52 - if( $wgRequest->getCheck( 'dosearch' ) || !$wgRequest->wasPosted() || !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ), 'makevalidate' ) ) {
53 - # Exists, check reviewerness
54 - if( in_array( 'reviewer', $user->mGroups ) ) {
55 - # Has a reviewer flag
56 - $wgOut->addWikiText( wfMsg( 'makevalidate-isvalidator', $user->getName() ) );
57 - $wgOut->addHtml( $this->makeGrantForm( MW_MAKEVALIDATE_REVOKE ) );
58 - } else {
59 - # Not a reviewer; show the grant form
60 - $wgOut->addHtml( $this->makeGrantForm( MW_MAKEVALIDATE_GRANT ) );
61 - }
62 - } elseif( $wgRequest->getCheck( 'grant' ) ) {
63 - # Grant the flag
64 - $user->addGroup( 'reviewer' );
65 - $this->addLogItem( 'grant', $user, trim( $wgRequest->getText( 'comment' ) ) );
66 - $wgOut->addWikiText( wfMsg( 'makevalidate-granted', $user->getName() ) );
67 - } elseif( $wgRequest->getCheck( 'revoke' ) ) {
68 - # Revoke the flag
69 - $user->removeGroup( 'reviewer' );
70 - $this->addLogItem( 'revoke', $user, trim( $wgRequest->getText( 'comment' ) ) );
71 - $wgOut->addWikiText( wfMsg( 'makevalidate-revoked', $user->getName() ) );
72 - }
73 - # Show log entries
74 - $this->showLogEntries( $user );
75 - } else {
76 - # Doesn't exist
77 - $wgOut->addWikiText( wfMsg( 'nosuchusershort', htmlspecialchars( $this->target ) ) );
78 - }
79 - } else {
80 - # Invalid username
81 - $wgOut->addWikiText( wfMsg( 'noname' ) );
82 - }
83 - }
84 -
85 - }
86 -
87 - /**
88 - * Produce a form to allow for entering a username
89 - * @return string
90 - */
91 - function makeSearchForm() {
92 - $thisTitle = Title::makeTitle( NS_SPECIAL, $this->getName() );
93 - $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) );
94 - $form .= wfElement( 'label', array( 'for' => 'username' ), wfMsg( 'makevalidate-username' ) ) . ' ';
95 - $form .= wfElement( 'input', array( 'type' => 'text', 'name' => 'username', 'id' => 'username', 'value' => $this->target ) ) . ' ';
96 - $form .= wfElement( 'input', array( 'type' => 'submit', 'name' => 'dosearch', 'value' => wfMsg( 'makevalidate-search' ) ) );
97 - $form .= wfCloseElement( 'form' );
98 - return $form;
99 - }
100 -
101 - /**
102 - * Produce a form to allow granting or revocation of the flag
103 - * @param $type Either MW_makevalidate_GRANT or MW_makevalidate_REVOKE
104 - * where the trailing name refers to what's enabled
105 - * @return string
106 - */
107 - function makeGrantForm( $type ) {
108 - global $wgUser;
109 - $thisTitle = Title::makeTitle( NS_SPECIAL, $this->getName() );
110 - if( $type == MW_MAKEVALIDATE_GRANT ) {
111 - $grant = true;
112 - $revoke = false;
113 - } else {
114 - $grant = false;
115 - $revoke = true;
116 - }
117 -
118 - # Start the table
119 - $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) );
120 - $form .= wfOpenElement( 'table' ) . wfOpenElement( 'tr' );
121 - # Grant/revoke buttons
122 - $form .= wfElement( 'td', array( 'align' => 'right' ), wfMsg( 'makevalidate-change' ) );
123 - $form .= wfOpenElement( 'td' );
124 - foreach( array( 'grant', 'revoke' ) as $button ) {
125 - $attribs = array( 'type' => 'submit', 'name' => $button, 'value' => wfMsg( 'makevalidate-' . $button ) );
126 - if( !$$button )
127 - $attribs['disabled'] = 'disabled';
128 - $form .= wfElement( 'input', $attribs );
129 - }
130 - $form .= wfCloseElement( 'td' ) . wfCloseElement( 'tr' );
131 - # Comment field
132 - $form .= wfOpenElement( 'td', array( 'align' => 'right' ) );
133 - $form .= wfElement( 'label', array( 'for' => 'comment' ), wfMsg( 'makevalidate-comment' ) );
134 - $form .= wfOpenElement( 'td' );
135 - $form .= wfElement( 'input', array( 'type' => 'text', 'name' => 'comment', 'id' => 'comment', 'size' => 45 ) );
136 - $form .= wfCloseElement( 'td' ) . wfCloseElement( 'tr' );
137 - # End table
138 - $form .= wfCloseElement( 'table' );
139 - # Username
140 - $form .= wfElement( 'input', array( 'type' => 'hidden', 'name' => 'username', 'value' => $this->target ) );
141 - # Edit token
142 - $form .= wfElement( 'input', array( 'type' => 'hidden', 'name' => 'token', 'value' => $wgUser->editToken( 'makevalidate' ) ) );
143 - $form .= wfCloseElement( 'form' );
144 - return $form;
145 - }
146 -
147 - /**
148 - * Add logging entries for the specified action
149 - * @param $type Either grant or revoke
150 - * @param $target User receiving the action
151 - * @param $comment Comment for the log item
152 - */
153 - function addLogItem( $type, &$target, $comment = '' ) {
154 - $log = new LogPage( 'validate' );
155 - $targetPage = $target->getUserPage();
156 - $log->addEntry( $type, $targetPage, $comment );
157 - }
158 -
159 - /**
160 - * Show the bot status log entries for the specified user
161 - * @param $user User to show the log for
162 - */
163 - function showLogEntries( &$user ) {
164 - global $wgOut;
165 - $title = $user->getUserPage();
166 - $wgOut->addHtml( wfElement( 'h2', NULL, htmlspecialchars( LogPage::logName( 'validate' ) ) ) );
167 - $logViewer = new LogViewer( new LogReader( new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'validate' ) ) ) );
168 - $logViewer->showList( $wgOut );
169 - }
170 -
171 -}
172 -
173 -?>
Index: branches/jhb/phase3/extensions/FlaggedRevs.sql
@@ -1,54 +0,0 @@
2 -
3 -
4 -CREATE TABLE /*$wgDBprefix*/flaggedrevs (
5 - fr_id int(10) NOT NULL auto_increment,
6 - fr_page_id int(10) NOT NULL,
7 - fr_rev_id int(10) NOT NULL,
8 - fr_acc int(2) NOT NULL,
9 - fr_dep int(2) NOT NULL,
10 - fr_sty int(2) NOT NULL,
11 - fr_user int(5) NOT NULL,
12 - fr_timestamp char(14) NOT NULL,
13 - fr_comment mediumblob default NULL,
14 -
15 - PRIMARY KEY fr_rev_id (fr_rev_id),
16 - UNIQUE KEY (fr_id),
17 - INDEX fr_page_rev (fr_page_id,fr_rev_id),
18 - INDEX fr_acc_dep_sty (fr_acc,fr_dep,fr_sty)
19 -) TYPE=InnoDB;
20 -
21 -CREATE TABLE /*$wgDBprefix*/flaggedtext (
22 - ft_id int(10) NOT NULL auto_increment,
23 - ft_rev_id int(10) NOT NULL,
24 - ft_text mediumblob NOT NULL default '',
25 -
26 - PRIMARY KEY ft_id (ft_id),
27 - UNIQUE KEY ft_rev_id (ft_rev_id)
28 -) TYPE=InnoDB;
29 -
30 -CREATE TABLE /*$wgDBprefix*/flaggedimages (
31 - fi_id int(10) NOT NULL auto_increment,
32 - fi_name varchar(255) NOT NULL,
33 - fi_rev_id int(10) NOT NULL,
34 -
35 - PRIMARY KEY (fi_name,fi_rev_id),
36 - UNIQUE KEY (fi_id),
37 - INDEX fi_name (fi_name)
38 -) TYPE=InnoDB;
39 -
40 -CREATE TABLE /*$wgDBprefix*/flaggedcache (
41 - fc_key char(255) binary NOT NULL default '',
42 - fc_cache mediumblob NOT NULL default '',
43 - fc_date char(14) NOT NULL,
44 -
45 - PRIMARY KEY fc_key (fc_key)
46 -) TYPE=InnoDB;
\ No newline at end of file
Index: branches/jhb/phase3/extensions/FlaggedRevsPage.body.php
@@ -1,302 +0,0 @@
2 -<?php
3 -
4 -#(c) Joerg Baach, Aaron Schulz, 2007 GPL
5 -
6 -global $IP;
7 -require_once( "$IP/includes/LogPage.php" );
8 -require_once( "$IP/includes/SpecialLog.php" );
9 -
10 -class Revisionreview extends SpecialPage
11 -{
12 -
13 - function Revisionreview() {
14 - SpecialPage::SpecialPage('Revisionreview', 'review');
15 - }
16 -
17 - function execute( $par ) {
18 - global $wgRequest, $wgUser, $wgOut, $wgFlaggedRevComments;
19 -
20 - if( !$wgUser->isAllowed( 'review' ) ) {
21 - $wgOut->permissionRequired( 'review' );
22 - return;
23 - }
24 -
25 - $this->setHeaders();
26 - // Our target page
27 - $this->target = $wgRequest->getText( 'target' );
28 - // Revision ID
29 - $this->oldid = $wgRequest->getIntOrNull( 'oldid' );
30 - // Log comment
31 - $this->comment = $wgRequest->getText( 'wpReason' );
32 - // Additional notes
33 - $this->notes = ($wgFlaggedRevComments) ? $wgRequest->getText('wpNotes') : '';
34 - // Get our accuracy/quality array
35 - $this->dimensions = array();
36 - $this->dimensions['acc'] = $wgRequest->getIntOrNull('accuracy');
37 - $this->dimensions['depth'] = $wgRequest->getIntOrNull('depth');
38 - $this->dimensions['style'] = $wgRequest->getIntOrNull('style');
39 - // Must be a valid page
40 - $this->page = Title::newFromUrl( $this->target );
41 - if( is_null($this->page) || is_null($this->oldid) || !$this->page->isContentPage() ) {
42 - $wgOut->showErrorPage( $this->page, 'notargettitle', 'notargettext' );
43 - return;
44 - }
45 - if( $wgRequest->wasPosted() ) {
46 - $this->submit( $wgRequest );
47 - } else {
48 - $this->showRevision( $wgRequest );
49 - }
50 - }
51 -
52 - /**
53 - * @param webrequest $request
54 - */
55 - function showRevision( $request ) {
56 - global $wgOut, $wgUser, $wgTitle, $wgFlaggedRevComments;
57 -
58 - $wgOut->addWikiText( wfMsgExt( 'revreview-selected', array('parsemag'), $this->page->getPrefixedText() ) );
59 -
60 - $this->skin = $wgUser->getSkin();
61 - $rev = Revision::newFromTitle( $this->page, $this->oldid );
62 - // Check if rev exists
63 - if( !isset( $rev ) ) {
64 - $wgOut->showErrorPage( 'internalerror', 'notargettitle', 'notargettext' );
65 - return;
66 - }
67 - // Do not mess with deleted revisions
68 - if ( $rev->mDeleted ) {
69 - $wgOut->showErrorPage( 'internalerror', 'badarticleerror' );
70 - return;
71 - }
72 - $wgOut->addHtml( "<ul>" );
73 - $wgOut->addHtml( $this->historyLine( $rev ) );
74 - $wgOut->addHtml( "</ul>" );
75 -
76 - $wgOut->addWikiText( wfMsgHtml( 'revreview-text' ) );
77 -
78 - $this->accRadios = array(
79 - array( 'revreview-acc-0', 'wpAcc1', 0 ),
80 - array( 'revreview-acc-1', 'wpAcc2', 1 ),
81 - array( 'revreview-acc-2', 'wpAcc3', 2 ),
82 - array( 'revreview-acc-3', 'wpAcc4', 3 ) );
83 - $this->depthRadios = array(
84 - array( 'revreview-depth-0', 'wpDepth1', 0 ),
85 - array( 'revreview-depth-1', 'wpDepth2', 1 ),
86 - array( 'revreview-depth-2', 'wpDepth3', 2 ),
87 - array( 'revreview-depth-3', 'wpDepth4', 3 ) );
88 - $this->styleRadios = array(
89 - array( 'revreview-style-0', 'wpStyle1', 0 ),
90 - array( 'revreview-style-1', 'wpStyle2', 1 ),
91 - array( 'revreview-style-2', 'wpStyle3', 2 ),
92 - array( 'revreview-style-3', 'wpStyle4', 3 ) );
93 - $items = array(
94 - wfInputLabel( wfMsgHtml( 'revreview-log' ), 'wpReason', 'wpReason', 60 ),
95 - wfSubmitButton( wfMsgHtml( 'revreview-submit' ) ) );
96 - $hidden = array(
97 - wfHidden( 'wpEditToken', $wgUser->editToken() ),
98 - wfHidden( 'target', $this->page->getPrefixedText() ),
99 - wfHidden( 'oldid', $this->oldid ) );
100 -
101 - $action = $wgTitle->escapeLocalUrl( 'action=submit' );
102 - $form = "<form name='revisionreview' action='$action' method='post'>";
103 - $form .= '<fieldset><legend>' . wfMsgHtml( 'revreview-legend' ) . '</legend><table><tr>';
104 - $form .= '<td><strong>' . wfMsgHtml( 'revreview-acc' ) . '</strong></td>';
105 - $form .= '<td width=\'25\'></td><td><strong>' . wfMsgHtml( 'revreview-depth' ) . '</strong></td>';
106 - $form .= '<td width=\'25\'></td><td><strong>' . wfMsgHtml( 'revreview-style' ) . '</strong></td>';
107 - $form .= '</tr><tr><td>';
108 - foreach( $this->accRadios as $item ) {
109 - list( $message, $name, $field ) = $item;
110 - $form .= "<div>" .
111 - Xml::radio( 'accuracy', $field, ($field==$this->dimensions['acc']) ) . ' ' . wfMsgHtml($message) .
112 - "</div>\n";
113 - }
114 - $form .= '<td width=\'25\'></td></td><td>';
115 - foreach( $this->depthRadios as $item ) {
116 - list( $message, $name, $field ) = $item;
117 - $form .= "<div>" .
118 - Xml::radio( 'depth', $field, ($field==$this->dimensions['depth']) ) . ' ' . wfMsgHtml($message) .
119 - "</div>\n";
120 - }
121 - $form .= '<td width=\'25\'></td></td><td>';
122 - foreach( $this->styleRadios as $item ) {
123 - list( $message, $name, $field ) = $item;
124 - $form .= "<div>" .
125 - Xml::radio( 'style', $field, ($field==$this->dimensions['style']) ) . ' ' . wfMsgHtml($message) .
126 - "</div>\n";
127 - }
128 - $form .= '</td></tr></table></fieldset>';
129 -
130 - list($images,$thumbs) = FlaggedRevs::findLocalImages( FlaggedRevs::expandText( $rev->getText() ) );
131 - if ( $images ) {
132 - $form .= wfMsg('revreview-images') . "\n";
133 - $form .= "<ul>";
134 - $imglist = '';
135 - foreach ( $images as $image ) {
136 - $imglist .= "<li>" . $this->skin->makeKnownLink( $image ) . "</li>\n";
137 - }
138 - $form .= $imglist;
139 - $form .= "</ul>\n";
140 - }
141 - if ( $wgFlaggedRevComments ) {
142 - $form .= "<fieldset><legend>" . wfMsgHtml( 'revreview-notes' ) . "</legend>" .
143 - "<textarea tabindex='1' name='wpNotes' id='wpNotes' rows='3' cols='80' style='width:100%'></textarea>" .
144 - "</fieldset>";
145 - }
146 -
147 - foreach( $items as $item ) {
148 - $form .= '<p>' . $item . '</p>';
149 - }
150 - foreach( $hidden as $item ) {
151 - $form .= $item;
152 - }
153 -
154 - $form .= '</form>';
155 - $wgOut->addHtml( $form );
156 - }
157 -
158 - /**
159 - * @param Revision $rev
160 - * @returns string
161 - */
162 - function historyLine( $rev ) {
163 - global $wgContLang;
164 - $date = $wgContLang->timeanddate( $rev->getTimestamp() );
165 -
166 - $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'),
167 - '&diff=' . $rev->getId() . '&oldid=prev' ) . ')';
168 -
169 - $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() );
170 -
171 - return
172 - "<li> $difflink $revlink " . $this->skin->revUserLink( $rev ) . " " . $this->skin->revComment( $rev ) . "</li>";
173 - }
174 -
175 - function submit( $request ) {
176 - global $wgOut;
177 -
178 - $rev = Revision::newFromTitle( $this->page, $this->oldid );
179 - // Do not mess with deleted revisions
180 - if ( is_null($rev) || $rev->mDeleted ) {
181 - $wgOut->showErrorPage( 'internalerror', 'badarticleerror' );
182 - return;
183 - }
184 - $approved = false;
185 - # If all values are set to zero, this has been unnapproved
186 - foreach( $this->dimensions as $quality => $value ) {
187 - if( $value ) $approved = true;
188 - }
189 - $success = ( $approved ) ? $this->approveRevision( $rev ) : $this->unapproveRevision( $rev );
190 - // Return to our page
191 - if ( $success ) {
192 - $wgOut->redirect( $this->page->escapeLocalUrl() );
193 - } else {
194 - $wgOut->showErrorPage( 'internalerror', 'badarticleerror' );
195 - }
196 - }
197 -
198 - /**
199 - * @param Revision $rev
200 - * Adds or updates the flagged revision table for this page/id set
201 - */
202 - function approveRevision( $rev=NULL ) {
203 - global $wgUser;
204 - if( is_null($rev) ) return false;
205 -
206 - wfProfileIn( __METHOD__ );
207 -
208 - $db = wfGetDB( DB_MASTER );
209 - $user = $wgUser->getId();
210 - $timestamp = wfTimestampNow();
211 -
212 - $cache_text = FlaggedRevs::expandText( $rev->getText() );
213 - // Add or update entry for this revision
214 - $set = array(
215 - 'fr_page_id' => $rev->getPage(),
216 - 'fr_rev_id' => $rev->getId(),
217 - 'fr_acc' => $this->dimensions['acc'],
218 - 'fr_dep' => $this->dimensions['depth'],
219 - 'fr_sty' => $this->dimensions['style'],
220 - 'fr_user' => $user,
221 - 'fr_timestamp' => $timestamp,
222 - 'fr_comment'=> $this->notes
223 - );
224 - $set2 = array('ft_rev_id' => $rev->getId(), 'ft_text' => $cache_text);
225 - // Update flagrevisions table
226 - $db->replace( 'flaggedrevs', array( array('fr_page_id','fr_rev_id') ), $set, __METHOD__ );
227 - // Store/update the text
228 - $db->replace( 'flaggedtext', array('ft_rev_id'), $set2, __METHOD__ );
229 - // Update the article review log
230 - $this->updateLog( $this->page, $this->dimensions, $this->comment, $this->oldid, true );
231 - // Clone images to stable dir
232 - list($images,$thumbs) = FlaggedRevs::findLocalImages( $cache_text );
233 - $copies = FlaggedRevs::makeStableImages( $images );
234 - FlaggedRevs::purgeStableThumbnails( $thumbs );
235 - // Update stable image table
236 - FlaggedRevs::insertStableImages( $rev->getId(), $copies );
237 - // Clear cache...
238 - $this->page->invalidateCache();
239 - return true;
240 - }
241 -
242 - /**
243 - * @param Revision $rev
244 - * Removes flagged revision data for this page/id set
245 - */
246 - function unapproveRevision( $rev=NULL ) {
247 - global $wgUser;
248 -
249 - if( is_null($rev) ) return false;
250 - $db = wfGetDB( DB_MASTER );
251 - $user = $wgUser->getId();
252 - $timestamp = wfTimestampNow();
253 - // get the flagged revision to access its cache text
254 - $frev = FlaggedRevs::getFlaggedRevision( $rev->getId() );
255 - if( !$frev ) {
256 - // Quitly ignore this...
257 - return true;
258 - }
259 - $db->delete( 'flaggedrevs', array( 'fr_rev_id' => $rev->getId ) );
260 - // Update the article review log
261 - $this->updateLog( $this->page, $this->dimensions, $this->comment, $this->oldid, false );
262 -
263 - $cache_text = FlaggedRevs::getFlaggedRevText( $rev->getId ) ;
264 - // Delete stable images if needed
265 - list($images,$thumbs) = FlaggedRevs::findLocalImages( $cache_text );
266 - $copies = FlaggedRevs::deleteStableImages( $images );
267 - // Stable versions must remake this thumbnail
268 - FlaggedRevs::purgeStableThumbnails( $thumbs );
269 - // Update stable image table
270 - FlaggedRevs::removeStableImages( $rev->getId(), $copies );
271 - // Clear cache...
272 - $this->page->invalidateCache();
273 - return true;
274 - }
275 -
276 - /**
277 - * Record a log entry on the action
278 - * @param Title $title
279 - * @param array $dimensions
280 - * @param string $comment
281 - * @param int $revid
282 - * @param bool $approve
283 - */
284 - function updateLog( $title, $dimensions, $comment, $oldid, $approve ) {
285 - $log = new LogPage( 'review' );
286 - // ID, accuracy, depth, style
287 - $ratings = array();
288 - foreach( $dimensions as $quality => $level ) {
289 - $ratings[] = wfMsg( "revreview-$quality" ) . ": " . wfMsg("revreview-$quality-$level");
290 - }
291 - $rating = ($approve) ? ' [' . implode(', ',$ratings). ']' : '';
292 - // Append comment with action
293 - $action = wfMsgExt('review-logaction', array('parsemag'), $oldid );
294 - $comment = ($comment) ? "$action: $comment$rating" : "$action $rating";
295 -
296 - if ( $approve ) {
297 - $log->addEntry( 'approve', $title, $comment );
298 - } else {
299 - $log->addEntry( 'unapprove', $title, $comment );
300 - }
301 - }
302 -}
303 -?>
Index: branches/jhb/phase3/extensions/FlaggedRevsPage.i18n.php
@@ -1,66 +0,0 @@
2 -<?php
3 -$RevisionreviewMessages = array();
4 -
5 -// English (Aaron Schulz)
6 -$RevisionreviewMessages['en'] = array(
7 - 'reviewer' => 'Reviewer',
8 - 'group-reviewer' => 'Reviewers',
9 - 'group-reviewer' => 'Reviewer',
10 - 'grouppage-reviewer' => '{{ns:project}}:Reviewer',
11 -
12 - 'revreview-noflagged' => 'There are no reviewed revisions of this page, so it has \'\'\'not\'\'\' been
13 - [[Help:Article validation|checked]] for quality.',
14 - 'revreview-isnewest' => 'This is the latest [[Help:Article validation|reviewed]] revision of this page (with
15 - updated images and templates) [{{fullurl:Special:Log/review|page={{FULLPAGENAMEE}}}} approved] on <i>$1</i>.',
16 - 'revreview-newest' => 'The [{{fullurl:{{FULLPAGENAMEE}}|oldid=$1}} latest reviewed revision]
17 - ([{{fullurl:{{FULLPAGENAMEE}}|oldid=$2&diff=$3}} compare]) was [{{fullurl:Special:Log/review|page={{FULLPAGENAMEE}}}} approved]
18 - on <i>$4</i>, rated as:',
19 - 'revreview-replaced' => 'This is the latest [[Help:Article validation|reviewed]] revision of this page,
20 - [{{fullurl:Special:Log/review|page={{FULLPAGENAMEE}}}} approved] on <i>$4</i>. The [{{fullurl:{{FULLPAGENAMEE}}|oldid=$2}} current revision]
21 - is editable and may be more up to date. There {{plural:$3|is $3 revision|are $3 revisions}}
22 - ([{{fullurl:{{FULLPAGENAMEE}}|oldid=$1&diff=$2}} changes]) awaiting review.',
23 -
24 - 'revisionreview' => 'Review revisions',
25 -
26 - 'flaggedrevs' => 'Flagged Revisions',
27 - 'review-logpage' => 'Article review log',
28 - 'review-logpagetext' => 'This is a log of changes to revisions\' [[Help:Article validation|approval]] status
29 - for content pages.',
30 - 'review-logentrygrant' => 'approved [[$1]]',
31 - 'review-logentryrevoke' => 'unapproved [[$1]]',
32 - 'review-logaction' => 'reviewed revision $1',
33 -
34 - 'revreview-selected' => "Selected revision of '''$1:'''",
35 - 'revreview-text' => "Approved revisions are set as the default revision shown upon page view rather
36 - than the top revision. The content of this approved revision will remain constant regardless of any transcluded
37 - pages or internal images. Users on this wiki will still be able to access
38 - unreviewed content through the page history.",
39 - 'revreview-images' => 'Internal images on this page will be copied to the stable image directory, updating
40 - existing versions, and stored there until no reviewed revisions use them. The following images are transcluded onto this page:',
41 -
42 - 'revreview-hist' => '[reviewed]',
43 -
44 - 'revreview-note' => '[[User:$1]] made the following notes [[Help:Article validation|reviewing]] this revision:',
45 -
46 - 'revreview-flag' => 'Review this revision (#$1):',
47 - 'revreview-legend' => 'Rate revision content:',
48 - 'revreview-notes' => 'Observations or notes to display:',
49 - 'revreview-acc' => 'Accuracy',
50 - 'revreview-acc-0' => 'Unapproved',
51 - 'revreview-acc-1' => 'Not vandalized',
52 - 'revreview-acc-2' => 'Accurate',
53 - 'revreview-acc-3' => 'Well sourced',
54 - 'revreview-depth' => 'Depth',
55 - 'revreview-depth-0' => 'Unapproved',
56 - 'revreview-depth-1' => 'Stub',
57 - 'revreview-depth-2' => 'Moderate',
58 - 'revreview-depth-3' => 'Complete',
59 - 'revreview-style' => 'Readability',
60 - 'revreview-style-0' => 'Unapproved',
61 - 'revreview-style-1' => 'Acceptable',
62 - 'revreview-style-2' => 'Good',
63 - 'revreview-style-3' => 'Concise',
64 - 'revreview-log' => 'Log comment:',
65 - 'revreview-submit' => 'Apply to selected revision',
66 -);
67 -?>
\ No newline at end of file
Index: branches/jhb/phase3/extensions/Makevalidate.i18n.php
@@ -1,34 +0,0 @@
2 -<?php
3 -/**
4 - * Internationalisation file for makevalidate extension.
5 - *
6 - * @package MediaWiki
7 - * @subpackage Extensions
8 -*/
9 -
10 -function efMakeValidateMessages() {
11 - $messages = array();
12 -
13 -/* English (Aaron Schulz) */
14 -$messages['en'] = array(
15 - 'makevalidate' => 'Promote/demote reviewers',
16 - 'makevalidate-header' => '<strong>This form is used by bureaucrats to turn ordinary users into stable version validators.</strong><br> Type the name of the user in the box and press the button to make the user a validator.',
17 - 'makevalidate-username' => 'Name of the user:',
18 - 'makevalidate-search' => 'Go',
19 - 'makevalidate-isvalidator' => '[[User:$1|$1]] has reviewer status.',
20 - 'makevalidate-notvalidator' => '[[User:$1|$1]] does not have reviewer status.',
21 - 'makevalidate-change' => 'Change status:',
22 - 'makevalidate-grant' => 'Grant',
23 - 'makevalidate-revoke' => 'Revoke',
24 - 'makevalidate-comment' => 'Comment:',
25 - 'makevalidate-granted' => '[[User:$1|$1]] now has validator status.',
26 - 'makevalidate-revoked' => '[[User:$1|$1]] no longer has validator status.',
27 - 'makevalidate-logpage' => 'Reviewer status log',
28 - 'makevalidate-logpagetext' => 'This is a log of changes to users\' [[Help:Article validation|article validation]] status.',
29 - 'makevalidate-logentrygrant' => 'granted validator status to [[$1]]',
30 - 'makevalidate-logentryrevoke' => 'removed validator status from [[$1]]',
31 -);
32 -
33 -return $messages;
34 -}
35 -?>
Index: branches/jhb/phase3/extensions/specs.txt
@@ -1,94 +0,0 @@
2 -== The implementation ==
3 -
4 -The specs to implement are a variation of the proposal by Philipp and
5 -other German Wikipedians, found at:
6 -http://de.wikipedia.org/wiki/Wikipedia:Gesichtete_Versionen
7 -http://de.wikipedia.org/wiki/Wikipedia:Gepr%C3%BCfte_Versionen
8 -
9 -The changes are aimed mostly at making the feature more flexibly
10 -configurable, and streamlining some of the UI tasks.
11 -
12 -The feature is to be implemented as an extension, if at all possible.
13 -It must be GPL-licensed and will be committed to the Wikimedia
14 -Subversion server.
15 -
16 -The wiki will allow the configuration of "revision tags", which can
17 -then be associated with any revision of an article. Tags are organized
18 -in tag array, where each array represents a set of tags that describe
19 -a similar attribute, e.g., accuracy-related tags would be organized in
20 -one array, while those used for flagging materials for export might be
21 -organized in another. This is to ensure that "levels" of quality
22 -(unvandalized -> reviewed for accuracy -> featured article etc.) can
23 -be represented correctly.
24 -
25 -Each tag has certain attributes:
26 -- tag comments: Should the tag support a comment field which explains
27 -why the revision was tagged in a certain way?
28 -- permissions: Which user groups have permission to set the tag?
29 -- stylesheet and UI: not requiring explicit configuration, each tag
30 -should have a stylesheet class and MediaWiki: namespace message(s)
31 -associated with it, to allow for differences in visual and textual
32 -representation
33 -- implicit tagging: should this tag be implicitly set by any user
34 -within the associated group when editing? (this will be used for the
35 -"non-vandalized" tag)
36 -
37 -A generic change is required to allow for automatic membership in a
38 -user group when a user has more than X edits and is older than Y days.
39 -
40 -There should be a configuration option which associates a namespace with
41 -- a tag-array
42 -- a minimum level in that array.
43 -
44 -This option determines that, by default, the revision shown from this
45 -namespace will be the one from that array which is also >= the minimum
46 -level. So, for instance, one could determine that pages in the article
47 -namespace need to be at least checked for vandalism, or at least
48 -reviewed for accuracy.
49 -
50 -As a high priority wishlist item, these view options should also be
51 -applicable on a per-page basis. The existing protection UI should be
52 -expanded for this purpose. Implementation (and schedule) of this item
53 -will depend on overall implementation progress.
54 -
55 -Whichever view one ends up with, we expect that the top of the page
56 -indicates this, and allows you to switch & get diffs to other views.
57 -
58 -Because MediaWiki currently does not show templates and revisions in
59 -time synchronization, this behavior has to be fixed for old revisions.
60 -When one has expressed a preference for a revision with a specific
61 -tag/level (e.g. "unvandalized") AND this is the most recent revision,
62 -it will be shown together with the most recent equally tagged
63 -templates if they exist, otherwise with the most recent ones.
64 -
65 -Example: On a page like the Main Page, which includes many templates,
66 -one would typically want to have an unvandalized view of the entire
67 -construction. The Main Page itself rarely changes, but because the
68 -most recent revision is flagged as "unvandalized", it would be
69 -synchronized with templates for which this is also true. When viewing
70 -an older version, however, the templates would be shown as they were
71 -at that date&time.
72 -
73 -It is crucial that queries for revision lookup are highly performant;
74 -we should aim for a performance impact of less than 10% on uncached
75 -pageviews with a revision tag preference. Needless to say, the feature
76 -needs to interact correctly with Squid proxy caching.
77 -
78 -Tagging revisions should be possible from three places:
79 -- when editing (with the help of a collapsible diff)
80 -- when looking at diffs
81 -- when looking at revisions without any prior or later tags.
82 -
83 -A tag can only be set with reference to a diff to the last version
84 -that has the same tag. The Special:Recentchanges tool should be
85 -customizable to have such a diff link to the last version with a given
86 -tag. It is desirable that this view also includes an icon that
87 -indicates the state of the logged revision (derived from the tag
88 -stylesheets).
89 -
90 -Wishlist items for the future include things like mass vandalism
91 -review and advanced queries. I also have some ideas for phase II of
92 -the project, which I would love to see implemented before the tool is
93 -switched on on the English Wikipedia, but this will wait until phase I
94 -and any adjustments needed for its operations are successfully
95 -completed.
Index: branches/jhb/phase3/extensions/SpecialMakevalidate.php
@@ -1,65 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Special page to allow local bureaucrats to grant/revoke the reviewer flag
6 - * for a particular user
7 - *
8 - * @addtogroup Extensions
9 - * Tiny modifications by Aaron Schulz to MakeBot
10 - * MakeBot extension:
11 - ** @author Rob Church <robchur@gmail.com>
12 - ** @copyright © 2006 Rob Church
13 - ** @licence GNU General Public Licence 2.0 or later
14 - */
15 -
16 -if( defined( 'MEDIAWIKI' ) ) {
17 -
18 - define( 'MW_MAKEVALIDATE_GRANT', 1 );
19 - define( 'MW_MAKEVALIDATE_REVOKE', 2 );
20 -
21 - $wgExtensionFunctions[] = 'efMakevalidate';
22 - $wgAvailableRights[] = 'makevalidate';
23 - $wgExtensionCredits['specialpage'][] = array(
24 - 'name' => 'MakeBot',
25 - 'author' => 'Rob Church',
26 - 'url' => 'http://www.mediawiki.org/wiki/Extension:MakeBot',
27 - 'description' => 'Special page allows local bureaucrats to grant and revoke bot permissions',
28 - );
29 -
30 - /**
31 - * Determines who can use the extension; as a default, bureaucrats are permitted
32 - */
33 - $wgGroupPermissions['bureaucrat']['makevalidate'] = true;
34 -
35 - /**
36 - * Toggles whether or not a bot flag can be given to a user who is also a sysop or bureaucrat
37 - */
38 - $wgMakeBotPrivileged = false;
39 -
40 - /**
41 - * Register the special page
42 - */
43 - $wgAutoloadClasses['Makevalidate'] = dirname( __FILE__ ) . '/Makevalidate.class.php';
44 - $wgSpecialPages['Makevalidate'] = 'Makevalidate';
45 -
46 - /**
47 - * Populate the message cache and set up the auditing
48 - */
49 - function efMakeValidate() {
50 - global $wgMessageCache, $wgLogTypes, $wgLogNames, $wgLogHeaders, $wgLogActions;
51 - require_once( dirname( __FILE__ ) . '/Makevalidate.i18n.php' );
52 - foreach( efMakeValidateMessages() as $lang => $messages )
53 - $wgMessageCache->addMessages( $messages, $lang );
54 - $wgLogTypes[] = 'validate';
55 - $wgLogNames['validate'] = 'makevalidate-logpage';
56 - $wgLogHeaders['validate'] = 'makevalidate-logpagetext';
57 - $wgLogActions['validate/grant'] = 'makevalidate-logentrygrant';
58 - $wgLogActions['validate/revoke'] = 'makevalidate-logentryrevoke';
59 - }
60 -
61 -} else {
62 - echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
63 - exit( 1 );
64 -}
65 -
66 -?>
Index: branches/jhb/phase3/extensions/FlaggedRevs.php
@@ -1,766 +0,0 @@
2 -<?
3 -#(c) Joerg Baach, Aaron Schulz 2007 GPL
4 -/*
5 -Possible Hooks
6 -
7 -'BeforePageDisplay': Called just before outputting a page (all kinds of,
8 - articles, special, history, preview, diff, edit, ...)
9 - Can be used to set custom CSS/JS
10 -$out: OutputPage object
11 -
12 -'OutputPageBeforeHTML': a page has been processed by the parser and
13 -the resulting HTML is about to be displayed.
14 -$parserOutput: the parserOutput (object) that corresponds to the page
15 -$text: the text that will be displayed, in HTML (string)
16 -*/
17 -
18 -if ( !defined( 'MEDIAWIKI' ) ) {
19 - echo "FlaggedRevs extension\n";
20 - exit( 1 );
21 -}
22 -
23 -$wgExtensionFunctions[] = 'efLoadReviewMessages';
24 -
25 -# Internationilization
26 -function efLoadReviewMessages() {
27 - global $wgMessageCache, $RevisionreviewMessages;
28 - require( dirname( __FILE__ ) . '/FlaggedRevsPage.i18n.php' );
29 - foreach ( $RevisionreviewMessages as $lang => $langMessages ) {
30 - $wgMessageCache->addMessages( $langMessages, $lang );
31 - }
32 -}
33 -
34 -# Revision tagging can slow development...
35 -# For example, the main user base may become complacent,
36 -# treating flagged pages as "done",
37 -# or just be too damn lazy to always click "current".
38 -# We may just want non-user visitors to see reviewd pages by default.
39 -$wgFlaggedRevsAnonOnly = true;
40 -# Can users make comments that will show up below flagged revisions?
41 -$wgFlaggedRevComments = true;
42 -# How long to cache stable versions
43 -$wgFlaggedRevsExpire = 7 * 24 * 3600;
44 -
45 -$wgAvailableRights[] = 'review';
46 -# Define our reviewer class
47 -$wgGroupPermissions['reviewer']['rollback'] = true;
48 -$wgGroupPermissions['reviewer']['patrol'] = true;
49 -$wgGroupPermissions['reviewer']['review'] = true;
50 -
51 -# Add review log
52 -$wgLogTypes[] = 'review';
53 -$wgLogNames['review'] = 'review-logpage';
54 -$wgLogHeaders['review'] = 'review-logpagetext';
55 -$wgLogActions['review/approve'] = 'review-logentrygrant';
56 -$wgLogActions['review/unapprove'] = 'review-logentryrevoke';
57 -
58 -class FlaggedRevs {
59 - /* 50MB allows fixing those huge pages */
60 - const MAX_INCLUDE_SIZE = 50000000;
61 -
62 - function __construct() {
63 - $this->dimensions = array( 'acc' => array( 0=>'acc-0',
64 - 1=>'acc-1',
65 - 2=>'acc-2',
66 - 3=>'acc-3'),
67 - 'depth' => array( 0=>'depth-0',
68 - 1=>'depth-1',
69 - 2=>'depth-2',
70 - 3=>'depth-3'),
71 - 'style' => array( 0=>'style-0',
72 - 1=>'style-1',
73 - 2=>'style-2',
74 - 3=>'style-3') );
75 - }
76 -
77 - function pageOverride() {
78 - global $wgFlaggedRevsAnonOnly, $wgUser;
79 - return !( $wgFlaggedRevsAnonOnly && !$wgUser->isAnon() );
80 - }
81 -
82 - function getFlaggedRevision( $rev_id ) {
83 - $db = wfGetDB( DB_SLAVE );
84 - // select a row, this should be unique
85 - $result = $db->select( 'flaggedrevs', array('*'), array('fr_rev_id' => $rev_id) );
86 - if( $row = $db->fetchObject($result) ) {
87 - return $row;
88 - }
89 - return NULL;
90 - }
91 -
92 - function getFlaggedRevText( $rev_id ) {
93 - $db = wfGetDB( DB_SLAVE );
94 - // select a row, this should be unique
95 - $result = $db->select( 'flaggedtext', array('ft_text'), array('ft_rev_id' => $rev_id) );
96 - if( $row = $db->fetchObject($result) ) {
97 - return $row->ft_text;
98 - }
99 - return NULL;
100 - }
101 -
102 - /**
103 - * @param string $text
104 - * @returns string
105 - * All included pages are expanded out to keep this text frozen
106 - */
107 - function expandText( $text ) {
108 - global $wgParser, $wgTitle;
109 - // Do not treat this as paring an article on normal view
110 - // enter the title object as wgTitle
111 - $options = new ParserOptions;
112 - $options->setRemoveComments( true );
113 - $options->setMaxIncludeSize( self::MAX_INCLUDE_SIZE );
114 - $output = $wgParser->preprocess( $text, $wgTitle, $options );
115 - return $output;
116 - }
117 -
118 - function getFlagsForRevision( $rev_id ) {
119 - // Set default blank flags
120 - $flags = array( 'acc' => 0, 'depth' => 0, 'style' => 0 );
121 -
122 - $db = wfGetDB( DB_SLAVE );
123 - // select a row, this should be unique
124 - $result = $db->select( 'flaggedrevs', array('*'), array('fr_rev_id' => $rev_id) );
125 - if( $row = $db->fetchObject($result) ) {
126 - $flags = array( 'acc' => $row->fr_acc, 'depth' => $row->fr_dep, 'style' => $row->fr_sty );
127 - }
128 - return $flags;
129 - }
130 -
131 - function getLatestFlaggedRev( $page_id ) {
132 - wfProfileIn( __METHOD__ );
133 -
134 - $db = wfGetDB( DB_SLAVE );
135 - // Skip deleted revisions
136 - $result = $db->select(
137 - array('flaggedrevs', 'revision'),
138 - array('*'),
139 - array( 'fr_page_id' => $page_id, 'rev_id = fr_rev_id', 'rev_deleted = 0'),
140 - __METHOD__ ,
141 - array('ORDER BY' => 'fr_rev_id DESC') );
142 - // Sorted from highest to lowest, so just take the first one if any
143 - if ( $row = $db->fetchObject( $result ) ) {
144 - return $row;
145 - }
146 - return NULL;
147 - }
148 -
149 - function getReviewedRevs( $page_id ) {
150 - wfProfileIn( __METHOD__ );
151 -
152 - $db = wfGetDB( DB_SLAVE );
153 - $rows = array();
154 - // Skip deleted revisions
155 - $result = $db->select(
156 - array('flaggedrevs', 'revision'),
157 - array('*'),
158 - array( 'fr_page_id' => $page_id, 'rev_id = fr_rev_id', 'rev_deleted = 0'),
159 - __METHOD__ ,
160 - array('ORDER BY' => 'fr_rev_id DESC') );
161 - while ( $row = $db->fetchObject( $result ) ) {
162 - $rows[] = $row;
163 - }
164 - return $rows;
165 - }
166 -
167 - function getUnreviewedRevCount( $page_id, $from_rev ) {
168 - wfProfileIn( __METHOD__ );
169 -
170 - $db = wfGetDB( DB_SLAVE );
171 - $result = $db->select(
172 - array('revision'),
173 - array('rev_page'),
174 - array( 'rev_page' => $page_id, "rev_id > $from_rev" ),
175 - __METHOD__ ,
176 - array('ORDER BY' => 'rev_id DESC') );
177 - // Return count of revisions
178 - return $db->numRows($result);
179 - }
180 -
181 - function parseStableText( $title, $text, $id=NULL, $options, $returnHTML=true ) {
182 - global $wgUser, $wgParser, $wgUploadDirectory, $wgUseSharedUploads, $wgUploadPath;
183 - # hack...temporarily change image directories
184 - # There is no nice option to set this for each parse.
185 - # This lets the parser know where to look...
186 - $uploadPath = $wgUploadPath;
187 - $uploadDir = $wgUploadDirectory;
188 - $useSharedUploads = $wgUseSharedUploads;
189 - # Stable thumbnails need to have the right path
190 - $wgUploadPath = ($wgUploadPath) ? "{$uploadPath}/stable" : false;
191 - # Create <img> tags with the right url
192 - $wgUploadDirectory = "{$uploadDir}/stable";
193 - # Stable images are never stored at commons
194 - $wgUseSharedUploads = false;
195 -
196 - $options->setTidy(true);
197 - # Don't show section-edit links
198 - # They can be old and misleading
199 - $options->setEditSection(false);
200 - # Parse the new body, wikitext -> html
201 - $parserOut = $wgParser->parse( $text, $title, $options, true, true, $id );
202 - if ( !$returnHTML )
203 - return $parserOut;
204 -
205 - $HTMLout = $parserOut->getText();
206 - # hack...
207 - $HTMLout = $this->proportionalImgScaling($HTMLout);
208 -
209 - # Reset our image directories
210 - $wgUploadPath = $uploadPath;
211 - $wgUploadDirectory = $uploadDir;
212 - $wgUseSharedUploads = $useSharedUploads;
213 -
214 - return $HTMLout;
215 - }
216 -
217 - function proportionalImgScaling( $text ) {
218 - # goddamn hack...
219 - # Thumbnails are stored based on width, don't do any unscaled resizing
220 - # MW will add height/width based on the metadata in the db for the current image
221 - $text = preg_replace( '/(<img[^<>]+ )height="\d+" ([^<>]+>)/i','$1$2', $text);
222 - return $text;
223 - }
224 -
225 - function setPageContent( &$out ) {
226 - global $wgArticle, $wgRequest, $wgTitle, $wgOut, $action;
227 - // Only trigger on article view, not for protect/delete/hist
228 - // Talk pages cannot be validated
229 - if( !$wgArticle || !$wgTitle->isContentPage() || $action !='view' )
230 - return;
231 - // Find out revision id
232 - $revid = ( $wgArticle->mRevision ) ? $wgArticle->mRevision->mId : $wgArticle->getLatest();
233 - // Grab the ratings for this revision if any
234 - if( !$revid ) return;
235 - $visible_id = $revid;
236 -
237 - // Set new body html text as that of now
238 - $flaghtml = ''; $newbodytext = $out->mBodytext;
239 - // Check the newest stable version
240 - $top_frev = $this->getLatestFlaggedRev( $wgArticle->getId() );
241 - if( $wgRequest->getVal('diff') ) {
242 - // Do not clutter up diffs any further...
243 - } else if( $top_frev ) {
244 - global $wgParser, $wgLang;
245 - // Parse the timestamp
246 - $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $top_frev->fr_timestamp), true );
247 - // Grab the flags
248 - $flags = $this->getFlagsForRevision( $top_frev->fr_rev_id );
249 - # Looking at some specific old rev or if flagged revs override only for anons
250 - if( $wgRequest->getVal('oldid') || !$this->pageOverride() ) {
251 - if( $revid==$top_frev->rev_id ) {
252 - $flaghtml = wfMsgExt('revreview-isnewest', array('parse'),$time);
253 - } else {
254 - # Our compare link should have a reasonable time-ordered old->new combination
255 - $oldid = ($revid > $top_frev->fr_rev_id) ? $top_frev->fr_rev_id : $revid;
256 - $diff = ($revid > $top_frev->fr_rev_id) ? $revid : $top_frev->fr_rev_id;
257 - $flaghtml = wfMsgExt('revreview-newest', array('parse'), $top_frev->fr_rev_id, $oldid, $diff, $time );
258 - }
259 - } # Viewing the page normally: override the page
260 - else {
261 - global $wgUser;
262 - # We will be looking at the reviewed revision...
263 - $visible_id = $top_frev->fr_rev_id;
264 - $revs_since = $this->getUnreviewedRevCount( $wgArticle->getId(), $visible_id );
265 - $flaghtml = wfMsgExt('revreview-replaced', array('parse'), $visible_id, $wgArticle->getLatest(), $revs_since, $time );
266 - $newbodytext = NULL;
267 - # Try the stable cache
268 - $newbodytext = $this->getPageCache( $wgArticle );
269 - # If no cache is available, get the text and parse it
270 - if ( is_null($newbodytext) ) {
271 - $text = $this->getFlaggedRevText( $visible_id );
272 - # For anons, use standard prefs, for users, get theirs
273 - $options = ParserOptions::newFromUser($wgUser);
274 - # Parsing this text is kind of funky...
275 - $newbodytext = $this->parseStableText( $wgTitle, $text, $visible_id, $options );
276 - # Update the general cache for non-users
277 - $this->updatePageCache( $wgArticle, $newbodytext );
278 - }
279 - }
280 - // Construct some tagging
281 - $flaghtml .= "<table align='center' cellspadding=\'0\'><tr>";
282 - foreach ( $this->dimensions as $quality => $value ) {
283 - $value = wfMsgHtml('revreview-' . $this->dimensions[$quality][$flags[$quality]]);
284 - $flaghtml .= "<td>&nbsp;<strong>" . wfMsgHtml("revreview-$quality") . "</strong>: $value&nbsp;</td>\n";
285 - }
286 - $flaghtml .= '</tr></table>';
287 - // Should use CSS?
288 - $flaghtml = "<small>$flaghtml</small>";
289 -
290 - // Set the new body HTML, place a tag on top
291 - $out->mBodytext = '<div class="mw-warning plainlinks">' . $flaghtml . '</div>' . $newbodytext;
292 - // Add any notes at the bottom
293 - $this->addReviewNotes( $top_frev );
294 - } else {
295 - $flaghtml = wfMsgExt('revreview-noflagged', array('parse'));
296 - $out->mBodytext = '<div class="mw-warning plainlinks">' . $flaghtml . '</div>' . $out->mBodytext;
297 - }
298 - // Override our reference ID for permalink/citation hooks
299 - $wgArticle->mRevision = Revision::newFromId( $visible_id );
300 - // Show review links for the VISIBLE revision
301 - // We cannot review deleted revisions
302 - if( is_object($wgArticle->mRevision) && $wgArticle->mRevision->mDeleted ) return;
303 - // Add quick review links IF we did not override, otherwise, they might
304 - // review a revision that parses out newer templates/images than what they say
305 - // Note: overrides are never done when viewing with "oldid="
306 - if( $visible_id==$revid || !$this->pageOverride() ) {
307 - $this->addQuickReview( $visible_id, false, $out );
308 - }
309 - }
310 -
311 - function addToEditView( &$editform ) {
312 - global $wgRequest, $wgTitle, $wgOut;
313 - // Talk pages cannot be validated
314 - if( !$editform->mArticle || !$wgTitle->isContentPage() )
315 - return;
316 - // Find out revision id
317 - if( $editform->mArticle->mRevision )
318 - $revid = $editform->mArticle->mRevision->mId;
319 - else
320 - $revid = $editform->mArticle->getLatest();
321 - // Grab the ratings for this revision if any
322 - if( !$revid ) return;
323 -
324 - // Set new body html text as that of now
325 - $flaghtml = '';
326 - // Check the newest stable version
327 - $top_frev = $this->getLatestFlaggedRev( $editform->mArticle->getId() );
328 - if( is_object($top_frev) ) {
329 - global $wgParser, $wgLang;
330 - $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $top_frev->fr_timestamp), true );
331 - $flags = $this->getFlagsForRevision( $top_frev->fr_rev_id );
332 - # Looking at some specific old rev
333 - if( $wgRequest->getVal('oldid') ) {
334 - if( $revid==$top_frev->rev_id ) {
335 - $flaghtml = wfMsgExt('revreview-isnewest', array('parse'),$time);
336 - } else {
337 - # Our compare link should have a reasonable time-ordered old->new combination
338 - $oldid = ($revid > $top_frev->fr_rev_id) ? $top_frev->fr_rev_id : $revid;
339 - $diff = ($revid > $top_frev->fr_rev_id) ? $revid : $top_frev->fr_rev_id;
340 - $flaghtml = wfMsgExt('revreview-newest', array('parse'), $top_frev->fr_rev_id, $oldid, $diff, $time );
341 - }
342 - } # Editing the page normally
343 - else {
344 - if( $revid==$top_frev->rev_id )
345 - $flaghtml = wfMsgExt('revreview-isnewest', array('parse'), $time);
346 - else
347 - $flaghtml = wfMsgExt('revreview-newest', array('parse'), $top_frev->fr_rev_id, $top_frev->fr_rev_id, $revid, $time );
348 - }
349 - // Construct some tagging
350 - $flaghtml .= "<table align='center' cellpadding=\'0\'><tr>";
351 - foreach ( $this->dimensions as $quality => $value ) {
352 - $value = wfMsgHtml('revreview-' . $this->dimensions[$quality][$flags[$quality]]);
353 - $flaghtml .= "<td>&nbsp;<strong>" . wfMsgHtml("revreview-$quality") . "</strong>: $value&nbsp;</td>\n";
354 - }
355 - $flaghtml .= '</tr></table>';
356 - // Should use CSS?
357 - $flaghtml = "<small>$flaghtml</small>";
358 - $wgOut->addHTML( '<div class="mw-warning plainlinks">' . $flaghtml . '</div><br/>' );
359 - }
360 - }
361 -
362 - function addToDiff( &$diff, &$oldrev, &$newrev ) {
363 - $id = $newrev->getId();
364 - // We cannot review deleted edits
365 - if( $newrev->mDeleted ) return;
366 - $this->addQuickReview( $id, true );
367 - }
368 -
369 - function setCurrentTab( &$sktmp, &$content_actions ) {
370 - global $wgRequest, $wgArticle, $action;
371 - // Only trigger on article view, not for protect/delete/hist
372 - // Non-content pages cannot be validated
373 - if( !$wgArticle || !$sktmp->mTitle->exists() || !$sktmp->mTitle->isContentPage() || $action !='view' )
374 - return;
375 - // If we are viewing a page normally, and it was overrode
376 - // change the edit tab to a "current revision" tab
377 - if( !$wgRequest->getVal('oldid') ) {
378 - $top_frev = $this->getLatestFlaggedRev( $wgArticle->getId() );
379 - // Note that revisions may not be set to override for users
380 - if( is_object($top_frev) && $this->pageOverride() ) {
381 - # Remove edit option altogether
382 - unset( $content_actions['edit']);
383 - unset( $content_actions['viewsource']);
384 - # Straighten out order
385 - $new_actions = array(); $counter = 0;
386 - foreach ( $content_actions as $action => $data ) {
387 - if( $counter==1 ) {
388 - # Set current rev tab AFTER the main tab is set
389 - $new_actions['current'] = array(
390 - 'class' => '',
391 - 'text' => wfMsg('currentrev'),
392 - 'href' => $sktmp->mTitle->getLocalUrl( 'oldid=' . $wgArticle->getLatest() )
393 - );
394 - }
395 - $new_actions[$action] = $data;
396 - $counter++;
397 - }
398 - # Reset static array
399 - $content_actions = $new_actions;
400 - }
401 - }
402 - }
403 -
404 - function addToPageHist( &$article ) {
405 - $this->pageFlaggedRevs = array();
406 - $rows = $this->getReviewedRevs( $article->getID() );
407 - if( !$rows ) return;
408 - foreach( $rows as $row => $data ) {
409 - $this->pageFlaggedRevs[] = $data->rev_id;
410 - }
411 - }
412 -
413 - function addToHistLine( &$row, &$s ) {
414 - if( isset($this->pageFlaggedRevs) ) {
415 - if( in_array( $row->rev_id, $this->pageFlaggedRevs ) )
416 - $s .= ' <small><strong>' . wfMsgHtml('revreview-hist') . '</strong></small>';
417 - }
418 - }
419 -
420 - function addQuickReview( $id, $ontop=false, &$out=false ) {
421 - global $wgOut, $wgTitle, $wgUser, $wgScript;
422 - // We don't want two forms!
423 - if( isset($this->formCount) && $this->formCount > 0 ) return;
424 - $this->formCount = 1;
425 -
426 - if( !$wgUser->isAllowed( 'review' ) ) return;
427 -
428 - $flags = $this->getFlagsForRevision( $id );
429 -
430 - $reviewtitle = SpecialPage::getTitleFor( 'Revisionreview' );
431 - $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
432 - $form .= "<fieldset><legend>" . wfMsgHtml( 'revreview-flag', $id ) . "</legend>\n";
433 - $form .= wfHidden( 'title', $reviewtitle->getPrefixedText() );
434 - $form .= wfHidden( 'target', $wgTitle->getPrefixedText() );
435 - $form .= wfHidden( 'oldid', $id );
436 - foreach ( $this->dimensions as $quality => $levels ) {
437 - $form .= wfMsgHtml("revreview-$quality") . ": <select name='$quality'>\n";
438 - foreach ( $levels as $idx => $label ) {
439 - if( $flags[$quality]==$idx )
440 - $selected = 'selected';
441 - else
442 - $selected = '';
443 - $form .= "<option value='$idx' $selected>" . wfMsgHtml("revreview-$label") . "</option>\n";
444 - }
445 - $form .= "</select>\n";
446 - }
447 - $form .= Xml::submitButton( wfMsgHtml( 'go' ) ) . "</fieldset>";
448 - $form .= Xml::closeElement( 'form' );
449 - // Hacks, to fiddle around with location a bit
450 - if( $ontop && $out ) {
451 - $out->mBodytext = $form . '<hr/>' . $out->mBodytext;
452 - } else if( $ontop ) {
453 - $wgOut->addHTML( $form );
454 - } else {
455 - $wgOut->addHTML( $form );
456 - }
457 - }
458 -
459 - function addReviewNotes( $row, $breakline=true ) {
460 - global $wgOut, $wgUser, $wgFlaggedRevComments;
461 -
462 - if( !$row || !$wgFlaggedRevComments) return;
463 -
464 - $this->skin = $wgUser->getSkin();
465 - if( $row->fr_comment ) {
466 - $notes = ($breakline) ? '<hr/><br/>' : '';
467 - $notes .= '<div class="mw-warning plainlinks">';
468 - $notes .= wfMsgExt('revreview-note', array('parse'), User::whoIs( $row->fr_user ) );
469 - $notes .= '<i>' . $this->skin->formatComment( $row->fr_comment ) . '</i></div>';
470 - $wgOut->addHTML( $notes );
471 - }
472 - }
473 -
474 - /**
475 - * Get all local image files and generate an array of them
476 - * @param string $s, wikitext
477 - * $output array, (string title array, string thumbnail array)
478 - */
479 - function findLocalImages( $s ) {
480 - global $wgUploadPath;
481 -
482 - $fname = 'findLocalImages';
483 - $imagelist = array(); $thumblist = array();
484 -
485 - if( !$s || !strval($s) ) return $imagelist;
486 -
487 - static $tc = FALSE;
488 - # the % is needed to support urlencoded titles as well
489 - if( !$tc ) { $tc = Title::legalChars() . '#%'; }
490 -
491 - # split the entire text string on occurences of [[
492 - $a = explode( '[[', $s );
493 -
494 - # Ignore things that start with colons, they are image links, not images
495 - $e1_img = "/^([:{$tc}]+)(.+)$/sD";
496 - # Loop for each link
497 - for ($k = 0; isset( $a[$k] ); $k++) {
498 - if( preg_match( $e1_img, $a[$k], $m ) ) {
499 - # page with normal text or alt of form x or ns:x
500 - $nt = Title::newFromText( $m[1] );
501 - $ns = $nt->getNamespace();
502 - # add if this is an image
503 - if( $ns == NS_IMAGE ) {
504 - $imagelist[] = $nt->getPrefixedText();
505 - }
506 - $image = $nt->getDBKey();
507 - # check for data for thumbnails
508 - $part = array_map( 'trim', explode( '|', $m[2]) );
509 - foreach( $part as $val ) {
510 - if( preg_match( '/^([0-9]+)px$/', $val, $n ) ) {
511 - $width = intval( $n[1] );
512 - $thumblist[$image] = $width;
513 - } else if( preg_match( '/^([0-9]+)x([0-9]+)$/', $val, $n ) ) {
514 - $width = intval( $n[1] );
515 - $thumblist[$image] = $width;
516 - }
517 - }
518 - }
519 - }
520 - return array( $imagelist, $thumblist );
521 - }
522 -
523 - /**
524 - * Showtime! Copy all used images to a stable directory
525 - * This updates (overwrites) any existing stable images
526 - * Won't work for sites with unhashed dirs that have subfolders protected
527 - * The future FileStore migration might effect this, not sure...
528 - * @param array $imagelist, list of string names
529 - * $output array, list of string names of images sucessfully cloned
530 - */
531 - function makeStableImages( $imagelist ) {
532 - global $wgUploadDirectory, $wgSharedUploadDirectory;
533 - // All stable images are local, not shared
534 - // Otherwise, we could have some nasty cross language/wiki conflicts
535 - $stableDir = "$wgUploadDirectory/stable";
536 - // Copy images to stable dir
537 - $usedimages = array();
538 - // We need valid input
539 - if( !is_array($imagelist) ) return $usedimages;
540 - foreach ( $imagelist as $name ) {
541 - // We want a clean and consistant title entry
542 - $nt = Title::newFromText( $name );
543 - if( is_null($nt) ) {
544 - // If this title somehow doesn't work, ignore it
545 - // this shouldn't happen...
546 - continue;
547 - }
548 - $name = $nt->getDBkey();
549 - $hash = wfGetHashPath($name);
550 - $path = $wgUploadDirectory . $hash;
551 - $sharedpath = $wgSharedUploadDirectory . $hash;
552 - // Try local repository
553 - if( is_dir($path) ) {
554 - if( file_exists("{$path}{$name}") ) {
555 - // Check if our stable dir exists
556 - // Make it if it doesn't
557 - if( !is_dir($stableDir . $hash) ) {
558 - wfMkdirParents($stableDir . $hash);
559 - }
560 - copy("{$path}{$name}","{$stableDir}{$hash}{$name}");
561 - $usedimages[] = $name;
562 - }
563 - } // Try shared repository
564 - else if( is_dir($sharedpath) ) {
565 - if( file_exists("{$sharedpath}{$name}") ) {
566 - // Check if our stable dir exists
567 - // Make it if it doesn't
568 - if( !is_dir($stableDir . $hash) ) {
569 - wfMkdirParents($stableDir . $hash);
570 - }
571 - copy("{$sharedpath}{$name}","{$stableDir}{$hash}{$name}");
572 - $usedimages[] = $name;
573 - }
574 - }
575 - }
576 - return $usedimages;
577 - }
578 -
579 - /**
580 - * Delete an a list of stable image files
581 - * @param array $imagelist, list of string names
582 - * $output array, list of string names of images to be deleted
583 - */
584 - function deleteStableImages( $imagelist ) {
585 - global $wgSharedUploadDirectory;
586 - // All stable images are local, not shared
587 - // Otherwise, we could have some nasty cross language/wiki conflicts
588 - $stableDir = "$wgUploadDirectory/stable";
589 - // Copy images to stable dir
590 - $deletedimages = array();
591 - // We need valid input
592 - if( !is_array($imagelist) ) return $usedimages;
593 - foreach ( $imagelist as $name ) {
594 - // We want a clean and consistant title entry
595 - $nt = Title::newFromText( $name );
596 - if( is_null($nt) ) {
597 - // If this title somehow doesn't work, ignore it
598 - // this shouldn't happen...
599 - continue;
600 - }
601 - $name = $nt->getDBkey();
602 - $hash = wfGetHashPath($name);
603 - $path = $stableDir . $hash;
604 - // Try the stable repository
605 - if( is_dir($path) ) {
606 - if( file_exists("{$path}{$name}") ) {
607 - // Delete!
608 - delete("{$path}{$name}");
609 - $deletedimages[] = $name;
610 - }
611 - }
612 - }
613 - return $deletedimages;
614 - }
615 -
616 - /**
617 - * Delete an a list of stable image thumbnails
618 - * New thumbnails don't normally override old ones, causing outdated images
619 - * This allows for tagged revisions to be re-reviewed with newer images
620 - * @param array $imagelist, list of string names
621 - * $output array, list of string names of images to be deleted
622 - */
623 - function purgeStableThumbnails( $thumblist ) {
624 - global $wgUploadDirectory, $wgUseImageResize;
625 - // Are thumbs even enabled?
626 - if ( !$wgUseImageResize ) return true;
627 - // We need valid input
628 - if( !is_array($thumblist) ) return false;
629 - foreach ( $thumblist as $name => $width ) {
630 - $thumburl = "{$wgUploadDirectory}/stable/thumb" . wfGetHashPath( $name, false ) . "$name/". $width."px-".$name;
631 - if( file_exists($thumburl) ) {
632 - unlink($thumburl);
633 - }
634 - }
635 - return true;
636 - }
637 -
638 - /**
639 - * Update the stable image usage table
640 - * Add some images if not redundant
641 - * @param array $imagelist, list of string names
642 - * $output bool, on succeed
643 - */
644 - function insertStableImages( $revid, $imagelist ) {
645 - wfProfileIn( __METHOD__ );
646 -
647 - if( !is_array($imagelist) ) return false;
648 -
649 - $db = wfGetDB( DB_MASTER );
650 - foreach( $imagelist as $name ) {
651 - // We want a clean and consistant title entry
652 - $nt = Title::newFromText( $name );
653 - if( is_null($nt) ) {
654 - // If this title somehow doesn't work, ignore it
655 - // this shouldn't happen...
656 - continue;
657 - }
658 - $imagename = $nt->getDBkey();
659 - // Add image and the revision that uses it
660 - $set = array('fi_rev_id' => $revid, 'fi_name' => $imagename);
661 - // Add entries or replace any that have the same rev_id
662 - $db->replace( 'flaggedimages', array( array('fi_rev_id', 'fi_name') ), $set, __METHOD__ );
663 - }
664 - return true;
665 - }
666 -
667 - /**
668 - * Update the stable image usage table
669 - * Clean out unused images if needed
670 - * @param array $imagelist, list of string names
671 - * $output bool, on succeed
672 - */
673 - function removeStableImages( $revid, $imagelist ) {
674 - wfProfileIn( __METHOD__ );
675 -
676 - if( !is_array($imagelist) ) return false;
677 - $unusedimages = array();
678 - $db = wfGetDB( DB_MASTER );
679 - foreach( $imagelist as $name ) {
680 - // We want a clean and consistant title entry
681 - $nt = Title::newFromText( $name );
682 - if( is_null($nt) ) {
683 - // If this title somehow doesn't work, ignore it
684 - // this shouldn't happen...
685 - continue;
686 - }
687 - $imagename = $nt->getDBkey();
688 - $where = array( 'fi_rev_id' => $revid, 'fi_name' => $imagename );
689 - // See how many revisions use this image total...
690 - $result = $db->select( 'flaggedimages', array('fi_id'), array( 'fi_name' => $imagename ) );
691 - // If only one, then delete the image
692 - // since it's about to be remove from that one
693 - if( $db->numRows($result)==1 ) {
694 - $unusedimages[] = $imagename;
695 - }
696 - // Clear out this revision's entry
697 - $db->delete( 'flaggedimages', $where );
698 - }
699 - $this->deleteStableImages( $unusedimages );
700 - return true;
701 - }
702 -
703 - function getPageCache( $article ) {
704 - global $wgUser, $wgFlaggedRevsExpire;
705 -
706 - wfProfileIn( __METHOD__ );
707 -
708 - // Make sure it is valid
709 - if ( !$article || !$article->getId() ) return NULL;
710 - $cachekey = ParserCache::getKey( $article, $wgUser );
711 -
712 - $db = wfGetDB( DB_SLAVE );
713 - $cutoff = $db->timestamp( time() - $wgFlaggedRevsExpire );
714 - // Replace the page cache if it is out of date
715 - $result = $db->select(
716 - array('flaggedcache'),
717 - array('fc_cache'),
718 - array('fc_key' => $cachekey, 'fc_date >= ' . $article->getTouched(), 'fc_date >= ' . $cutoff ),
719 - __METHOD__);
720 - if ( $row = $db->fetchObject($result) ) {
721 - return $row->fc_cache;
722 - }
723 - return NULL;
724 - }
725 -
726 - function updatePageCache( $article, $value=NULL ) {
727 - global $wgUser;
728 - wfProfileIn( __METHOD__ );
729 -
730 - // Make sure it is valid
731 - if ( is_null($value) || !$article || !$article->getId() ) return false;
732 - $cachekey = ParserCache::getKey( $article, $wgUser );
733 - // Add cache mark
734 - $timestamp = wfTimestampNow();
735 - $value .= "\n<!-- Saved in stable version parser cache with key $cachekey and timestamp $timestamp -->";
736 -
737 - $dbw = wfGetDB( DB_MASTER );
738 - // Replace the page cache if it is out of date
739 - $dbw->replace('flaggedcache',
740 - array('fc_key'),
741 - array('fc_key' => $cachekey, 'fc_cache' => $value, 'fc_date' => $timestamp),
742 - __METHOD__);
743 -
744 - return true;
745 - }
746 -}
747 -
748 -# Load expert promotion UI
749 -include_once('SpecialMakevalidate.php');
750 -
751 -if( !function_exists( 'extAddSpecialPage' ) ) {
752 - require( dirname(__FILE__) . '/../ExtensionFunctions.php' );
753 -}
754 -extAddSpecialPage( dirname(__FILE__) . '/FlaggedRevsPage.body.php', 'Revisionreview', 'Revisionreview' );
755 -
756 -# Load approve/unapprove UI
757 -$wgHooks['LoadAllMessages'][] = 'efLoadReviewMessages';
758 -
759 -$flaggedrevs = new FlaggedRevs();
760 -$wgHooks['BeforePageDisplay'][] = array($flaggedrevs, 'setPageContent');
761 -$wgHooks['DiffViewHeader'][] = array($flaggedrevs, 'addToDiff');
762 -$wgHooks['EditPage::showEditForm:initial'][] = array($flaggedrevs, 'addToEditView');
763 -$wgHooks['SkinTemplateTabs'][] = array($flaggedrevs, 'setCurrentTab');
764 -$wgHooks['PageHistoryBeforeList'][] = array($flaggedrevs, 'addToPageHist');
765 -$wgHooks['PageHistoryLineEnding'][] = array($flaggedrevs, 'addToHistLine');
766 -?>
Index: branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.body.php
@@ -0,0 +1,302 @@
 2+<?php
 3+
 4+#(c) Joerg Baach, Aaron Schulz, 2007 GPL
 5+
 6+global $IP;
 7+require_once( "$IP/includes/LogPage.php" );
 8+require_once( "$IP/includes/SpecialLog.php" );
 9+
 10+class Revisionreview extends SpecialPage
 11+{
 12+
 13+ function Revisionreview() {
 14+ SpecialPage::SpecialPage('Revisionreview', 'review');
 15+ }
 16+
 17+ function execute( $par ) {
 18+ global $wgRequest, $wgUser, $wgOut, $wgFlaggedRevComments;
 19+
 20+ if( !$wgUser->isAllowed( 'review' ) ) {
 21+ $wgOut->permissionRequired( 'review' );
 22+ return;
 23+ }
 24+
 25+ $this->setHeaders();
 26+ // Our target page
 27+ $this->target = $wgRequest->getText( 'target' );
 28+ // Revision ID
 29+ $this->oldid = $wgRequest->getIntOrNull( 'oldid' );
 30+ // Log comment
 31+ $this->comment = $wgRequest->getText( 'wpReason' );
 32+ // Additional notes
 33+ $this->notes = ($wgFlaggedRevComments) ? $wgRequest->getText('wpNotes') : '';
 34+ // Get our accuracy/quality array
 35+ $this->dimensions = array();
 36+ $this->dimensions['acc'] = $wgRequest->getIntOrNull('accuracy');
 37+ $this->dimensions['depth'] = $wgRequest->getIntOrNull('depth');
 38+ $this->dimensions['style'] = $wgRequest->getIntOrNull('style');
 39+ // Must be a valid page
 40+ $this->page = Title::newFromUrl( $this->target );
 41+ if( is_null($this->page) || is_null($this->oldid) || !$this->page->isContentPage() ) {
 42+ $wgOut->showErrorPage( $this->page, 'notargettitle', 'notargettext' );
 43+ return;
 44+ }
 45+ if( $wgRequest->wasPosted() ) {
 46+ $this->submit( $wgRequest );
 47+ } else {
 48+ $this->showRevision( $wgRequest );
 49+ }
 50+ }
 51+
 52+ /**
 53+ * @param webrequest $request
 54+ */
 55+ function showRevision( $request ) {
 56+ global $wgOut, $wgUser, $wgTitle, $wgFlaggedRevComments;
 57+
 58+ $wgOut->addWikiText( wfMsgExt( 'revreview-selected', array('parsemag'), $this->page->getPrefixedText() ) );
 59+
 60+ $this->skin = $wgUser->getSkin();
 61+ $rev = Revision::newFromTitle( $this->page, $this->oldid );
 62+ // Check if rev exists
 63+ if( !isset( $rev ) ) {
 64+ $wgOut->showErrorPage( 'internalerror', 'notargettitle', 'notargettext' );
 65+ return;
 66+ }
 67+ // Do not mess with deleted revisions
 68+ if ( $rev->mDeleted ) {
 69+ $wgOut->showErrorPage( 'internalerror', 'badarticleerror' );
 70+ return;
 71+ }
 72+ $wgOut->addHtml( "<ul>" );
 73+ $wgOut->addHtml( $this->historyLine( $rev ) );
 74+ $wgOut->addHtml( "</ul>" );
 75+
 76+ $wgOut->addWikiText( wfMsgHtml( 'revreview-text' ) );
 77+
 78+ $this->accRadios = array(
 79+ array( 'revreview-acc-0', 'wpAcc1', 0 ),
 80+ array( 'revreview-acc-1', 'wpAcc2', 1 ),
 81+ array( 'revreview-acc-2', 'wpAcc3', 2 ),
 82+ array( 'revreview-acc-3', 'wpAcc4', 3 ) );
 83+ $this->depthRadios = array(
 84+ array( 'revreview-depth-0', 'wpDepth1', 0 ),
 85+ array( 'revreview-depth-1', 'wpDepth2', 1 ),
 86+ array( 'revreview-depth-2', 'wpDepth3', 2 ),
 87+ array( 'revreview-depth-3', 'wpDepth4', 3 ) );
 88+ $this->styleRadios = array(
 89+ array( 'revreview-style-0', 'wpStyle1', 0 ),
 90+ array( 'revreview-style-1', 'wpStyle2', 1 ),
 91+ array( 'revreview-style-2', 'wpStyle3', 2 ),
 92+ array( 'revreview-style-3', 'wpStyle4', 3 ) );
 93+ $items = array(
 94+ wfInputLabel( wfMsgHtml( 'revreview-log' ), 'wpReason', 'wpReason', 60 ),
 95+ wfSubmitButton( wfMsgHtml( 'revreview-submit' ) ) );
 96+ $hidden = array(
 97+ wfHidden( 'wpEditToken', $wgUser->editToken() ),
 98+ wfHidden( 'target', $this->page->getPrefixedText() ),
 99+ wfHidden( 'oldid', $this->oldid ) );
 100+
 101+ $action = $wgTitle->escapeLocalUrl( 'action=submit' );
 102+ $form = "<form name='revisionreview' action='$action' method='post'>";
 103+ $form .= '<fieldset><legend>' . wfMsgHtml( 'revreview-legend' ) . '</legend><table><tr>';
 104+ $form .= '<td><strong>' . wfMsgHtml( 'revreview-acc' ) . '</strong></td>';
 105+ $form .= '<td width=\'25\'></td><td><strong>' . wfMsgHtml( 'revreview-depth' ) . '</strong></td>';
 106+ $form .= '<td width=\'25\'></td><td><strong>' . wfMsgHtml( 'revreview-style' ) . '</strong></td>';
 107+ $form .= '</tr><tr><td>';
 108+ foreach( $this->accRadios as $item ) {
 109+ list( $message, $name, $field ) = $item;
 110+ $form .= "<div>" .
 111+ Xml::radio( 'accuracy', $field, ($field==$this->dimensions['acc']) ) . ' ' . wfMsgHtml($message) .
 112+ "</div>\n";
 113+ }
 114+ $form .= '<td width=\'25\'></td></td><td>';
 115+ foreach( $this->depthRadios as $item ) {
 116+ list( $message, $name, $field ) = $item;
 117+ $form .= "<div>" .
 118+ Xml::radio( 'depth', $field, ($field==$this->dimensions['depth']) ) . ' ' . wfMsgHtml($message) .
 119+ "</div>\n";
 120+ }
 121+ $form .= '<td width=\'25\'></td></td><td>';
 122+ foreach( $this->styleRadios as $item ) {
 123+ list( $message, $name, $field ) = $item;
 124+ $form .= "<div>" .
 125+ Xml::radio( 'style', $field, ($field==$this->dimensions['style']) ) . ' ' . wfMsgHtml($message) .
 126+ "</div>\n";
 127+ }
 128+ $form .= '</td></tr></table></fieldset>';
 129+
 130+ list($images,$thumbs) = FlaggedRevs::findLocalImages( FlaggedRevs::expandText( $rev->getText() ) );
 131+ if ( $images ) {
 132+ $form .= wfMsg('revreview-images') . "\n";
 133+ $form .= "<ul>";
 134+ $imglist = '';
 135+ foreach ( $images as $image ) {
 136+ $imglist .= "<li>" . $this->skin->makeKnownLink( $image ) . "</li>\n";
 137+ }
 138+ $form .= $imglist;
 139+ $form .= "</ul>\n";
 140+ }
 141+ if ( $wgFlaggedRevComments ) {
 142+ $form .= "<fieldset><legend>" . wfMsgHtml( 'revreview-notes' ) . "</legend>" .
 143+ "<textarea tabindex='1' name='wpNotes' id='wpNotes' rows='3' cols='80' style='width:100%'></textarea>" .
 144+ "</fieldset>";
 145+ }
 146+
 147+ foreach( $items as $item ) {
 148+ $form .= '<p>' . $item . '</p>';
 149+ }
 150+ foreach( $hidden as $item ) {
 151+ $form .= $item;
 152+ }
 153+
 154+ $form .= '</form>';
 155+ $wgOut->addHtml( $form );
 156+ }
 157+
 158+ /**
 159+ * @param Revision $rev
 160+ * @returns string
 161+ */
 162+ function historyLine( $rev ) {
 163+ global $wgContLang;
 164+ $date = $wgContLang->timeanddate( $rev->getTimestamp() );
 165+
 166+ $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'),
 167+ '&diff=' . $rev->getId() . '&oldid=prev' ) . ')';
 168+
 169+ $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid=' . $rev->getId() );
 170+
 171+ return
 172+ "<li> $difflink $revlink " . $this->skin->revUserLink( $rev ) . " " . $this->skin->revComment( $rev ) . "</li>";
 173+ }
 174+
 175+ function submit( $request ) {
 176+ global $wgOut;
 177+
 178+ $rev = Revision::newFromTitle( $this->page, $this->oldid );
 179+ // Do not mess with deleted revisions
 180+ if ( is_null($rev) || $rev->mDeleted ) {
 181+ $wgOut->showErrorPage( 'internalerror', 'badarticleerror' );
 182+ return;
 183+ }
 184+ $approved = false;
 185+ # If all values are set to zero, this has been unnapproved
 186+ foreach( $this->dimensions as $quality => $value ) {
 187+ if( $value ) $approved = true;
 188+ }
 189+ $success = ( $approved ) ? $this->approveRevision( $rev ) : $this->unapproveRevision( $rev );
 190+ // Return to our page
 191+ if ( $success ) {
 192+ $wgOut->redirect( $this->page->escapeLocalUrl() );
 193+ } else {
 194+ $wgOut->showErrorPage( 'internalerror', 'badarticleerror' );
 195+ }
 196+ }
 197+
 198+ /**
 199+ * @param Revision $rev
 200+ * Adds or updates the flagged revision table for this page/id set
 201+ */
 202+ function approveRevision( $rev=NULL ) {
 203+ global $wgUser;
 204+ if( is_null($rev) ) return false;
 205+
 206+ wfProfileIn( __METHOD__ );
 207+
 208+ $db = wfGetDB( DB_MASTER );
 209+ $user = $wgUser->getId();
 210+ $timestamp = wfTimestampNow();
 211+
 212+ $cache_text = FlaggedRevs::expandText( $rev->getText() );
 213+ // Add or update entry for this revision
 214+ $set = array(
 215+ 'fr_page_id' => $rev->getPage(),
 216+ 'fr_rev_id' => $rev->getId(),
 217+ 'fr_acc' => $this->dimensions['acc'],
 218+ 'fr_dep' => $this->dimensions['depth'],
 219+ 'fr_sty' => $this->dimensions['style'],
 220+ 'fr_user' => $user,
 221+ 'fr_timestamp' => $timestamp,
 222+ 'fr_comment'=> $this->notes
 223+ );
 224+ $set2 = array('ft_rev_id' => $rev->getId(), 'ft_text' => $cache_text);
 225+ // Update flagrevisions table
 226+ $db->replace( 'flaggedrevs', array( array('fr_page_id','fr_rev_id') ), $set, __METHOD__ );
 227+ // Store/update the text
 228+ $db->replace( 'flaggedtext', array('ft_rev_id'), $set2, __METHOD__ );
 229+ // Update the article review log
 230+ $this->updateLog( $this->page, $this->dimensions, $this->comment, $this->oldid, true );
 231+ // Clone images to stable dir
 232+ list($images,$thumbs) = FlaggedRevs::findLocalImages( $cache_text );
 233+ $copies = FlaggedRevs::makeStableImages( $images );
 234+ FlaggedRevs::purgeStableThumbnails( $thumbs );
 235+ // Update stable image table
 236+ FlaggedRevs::insertStableImages( $rev->getId(), $copies );
 237+ // Clear cache...
 238+ $this->page->invalidateCache();
 239+ return true;
 240+ }
 241+
 242+ /**
 243+ * @param Revision $rev
 244+ * Removes flagged revision data for this page/id set
 245+ */
 246+ function unapproveRevision( $rev=NULL ) {
 247+ global $wgUser;
 248+
 249+ if( is_null($rev) ) return false;
 250+ $db = wfGetDB( DB_MASTER );
 251+ $user = $wgUser->getId();
 252+ $timestamp = wfTimestampNow();
 253+ // get the flagged revision to access its cache text
 254+ $frev = FlaggedRevs::getFlaggedRevision( $rev->getId() );
 255+ if( !$frev ) {
 256+ // Quitly ignore this...
 257+ return true;
 258+ }
 259+ $db->delete( 'flaggedrevs', array( 'fr_rev_id' => $rev->getId ) );
 260+ // Update the article review log
 261+ $this->updateLog( $this->page, $this->dimensions, $this->comment, $this->oldid, false );
 262+
 263+ $cache_text = FlaggedRevs::getFlaggedRevText( $rev->getId ) ;
 264+ // Delete stable images if needed
 265+ list($images,$thumbs) = FlaggedRevs::findLocalImages( $cache_text );
 266+ $copies = FlaggedRevs::deleteStableImages( $images );
 267+ // Stable versions must remake this thumbnail
 268+ FlaggedRevs::purgeStableThumbnails( $thumbs );
 269+ // Update stable image table
 270+ FlaggedRevs::removeStableImages( $rev->getId(), $copies );
 271+ // Clear cache...
 272+ $this->page->invalidateCache();
 273+ return true;
 274+ }
 275+
 276+ /**
 277+ * Record a log entry on the action
 278+ * @param Title $title
 279+ * @param array $dimensions
 280+ * @param string $comment
 281+ * @param int $revid
 282+ * @param bool $approve
 283+ */
 284+ function updateLog( $title, $dimensions, $comment, $oldid, $approve ) {
 285+ $log = new LogPage( 'review' );
 286+ // ID, accuracy, depth, style
 287+ $ratings = array();
 288+ foreach( $dimensions as $quality => $level ) {
 289+ $ratings[] = wfMsg( "revreview-$quality" ) . ": " . wfMsg("revreview-$quality-$level");
 290+ }
 291+ $rating = ($approve) ? ' [' . implode(', ',$ratings). ']' : '';
 292+ // Append comment with action
 293+ $action = wfMsgExt('review-logaction', array('parsemag'), $oldid );
 294+ $comment = ($comment) ? "$action: $comment$rating" : "$action $rating";
 295+
 296+ if ( $approve ) {
 297+ $log->addEntry( 'approve', $title, $comment );
 298+ } else {
 299+ $log->addEntry( 'unapprove', $title, $comment );
 300+ }
 301+ }
 302+}
 303+?>
Property changes on: branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.body.php
___________________________________________________________________
Added: svn:eol-style
1304 + native
Index: branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.i18n.php
@@ -0,0 +1,66 @@
 2+<?php
 3+$RevisionreviewMessages = array();
 4+
 5+// English (Aaron Schulz)
 6+$RevisionreviewMessages['en'] = array(
 7+ 'reviewer' => 'Reviewer',
 8+ 'group-reviewer' => 'Reviewers',
 9+ 'group-reviewer' => 'Reviewer',
 10+ 'grouppage-reviewer' => '{{ns:project}}:Reviewer',
 11+
 12+ 'revreview-noflagged' => 'There are no reviewed revisions of this page, so it has \'\'\'not\'\'\' been
 13+ [[Help:Article validation|checked]] for quality.',
 14+ 'revreview-isnewest' => 'This is the latest [[Help:Article validation|reviewed]] revision of this page (with
 15+ updated images and templates) [{{fullurl:Special:Log/review|page={{FULLPAGENAMEE}}}} approved] on <i>$1</i>.',
 16+ 'revreview-newest' => 'The [{{fullurl:{{FULLPAGENAMEE}}|oldid=$1}} latest reviewed revision]
 17+ ([{{fullurl:{{FULLPAGENAMEE}}|oldid=$2&diff=$3}} compare]) was [{{fullurl:Special:Log/review|page={{FULLPAGENAMEE}}}} approved]
 18+ on <i>$4</i>, rated as:',
 19+ 'revreview-replaced' => 'This is the latest [[Help:Article validation|reviewed]] revision of this page,
 20+ [{{fullurl:Special:Log/review|page={{FULLPAGENAMEE}}}} approved] on <i>$4</i>. The [{{fullurl:{{FULLPAGENAMEE}}|oldid=$2}} current revision]
 21+ is editable and may be more up to date. There {{plural:$3|is $3 revision|are $3 revisions}}
 22+ ([{{fullurl:{{FULLPAGENAMEE}}|oldid=$1&diff=$2}} changes]) awaiting review.',
 23+
 24+ 'revisionreview' => 'Review revisions',
 25+
 26+ 'flaggedrevs' => 'Flagged Revisions',
 27+ 'review-logpage' => 'Article review log',
 28+ 'review-logpagetext' => 'This is a log of changes to revisions\' [[Help:Article validation|approval]] status
 29+ for content pages.',
 30+ 'review-logentrygrant' => 'approved [[$1]]',
 31+ 'review-logentryrevoke' => 'unapproved [[$1]]',
 32+ 'review-logaction' => 'reviewed revision $1',
 33+
 34+ 'revreview-selected' => "Selected revision of '''$1:'''",
 35+ 'revreview-text' => "Approved revisions are set as the default revision shown upon page view rather
 36+ than the top revision. The content of this approved revision will remain constant regardless of any transcluded
 37+ pages or internal images. Users on this wiki will still be able to access
 38+ unreviewed content through the page history.",
 39+ 'revreview-images' => 'Internal images on this page will be copied to the stable image directory, updating
 40+ existing versions, and stored there until no reviewed revisions use them. The following images are transcluded onto this page:',
 41+
 42+ 'revreview-hist' => '[reviewed]',
 43+
 44+ 'revreview-note' => '[[User:$1]] made the following notes [[Help:Article validation|reviewing]] this revision:',
 45+
 46+ 'revreview-flag' => 'Review this revision (#$1):',
 47+ 'revreview-legend' => 'Rate revision content:',
 48+ 'revreview-notes' => 'Observations or notes to display:',
 49+ 'revreview-acc' => 'Accuracy',
 50+ 'revreview-acc-0' => 'Unapproved',
 51+ 'revreview-acc-1' => 'Not vandalized',
 52+ 'revreview-acc-2' => 'Accurate',
 53+ 'revreview-acc-3' => 'Well sourced',
 54+ 'revreview-depth' => 'Depth',
 55+ 'revreview-depth-0' => 'Unapproved',
 56+ 'revreview-depth-1' => 'Stub',
 57+ 'revreview-depth-2' => 'Moderate',
 58+ 'revreview-depth-3' => 'Complete',
 59+ 'revreview-style' => 'Readability',
 60+ 'revreview-style-0' => 'Unapproved',
 61+ 'revreview-style-1' => 'Acceptable',
 62+ 'revreview-style-2' => 'Good',
 63+ 'revreview-style-3' => 'Concise',
 64+ 'revreview-log' => 'Log comment:',
 65+ 'revreview-submit' => 'Apply to selected revision',
 66+);
 67+?>
\ No newline at end of file
Property changes on: branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevsPage.i18n.php
___________________________________________________________________
Added: svn:eol-style
168 + native
Index: branches/jhb/phase3/extensions/FlaggedRevs/Makevalidate.i18n.php
@@ -0,0 +1,34 @@
 2+<?php
 3+/**
 4+ * Internationalisation file for makevalidate extension.
 5+ *
 6+ * @package MediaWiki
 7+ * @subpackage Extensions
 8+*/
 9+
 10+function efMakeValidateMessages() {
 11+ $messages = array();
 12+
 13+/* English (Aaron Schulz) */
 14+$messages['en'] = array(
 15+ 'makevalidate' => 'Promote/demote reviewers',
 16+ 'makevalidate-header' => '<strong>This form is used by bureaucrats to turn ordinary users into stable version validators.</strong><br> Type the name of the user in the box and press the button to make the user a validator.',
 17+ 'makevalidate-username' => 'Name of the user:',
 18+ 'makevalidate-search' => 'Go',
 19+ 'makevalidate-isvalidator' => '[[User:$1|$1]] has reviewer status.',
 20+ 'makevalidate-notvalidator' => '[[User:$1|$1]] does not have reviewer status.',
 21+ 'makevalidate-change' => 'Change status:',
 22+ 'makevalidate-grant' => 'Grant',
 23+ 'makevalidate-revoke' => 'Revoke',
 24+ 'makevalidate-comment' => 'Comment:',
 25+ 'makevalidate-granted' => '[[User:$1|$1]] now has validator status.',
 26+ 'makevalidate-revoked' => '[[User:$1|$1]] no longer has validator status.',
 27+ 'makevalidate-logpage' => 'Reviewer status log',
 28+ 'makevalidate-logpagetext' => 'This is a log of changes to users\' [[Help:Article validation|article validation]] status.',
 29+ 'makevalidate-logentrygrant' => 'granted validator status to [[$1]]',
 30+ 'makevalidate-logentryrevoke' => 'removed validator status from [[$1]]',
 31+);
 32+
 33+return $messages;
 34+}
 35+?>
Index: branches/jhb/phase3/extensions/FlaggedRevs/specs.txt
@@ -0,0 +1,94 @@
 2+== The implementation ==
 3+
 4+The specs to implement are a variation of the proposal by Philipp and
 5+other German Wikipedians, found at:
 6+http://de.wikipedia.org/wiki/Wikipedia:Gesichtete_Versionen
 7+http://de.wikipedia.org/wiki/Wikipedia:Gepr%C3%BCfte_Versionen
 8+
 9+The changes are aimed mostly at making the feature more flexibly
 10+configurable, and streamlining some of the UI tasks.
 11+
 12+The feature is to be implemented as an extension, if at all possible.
 13+It must be GPL-licensed and will be committed to the Wikimedia
 14+Subversion server.
 15+
 16+The wiki will allow the configuration of "revision tags", which can
 17+then be associated with any revision of an article. Tags are organized
 18+in tag array, where each array represents a set of tags that describe
 19+a similar attribute, e.g., accuracy-related tags would be organized in
 20+one array, while those used for flagging materials for export might be
 21+organized in another. This is to ensure that "levels" of quality
 22+(unvandalized -> reviewed for accuracy -> featured article etc.) can
 23+be represented correctly.
 24+
 25+Each tag has certain attributes:
 26+- tag comments: Should the tag support a comment field which explains
 27+why the revision was tagged in a certain way?
 28+- permissions: Which user groups have permission to set the tag?
 29+- stylesheet and UI: not requiring explicit configuration, each tag
 30+should have a stylesheet class and MediaWiki: namespace message(s)
 31+associated with it, to allow for differences in visual and textual
 32+representation
 33+- implicit tagging: should this tag be implicitly set by any user
 34+within the associated group when editing? (this will be used for the
 35+"non-vandalized" tag)
 36+
 37+A generic change is required to allow for automatic membership in a
 38+user group when a user has more than X edits and is older than Y days.
 39+
 40+There should be a configuration option which associates a namespace with
 41+- a tag-array
 42+- a minimum level in that array.
 43+
 44+This option determines that, by default, the revision shown from this
 45+namespace will be the one from that array which is also >= the minimum
 46+level. So, for instance, one could determine that pages in the article
 47+namespace need to be at least checked for vandalism, or at least
 48+reviewed for accuracy.
 49+
 50+As a high priority wishlist item, these view options should also be
 51+applicable on a per-page basis. The existing protection UI should be
 52+expanded for this purpose. Implementation (and schedule) of this item
 53+will depend on overall implementation progress.
 54+
 55+Whichever view one ends up with, we expect that the top of the page
 56+indicates this, and allows you to switch & get diffs to other views.
 57+
 58+Because MediaWiki currently does not show templates and revisions in
 59+time synchronization, this behavior has to be fixed for old revisions.
 60+When one has expressed a preference for a revision with a specific
 61+tag/level (e.g. "unvandalized") AND this is the most recent revision,
 62+it will be shown together with the most recent equally tagged
 63+templates if they exist, otherwise with the most recent ones.
 64+
 65+Example: On a page like the Main Page, which includes many templates,
 66+one would typically want to have an unvandalized view of the entire
 67+construction. The Main Page itself rarely changes, but because the
 68+most recent revision is flagged as "unvandalized", it would be
 69+synchronized with templates for which this is also true. When viewing
 70+an older version, however, the templates would be shown as they were
 71+at that date&time.
 72+
 73+It is crucial that queries for revision lookup are highly performant;
 74+we should aim for a performance impact of less than 10% on uncached
 75+pageviews with a revision tag preference. Needless to say, the feature
 76+needs to interact correctly with Squid proxy caching.
 77+
 78+Tagging revisions should be possible from three places:
 79+- when editing (with the help of a collapsible diff)
 80+- when looking at diffs
 81+- when looking at revisions without any prior or later tags.
 82+
 83+A tag can only be set with reference to a diff to the last version
 84+that has the same tag. The Special:Recentchanges tool should be
 85+customizable to have such a diff link to the last version with a given
 86+tag. It is desirable that this view also includes an icon that
 87+indicates the state of the logged revision (derived from the tag
 88+stylesheets).
 89+
 90+Wishlist items for the future include things like mass vandalism
 91+review and advanced queries. I also have some ideas for phase II of
 92+the project, which I would love to see implemented before the tool is
 93+switched on on the English Wikipedia, but this will wait until phase I
 94+and any adjustments needed for its operations are successfully
 95+completed.
Property changes on: branches/jhb/phase3/extensions/FlaggedRevs/specs.txt
___________________________________________________________________
Added: svn:eol-style
196 + native
Index: branches/jhb/phase3/extensions/FlaggedRevs/SpecialMakevalidate.php
@@ -0,0 +1,65 @@
 2+<?php
 3+
 4+/**
 5+ * Special page to allow local bureaucrats to grant/revoke the reviewer flag
 6+ * for a particular user
 7+ *
 8+ * @addtogroup Extensions
 9+ * Tiny modifications by Aaron Schulz to MakeBot
 10+ * MakeBot extension:
 11+ ** @author Rob Church <robchur@gmail.com>
 12+ ** @copyright © 2006 Rob Church
 13+ ** @licence GNU General Public Licence 2.0 or later
 14+ */
 15+
 16+if( defined( 'MEDIAWIKI' ) ) {
 17+
 18+ define( 'MW_MAKEVALIDATE_GRANT', 1 );
 19+ define( 'MW_MAKEVALIDATE_REVOKE', 2 );
 20+
 21+ $wgExtensionFunctions[] = 'efMakevalidate';
 22+ $wgAvailableRights[] = 'makevalidate';
 23+ $wgExtensionCredits['specialpage'][] = array(
 24+ 'name' => 'MakeBot',
 25+ 'author' => 'Rob Church',
 26+ 'url' => 'http://www.mediawiki.org/wiki/Extension:MakeBot',
 27+ 'description' => 'Special page allows local bureaucrats to grant and revoke bot permissions',
 28+ );
 29+
 30+ /**
 31+ * Determines who can use the extension; as a default, bureaucrats are permitted
 32+ */
 33+ $wgGroupPermissions['bureaucrat']['makevalidate'] = true;
 34+
 35+ /**
 36+ * Toggles whether or not a bot flag can be given to a user who is also a sysop or bureaucrat
 37+ */
 38+ $wgMakeBotPrivileged = false;
 39+
 40+ /**
 41+ * Register the special page
 42+ */
 43+ $wgAutoloadClasses['Makevalidate'] = dirname( __FILE__ ) . '/Makevalidate.class.php';
 44+ $wgSpecialPages['Makevalidate'] = 'Makevalidate';
 45+
 46+ /**
 47+ * Populate the message cache and set up the auditing
 48+ */
 49+ function efMakeValidate() {
 50+ global $wgMessageCache, $wgLogTypes, $wgLogNames, $wgLogHeaders, $wgLogActions;
 51+ require_once( dirname( __FILE__ ) . '/Makevalidate.i18n.php' );
 52+ foreach( efMakeValidateMessages() as $lang => $messages )
 53+ $wgMessageCache->addMessages( $messages, $lang );
 54+ $wgLogTypes[] = 'validate';
 55+ $wgLogNames['validate'] = 'makevalidate-logpage';
 56+ $wgLogHeaders['validate'] = 'makevalidate-logpagetext';
 57+ $wgLogActions['validate/grant'] = 'makevalidate-logentrygrant';
 58+ $wgLogActions['validate/revoke'] = 'makevalidate-logentryrevoke';
 59+ }
 60+
 61+} else {
 62+ echo( "This file is an extension to the MediaWiki software and cannot be used standalone.\n" );
 63+ exit( 1 );
 64+}
 65+
 66+?>
Index: branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevs.php
@@ -0,0 +1,766 @@
 2+<?
 3+#(c) Joerg Baach, Aaron Schulz 2007 GPL
 4+/*
 5+Possible Hooks
 6+--------------
 7+
 8+'BeforePageDisplay': Called just before outputting a page (all kinds of,
 9+ articles, special, history, preview, diff, edit, ...)
 10+ Can be used to set custom CSS/JS
 11+$out: OutputPage object
 12+
 13+'OutputPageBeforeHTML': a page has been processed by the parser and
 14+the resulting HTML is about to be displayed.
 15+$parserOutput: the parserOutput (object) that corresponds to the page
 16+$text: the text that will be displayed, in HTML (string)
 17+*/
 18+
 19+if ( !defined( 'MEDIAWIKI' ) ) {
 20+ echo "FlaggedRevs extension\n";
 21+ exit( 1 );
 22+}
 23+
 24+$wgExtensionFunctions[] = 'efLoadReviewMessages';
 25+
 26+# Internationilization
 27+function efLoadReviewMessages() {
 28+ global $wgMessageCache, $RevisionreviewMessages;
 29+ require( dirname( __FILE__ ) . '/FlaggedRevsPage.i18n.php' );
 30+ foreach ( $RevisionreviewMessages as $lang => $langMessages ) {
 31+ $wgMessageCache->addMessages( $langMessages, $lang );
 32+ }
 33+}
 34+
 35+# Revision tagging can slow development...
 36+# For example, the main user base may become complacent,
 37+# treating flagged pages as "done",
 38+# or just be too damn lazy to always click "current".
 39+# We may just want non-user visitors to see reviewd pages by default.
 40+$wgFlaggedRevsAnonOnly = true;
 41+# Can users make comments that will show up below flagged revisions?
 42+$wgFlaggedRevComments = true;
 43+# How long to cache stable versions
 44+$wgFlaggedRevsExpire = 7 * 24 * 3600;
 45+
 46+$wgAvailableRights[] = 'review';
 47+# Define our reviewer class
 48+$wgGroupPermissions['reviewer']['rollback'] = true;
 49+$wgGroupPermissions['reviewer']['patrol'] = true;
 50+$wgGroupPermissions['reviewer']['review'] = true;
 51+
 52+# Add review log
 53+$wgLogTypes[] = 'review';
 54+$wgLogNames['review'] = 'review-logpage';
 55+$wgLogHeaders['review'] = 'review-logpagetext';
 56+$wgLogActions['review/approve'] = 'review-logentrygrant';
 57+$wgLogActions['review/unapprove'] = 'review-logentryrevoke';
 58+
 59+class FlaggedRevs {
 60+ /* 50MB allows fixing those huge pages */
 61+ const MAX_INCLUDE_SIZE = 50000000;
 62+
 63+ function __construct() {
 64+ $this->dimensions = array( 'acc' => array( 0=>'acc-0',
 65+ 1=>'acc-1',
 66+ 2=>'acc-2',
 67+ 3=>'acc-3'),
 68+ 'depth' => array( 0=>'depth-0',
 69+ 1=>'depth-1',
 70+ 2=>'depth-2',
 71+ 3=>'depth-3'),
 72+ 'style' => array( 0=>'style-0',
 73+ 1=>'style-1',
 74+ 2=>'style-2',
 75+ 3=>'style-3') );
 76+ }
 77+
 78+ function pageOverride() {
 79+ global $wgFlaggedRevsAnonOnly, $wgUser;
 80+ return !( $wgFlaggedRevsAnonOnly && !$wgUser->isAnon() );
 81+ }
 82+
 83+ function getFlaggedRevision( $rev_id ) {
 84+ $db = wfGetDB( DB_SLAVE );
 85+ // select a row, this should be unique
 86+ $result = $db->select( 'flaggedrevs', array('*'), array('fr_rev_id' => $rev_id) );
 87+ if( $row = $db->fetchObject($result) ) {
 88+ return $row;
 89+ }
 90+ return NULL;
 91+ }
 92+
 93+ function getFlaggedRevText( $rev_id ) {
 94+ $db = wfGetDB( DB_SLAVE );
 95+ // select a row, this should be unique
 96+ $result = $db->select( 'flaggedtext', array('ft_text'), array('ft_rev_id' => $rev_id) );
 97+ if( $row = $db->fetchObject($result) ) {
 98+ return $row->ft_text;
 99+ }
 100+ return NULL;
 101+ }
 102+
 103+ /**
 104+ * @param string $text
 105+ * @returns string
 106+ * All included pages are expanded out to keep this text frozen
 107+ */
 108+ function expandText( $text ) {
 109+ global $wgParser, $wgTitle;
 110+ // Do not treat this as paring an article on normal view
 111+ // enter the title object as wgTitle
 112+ $options = new ParserOptions;
 113+ $options->setRemoveComments( true );
 114+ $options->setMaxIncludeSize( self::MAX_INCLUDE_SIZE );
 115+ $output = $wgParser->preprocess( $text, $wgTitle, $options );
 116+ return $output;
 117+ }
 118+
 119+ function getFlagsForRevision( $rev_id ) {
 120+ // Set default blank flags
 121+ $flags = array( 'acc' => 0, 'depth' => 0, 'style' => 0 );
 122+
 123+ $db = wfGetDB( DB_SLAVE );
 124+ // select a row, this should be unique
 125+ $result = $db->select( 'flaggedrevs', array('*'), array('fr_rev_id' => $rev_id) );
 126+ if( $row = $db->fetchObject($result) ) {
 127+ $flags = array( 'acc' => $row->fr_acc, 'depth' => $row->fr_dep, 'style' => $row->fr_sty );
 128+ }
 129+ return $flags;
 130+ }
 131+
 132+ function getLatestFlaggedRev( $page_id ) {
 133+ wfProfileIn( __METHOD__ );
 134+
 135+ $db = wfGetDB( DB_SLAVE );
 136+ // Skip deleted revisions
 137+ $result = $db->select(
 138+ array('flaggedrevs', 'revision'),
 139+ array('*'),
 140+ array( 'fr_page_id' => $page_id, 'rev_id = fr_rev_id', 'rev_deleted = 0'),
 141+ __METHOD__ ,
 142+ array('ORDER BY' => 'fr_rev_id DESC') );
 143+ // Sorted from highest to lowest, so just take the first one if any
 144+ if ( $row = $db->fetchObject( $result ) ) {
 145+ return $row;
 146+ }
 147+ return NULL;
 148+ }
 149+
 150+ function getReviewedRevs( $page_id ) {
 151+ wfProfileIn( __METHOD__ );
 152+
 153+ $db = wfGetDB( DB_SLAVE );
 154+ $rows = array();
 155+ // Skip deleted revisions
 156+ $result = $db->select(
 157+ array('flaggedrevs', 'revision'),
 158+ array('*'),
 159+ array( 'fr_page_id' => $page_id, 'rev_id = fr_rev_id', 'rev_deleted = 0'),
 160+ __METHOD__ ,
 161+ array('ORDER BY' => 'fr_rev_id DESC') );
 162+ while ( $row = $db->fetchObject( $result ) ) {
 163+ $rows[] = $row;
 164+ }
 165+ return $rows;
 166+ }
 167+
 168+ function getUnreviewedRevCount( $page_id, $from_rev ) {
 169+ wfProfileIn( __METHOD__ );
 170+
 171+ $db = wfGetDB( DB_SLAVE );
 172+ $result = $db->select(
 173+ array('revision'),
 174+ array('rev_page'),
 175+ array( 'rev_page' => $page_id, "rev_id > $from_rev" ),
 176+ __METHOD__ ,
 177+ array('ORDER BY' => 'rev_id DESC') );
 178+ // Return count of revisions
 179+ return $db->numRows($result);
 180+ }
 181+
 182+ function parseStableText( $title, $text, $id=NULL, $options, $returnHTML=true ) {
 183+ global $wgUser, $wgParser, $wgUploadDirectory, $wgUseSharedUploads, $wgUploadPath;
 184+ # hack...temporarily change image directories
 185+ # There is no nice option to set this for each parse.
 186+ # This lets the parser know where to look...
 187+ $uploadPath = $wgUploadPath;
 188+ $uploadDir = $wgUploadDirectory;
 189+ $useSharedUploads = $wgUseSharedUploads;
 190+ # Stable thumbnails need to have the right path
 191+ $wgUploadPath = ($wgUploadPath) ? "{$uploadPath}/stable" : false;
 192+ # Create <img> tags with the right url
 193+ $wgUploadDirectory = "{$uploadDir}/stable";
 194+ # Stable images are never stored at commons
 195+ $wgUseSharedUploads = false;
 196+
 197+ $options->setTidy(true);
 198+ # Don't show section-edit links
 199+ # They can be old and misleading
 200+ $options->setEditSection(false);
 201+ # Parse the new body, wikitext -> html
 202+ $parserOut = $wgParser->parse( $text, $title, $options, true, true, $id );
 203+ if ( !$returnHTML )
 204+ return $parserOut;
 205+
 206+ $HTMLout = $parserOut->getText();
 207+ # hack...
 208+ $HTMLout = $this->proportionalImgScaling($HTMLout);
 209+
 210+ # Reset our image directories
 211+ $wgUploadPath = $uploadPath;
 212+ $wgUploadDirectory = $uploadDir;
 213+ $wgUseSharedUploads = $useSharedUploads;
 214+
 215+ return $HTMLout;
 216+ }
 217+
 218+ function proportionalImgScaling( $text ) {
 219+ # goddamn hack...
 220+ # Thumbnails are stored based on width, don't do any unscaled resizing
 221+ # MW will add height/width based on the metadata in the db for the current image
 222+ $text = preg_replace( '/(<img[^<>]+ )height="\d+" ([^<>]+>)/i','$1$2', $text);
 223+ return $text;
 224+ }
 225+
 226+ function setPageContent( &$out ) {
 227+ global $wgArticle, $wgRequest, $wgTitle, $wgOut, $action;
 228+ // Only trigger on article view, not for protect/delete/hist
 229+ // Talk pages cannot be validated
 230+ if( !$wgArticle || !$wgTitle->isContentPage() || $action !='view' )
 231+ return;
 232+ // Find out revision id
 233+ $revid = ( $wgArticle->mRevision ) ? $wgArticle->mRevision->mId : $wgArticle->getLatest();
 234+ // Grab the ratings for this revision if any
 235+ if( !$revid ) return;
 236+ $visible_id = $revid;
 237+
 238+ // Set new body html text as that of now
 239+ $flaghtml = ''; $newbodytext = $out->mBodytext;
 240+ // Check the newest stable version
 241+ $top_frev = $this->getLatestFlaggedRev( $wgArticle->getId() );
 242+ if( $wgRequest->getVal('diff') ) {
 243+ // Do not clutter up diffs any further...
 244+ } else if( $top_frev ) {
 245+ global $wgParser, $wgLang;
 246+ // Parse the timestamp
 247+ $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $top_frev->fr_timestamp), true );
 248+ // Grab the flags
 249+ $flags = $this->getFlagsForRevision( $top_frev->fr_rev_id );
 250+ # Looking at some specific old rev or if flagged revs override only for anons
 251+ if( $wgRequest->getVal('oldid') || !$this->pageOverride() ) {
 252+ if( $revid==$top_frev->rev_id ) {
 253+ $flaghtml = wfMsgExt('revreview-isnewest', array('parse'),$time);
 254+ } else {
 255+ # Our compare link should have a reasonable time-ordered old->new combination
 256+ $oldid = ($revid > $top_frev->fr_rev_id) ? $top_frev->fr_rev_id : $revid;
 257+ $diff = ($revid > $top_frev->fr_rev_id) ? $revid : $top_frev->fr_rev_id;
 258+ $flaghtml = wfMsgExt('revreview-newest', array('parse'), $top_frev->fr_rev_id, $oldid, $diff, $time );
 259+ }
 260+ } # Viewing the page normally: override the page
 261+ else {
 262+ global $wgUser;
 263+ # We will be looking at the reviewed revision...
 264+ $visible_id = $top_frev->fr_rev_id;
 265+ $revs_since = $this->getUnreviewedRevCount( $wgArticle->getId(), $visible_id );
 266+ $flaghtml = wfMsgExt('revreview-replaced', array('parse'), $visible_id, $wgArticle->getLatest(), $revs_since, $time );
 267+ $newbodytext = NULL;
 268+ # Try the stable cache
 269+ $newbodytext = $this->getPageCache( $wgArticle );
 270+ # If no cache is available, get the text and parse it
 271+ if ( is_null($newbodytext) ) {
 272+ $text = $this->getFlaggedRevText( $visible_id );
 273+ # For anons, use standard prefs, for users, get theirs
 274+ $options = ParserOptions::newFromUser($wgUser);
 275+ # Parsing this text is kind of funky...
 276+ $newbodytext = $this->parseStableText( $wgTitle, $text, $visible_id, $options );
 277+ # Update the general cache for non-users
 278+ $this->updatePageCache( $wgArticle, $newbodytext );
 279+ }
 280+ }
 281+ // Construct some tagging
 282+ $flaghtml .= "<table align='center' cellspadding=\'0\'><tr>";
 283+ foreach ( $this->dimensions as $quality => $value ) {
 284+ $value = wfMsgHtml('revreview-' . $this->dimensions[$quality][$flags[$quality]]);
 285+ $flaghtml .= "<td>&nbsp;<strong>" . wfMsgHtml("revreview-$quality") . "</strong>: $value&nbsp;</td>\n";
 286+ }
 287+ $flaghtml .= '</tr></table>';
 288+ // Should use CSS?
 289+ $flaghtml = "<small>$flaghtml</small>";
 290+
 291+ // Set the new body HTML, place a tag on top
 292+ $out->mBodytext = '<div class="mw-warning plainlinks">' . $flaghtml . '</div>' . $newbodytext;
 293+ // Add any notes at the bottom
 294+ $this->addReviewNotes( $top_frev );
 295+ } else {
 296+ $flaghtml = wfMsgExt('revreview-noflagged', array('parse'));
 297+ $out->mBodytext = '<div class="mw-warning plainlinks">' . $flaghtml . '</div>' . $out->mBodytext;
 298+ }
 299+ // Override our reference ID for permalink/citation hooks
 300+ $wgArticle->mRevision = Revision::newFromId( $visible_id );
 301+ // Show review links for the VISIBLE revision
 302+ // We cannot review deleted revisions
 303+ if( is_object($wgArticle->mRevision) && $wgArticle->mRevision->mDeleted ) return;
 304+ // Add quick review links IF we did not override, otherwise, they might
 305+ // review a revision that parses out newer templates/images than what they say
 306+ // Note: overrides are never done when viewing with "oldid="
 307+ if( $visible_id==$revid || !$this->pageOverride() ) {
 308+ $this->addQuickReview( $visible_id, false, $out );
 309+ }
 310+ }
 311+
 312+ function addToEditView( &$editform ) {
 313+ global $wgRequest, $wgTitle, $wgOut;
 314+ // Talk pages cannot be validated
 315+ if( !$editform->mArticle || !$wgTitle->isContentPage() )
 316+ return;
 317+ // Find out revision id
 318+ if( $editform->mArticle->mRevision )
 319+ $revid = $editform->mArticle->mRevision->mId;
 320+ else
 321+ $revid = $editform->mArticle->getLatest();
 322+ // Grab the ratings for this revision if any
 323+ if( !$revid ) return;
 324+
 325+ // Set new body html text as that of now
 326+ $flaghtml = '';
 327+ // Check the newest stable version
 328+ $top_frev = $this->getLatestFlaggedRev( $editform->mArticle->getId() );
 329+ if( is_object($top_frev) ) {
 330+ global $wgParser, $wgLang;
 331+ $time = $wgLang->timeanddate( wfTimestamp(TS_MW, $top_frev->fr_timestamp), true );
 332+ $flags = $this->getFlagsForRevision( $top_frev->fr_rev_id );
 333+ # Looking at some specific old rev
 334+ if( $wgRequest->getVal('oldid') ) {
 335+ if( $revid==$top_frev->rev_id ) {
 336+ $flaghtml = wfMsgExt('revreview-isnewest', array('parse'),$time);
 337+ } else {
 338+ # Our compare link should have a reasonable time-ordered old->new combination
 339+ $oldid = ($revid > $top_frev->fr_rev_id) ? $top_frev->fr_rev_id : $revid;
 340+ $diff = ($revid > $top_frev->fr_rev_id) ? $revid : $top_frev->fr_rev_id;
 341+ $flaghtml = wfMsgExt('revreview-newest', array('parse'), $top_frev->fr_rev_id, $oldid, $diff, $time );
 342+ }
 343+ } # Editing the page normally
 344+ else {
 345+ if( $revid==$top_frev->rev_id )
 346+ $flaghtml = wfMsgExt('revreview-isnewest', array('parse'), $time);
 347+ else
 348+ $flaghtml = wfMsgExt('revreview-newest', array('parse'), $top_frev->fr_rev_id, $top_frev->fr_rev_id, $revid, $time );
 349+ }
 350+ // Construct some tagging
 351+ $flaghtml .= "<table align='center' cellpadding=\'0\'><tr>";
 352+ foreach ( $this->dimensions as $quality => $value ) {
 353+ $value = wfMsgHtml('revreview-' . $this->dimensions[$quality][$flags[$quality]]);
 354+ $flaghtml .= "<td>&nbsp;<strong>" . wfMsgHtml("revreview-$quality") . "</strong>: $value&nbsp;</td>\n";
 355+ }
 356+ $flaghtml .= '</tr></table>';
 357+ // Should use CSS?
 358+ $flaghtml = "<small>$flaghtml</small>";
 359+ $wgOut->addHTML( '<div class="mw-warning plainlinks">' . $flaghtml . '</div><br/>' );
 360+ }
 361+ }
 362+
 363+ function addToDiff( &$diff, &$oldrev, &$newrev ) {
 364+ $id = $newrev->getId();
 365+ // We cannot review deleted edits
 366+ if( $newrev->mDeleted ) return;
 367+ $this->addQuickReview( $id, true );
 368+ }
 369+
 370+ function setCurrentTab( &$sktmp, &$content_actions ) {
 371+ global $wgRequest, $wgArticle, $action;
 372+ // Only trigger on article view, not for protect/delete/hist
 373+ // Non-content pages cannot be validated
 374+ if( !$wgArticle || !$sktmp->mTitle->exists() || !$sktmp->mTitle->isContentPage() || $action !='view' )
 375+ return;
 376+ // If we are viewing a page normally, and it was overrode
 377+ // change the edit tab to a "current revision" tab
 378+ if( !$wgRequest->getVal('oldid') ) {
 379+ $top_frev = $this->getLatestFlaggedRev( $wgArticle->getId() );
 380+ // Note that revisions may not be set to override for users
 381+ if( is_object($top_frev) && $this->pageOverride() ) {
 382+ # Remove edit option altogether
 383+ unset( $content_actions['edit']);
 384+ unset( $content_actions['viewsource']);
 385+ # Straighten out order
 386+ $new_actions = array(); $counter = 0;
 387+ foreach ( $content_actions as $action => $data ) {
 388+ if( $counter==1 ) {
 389+ # Set current rev tab AFTER the main tab is set
 390+ $new_actions['current'] = array(
 391+ 'class' => '',
 392+ 'text' => wfMsg('currentrev'),
 393+ 'href' => $sktmp->mTitle->getLocalUrl( 'oldid=' . $wgArticle->getLatest() )
 394+ );
 395+ }
 396+ $new_actions[$action] = $data;
 397+ $counter++;
 398+ }
 399+ # Reset static array
 400+ $content_actions = $new_actions;
 401+ }
 402+ }
 403+ }
 404+
 405+ function addToPageHist( &$article ) {
 406+ $this->pageFlaggedRevs = array();
 407+ $rows = $this->getReviewedRevs( $article->getID() );
 408+ if( !$rows ) return;
 409+ foreach( $rows as $row => $data ) {
 410+ $this->pageFlaggedRevs[] = $data->rev_id;
 411+ }
 412+ }
 413+
 414+ function addToHistLine( &$row, &$s ) {
 415+ if( isset($this->pageFlaggedRevs) ) {
 416+ if( in_array( $row->rev_id, $this->pageFlaggedRevs ) )
 417+ $s .= ' <small><strong>' . wfMsgHtml('revreview-hist') . '</strong></small>';
 418+ }
 419+ }
 420+
 421+ function addQuickReview( $id, $ontop=false, &$out=false ) {
 422+ global $wgOut, $wgTitle, $wgUser, $wgScript;
 423+ // We don't want two forms!
 424+ if( isset($this->formCount) && $this->formCount > 0 ) return;
 425+ $this->formCount = 1;
 426+
 427+ if( !$wgUser->isAllowed( 'review' ) ) return;
 428+
 429+ $flags = $this->getFlagsForRevision( $id );
 430+
 431+ $reviewtitle = SpecialPage::getTitleFor( 'Revisionreview' );
 432+ $form = Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
 433+ $form .= "<fieldset><legend>" . wfMsgHtml( 'revreview-flag', $id ) . "</legend>\n";
 434+ $form .= wfHidden( 'title', $reviewtitle->getPrefixedText() );
 435+ $form .= wfHidden( 'target', $wgTitle->getPrefixedText() );
 436+ $form .= wfHidden( 'oldid', $id );
 437+ foreach ( $this->dimensions as $quality => $levels ) {
 438+ $form .= wfMsgHtml("revreview-$quality") . ": <select name='$quality'>\n";
 439+ foreach ( $levels as $idx => $label ) {
 440+ if( $flags[$quality]==$idx )
 441+ $selected = 'selected';
 442+ else
 443+ $selected = '';
 444+ $form .= "<option value='$idx' $selected>" . wfMsgHtml("revreview-$label") . "</option>\n";
 445+ }
 446+ $form .= "</select>\n";
 447+ }
 448+ $form .= Xml::submitButton( wfMsgHtml( 'go' ) ) . "</fieldset>";
 449+ $form .= Xml::closeElement( 'form' );
 450+ // Hacks, to fiddle around with location a bit
 451+ if( $ontop && $out ) {
 452+ $out->mBodytext = $form . '<hr/>' . $out->mBodytext;
 453+ } else if( $ontop ) {
 454+ $wgOut->addHTML( $form );
 455+ } else {
 456+ $wgOut->addHTML( $form );
 457+ }
 458+ }
 459+
 460+ function addReviewNotes( $row, $breakline=true ) {
 461+ global $wgOut, $wgUser, $wgFlaggedRevComments;
 462+
 463+ if( !$row || !$wgFlaggedRevComments) return;
 464+
 465+ $this->skin = $wgUser->getSkin();
 466+ if( $row->fr_comment ) {
 467+ $notes = ($breakline) ? '<hr/><br/>' : '';
 468+ $notes .= '<div class="mw-warning plainlinks">';
 469+ $notes .= wfMsgExt('revreview-note', array('parse'), User::whoIs( $row->fr_user ) );
 470+ $notes .= '<i>' . $this->skin->formatComment( $row->fr_comment ) . '</i></div>';
 471+ $wgOut->addHTML( $notes );
 472+ }
 473+ }
 474+
 475+ /**
 476+ * Get all local image files and generate an array of them
 477+ * @param string $s, wikitext
 478+ * $output array, (string title array, string thumbnail array)
 479+ */
 480+ function findLocalImages( $s ) {
 481+ global $wgUploadPath;
 482+
 483+ $fname = 'findLocalImages';
 484+ $imagelist = array(); $thumblist = array();
 485+
 486+ if( !$s || !strval($s) ) return $imagelist;
 487+
 488+ static $tc = FALSE;
 489+ # the % is needed to support urlencoded titles as well
 490+ if( !$tc ) { $tc = Title::legalChars() . '#%'; }
 491+
 492+ # split the entire text string on occurences of [[
 493+ $a = explode( '[[', $s );
 494+
 495+ # Ignore things that start with colons, they are image links, not images
 496+ $e1_img = "/^([:{$tc}]+)(.+)$/sD";
 497+ # Loop for each link
 498+ for ($k = 0; isset( $a[$k] ); $k++) {
 499+ if( preg_match( $e1_img, $a[$k], $m ) ) {
 500+ # page with normal text or alt of form x or ns:x
 501+ $nt = Title::newFromText( $m[1] );
 502+ $ns = $nt->getNamespace();
 503+ # add if this is an image
 504+ if( $ns == NS_IMAGE ) {
 505+ $imagelist[] = $nt->getPrefixedText();
 506+ }
 507+ $image = $nt->getDBKey();
 508+ # check for data for thumbnails
 509+ $part = array_map( 'trim', explode( '|', $m[2]) );
 510+ foreach( $part as $val ) {
 511+ if( preg_match( '/^([0-9]+)px$/', $val, $n ) ) {
 512+ $width = intval( $n[1] );
 513+ $thumblist[$image] = $width;
 514+ } else if( preg_match( '/^([0-9]+)x([0-9]+)$/', $val, $n ) ) {
 515+ $width = intval( $n[1] );
 516+ $thumblist[$image] = $width;
 517+ }
 518+ }
 519+ }
 520+ }
 521+ return array( $imagelist, $thumblist );
 522+ }
 523+
 524+ /**
 525+ * Showtime! Copy all used images to a stable directory
 526+ * This updates (overwrites) any existing stable images
 527+ * Won't work for sites with unhashed dirs that have subfolders protected
 528+ * The future FileStore migration might effect this, not sure...
 529+ * @param array $imagelist, list of string names
 530+ * $output array, list of string names of images sucessfully cloned
 531+ */
 532+ function makeStableImages( $imagelist ) {
 533+ global $wgUploadDirectory, $wgSharedUploadDirectory;
 534+ // All stable images are local, not shared
 535+ // Otherwise, we could have some nasty cross language/wiki conflicts
 536+ $stableDir = "$wgUploadDirectory/stable";
 537+ // Copy images to stable dir
 538+ $usedimages = array();
 539+ // We need valid input
 540+ if( !is_array($imagelist) ) return $usedimages;
 541+ foreach ( $imagelist as $name ) {
 542+ // We want a clean and consistant title entry
 543+ $nt = Title::newFromText( $name );
 544+ if( is_null($nt) ) {
 545+ // If this title somehow doesn't work, ignore it
 546+ // this shouldn't happen...
 547+ continue;
 548+ }
 549+ $name = $nt->getDBkey();
 550+ $hash = wfGetHashPath($name);
 551+ $path = $wgUploadDirectory . $hash;
 552+ $sharedpath = $wgSharedUploadDirectory . $hash;
 553+ // Try local repository
 554+ if( is_dir($path) ) {
 555+ if( file_exists("{$path}{$name}") ) {
 556+ // Check if our stable dir exists
 557+ // Make it if it doesn't
 558+ if( !is_dir($stableDir . $hash) ) {
 559+ wfMkdirParents($stableDir . $hash);
 560+ }
 561+ copy("{$path}{$name}","{$stableDir}{$hash}{$name}");
 562+ $usedimages[] = $name;
 563+ }
 564+ } // Try shared repository
 565+ else if( is_dir($sharedpath) ) {
 566+ if( file_exists("{$sharedpath}{$name}") ) {
 567+ // Check if our stable dir exists
 568+ // Make it if it doesn't
 569+ if( !is_dir($stableDir . $hash) ) {
 570+ wfMkdirParents($stableDir . $hash);
 571+ }
 572+ copy("{$sharedpath}{$name}","{$stableDir}{$hash}{$name}");
 573+ $usedimages[] = $name;
 574+ }
 575+ }
 576+ }
 577+ return $usedimages;
 578+ }
 579+
 580+ /**
 581+ * Delete an a list of stable image files
 582+ * @param array $imagelist, list of string names
 583+ * $output array, list of string names of images to be deleted
 584+ */
 585+ function deleteStableImages( $imagelist ) {
 586+ global $wgSharedUploadDirectory;
 587+ // All stable images are local, not shared
 588+ // Otherwise, we could have some nasty cross language/wiki conflicts
 589+ $stableDir = "$wgUploadDirectory/stable";
 590+ // Copy images to stable dir
 591+ $deletedimages = array();
 592+ // We need valid input
 593+ if( !is_array($imagelist) ) return $usedimages;
 594+ foreach ( $imagelist as $name ) {
 595+ // We want a clean and consistant title entry
 596+ $nt = Title::newFromText( $name );
 597+ if( is_null($nt) ) {
 598+ // If this title somehow doesn't work, ignore it
 599+ // this shouldn't happen...
 600+ continue;
 601+ }
 602+ $name = $nt->getDBkey();
 603+ $hash = wfGetHashPath($name);
 604+ $path = $stableDir . $hash;
 605+ // Try the stable repository
 606+ if( is_dir($path) ) {
 607+ if( file_exists("{$path}{$name}") ) {
 608+ // Delete!
 609+ delete("{$path}{$name}");
 610+ $deletedimages[] = $name;
 611+ }
 612+ }
 613+ }
 614+ return $deletedimages;
 615+ }
 616+
 617+ /**
 618+ * Delete an a list of stable image thumbnails
 619+ * New thumbnails don't normally override old ones, causing outdated images
 620+ * This allows for tagged revisions to be re-reviewed with newer images
 621+ * @param array $imagelist, list of string names
 622+ * $output array, list of string names of images to be deleted
 623+ */
 624+ function purgeStableThumbnails( $thumblist ) {
 625+ global $wgUploadDirectory, $wgUseImageResize;
 626+ // Are thumbs even enabled?
 627+ if ( !$wgUseImageResize ) return true;
 628+ // We need valid input
 629+ if( !is_array($thumblist) ) return false;
 630+ foreach ( $thumblist as $name => $width ) {
 631+ $thumburl = "{$wgUploadDirectory}/stable/thumb" . wfGetHashPath( $name, false ) . "$name/". $width."px-".$name;
 632+ if( file_exists($thumburl) ) {
 633+ unlink($thumburl);
 634+ }
 635+ }
 636+ return true;
 637+ }
 638+
 639+ /**
 640+ * Update the stable image usage table
 641+ * Add some images if not redundant
 642+ * @param array $imagelist, list of string names
 643+ * $output bool, on succeed
 644+ */
 645+ function insertStableImages( $revid, $imagelist ) {
 646+ wfProfileIn( __METHOD__ );
 647+
 648+ if( !is_array($imagelist) ) return false;
 649+
 650+ $db = wfGetDB( DB_MASTER );
 651+ foreach( $imagelist as $name ) {
 652+ // We want a clean and consistant title entry
 653+ $nt = Title::newFromText( $name );
 654+ if( is_null($nt) ) {
 655+ // If this title somehow doesn't work, ignore it
 656+ // this shouldn't happen...
 657+ continue;
 658+ }
 659+ $imagename = $nt->getDBkey();
 660+ // Add image and the revision that uses it
 661+ $set = array('fi_rev_id' => $revid, 'fi_name' => $imagename);
 662+ // Add entries or replace any that have the same rev_id
 663+ $db->replace( 'flaggedimages', array( array('fi_rev_id', 'fi_name') ), $set, __METHOD__ );
 664+ }
 665+ return true;
 666+ }
 667+
 668+ /**
 669+ * Update the stable image usage table
 670+ * Clean out unused images if needed
 671+ * @param array $imagelist, list of string names
 672+ * $output bool, on succeed
 673+ */
 674+ function removeStableImages( $revid, $imagelist ) {
 675+ wfProfileIn( __METHOD__ );
 676+
 677+ if( !is_array($imagelist) ) return false;
 678+ $unusedimages = array();
 679+ $db = wfGetDB( DB_MASTER );
 680+ foreach( $imagelist as $name ) {
 681+ // We want a clean and consistant title entry
 682+ $nt = Title::newFromText( $name );
 683+ if( is_null($nt) ) {
 684+ // If this title somehow doesn't work, ignore it
 685+ // this shouldn't happen...
 686+ continue;
 687+ }
 688+ $imagename = $nt->getDBkey();
 689+ $where = array( 'fi_rev_id' => $revid, 'fi_name' => $imagename );
 690+ // See how many revisions use this image total...
 691+ $result = $db->select( 'flaggedimages', array('fi_id'), array( 'fi_name' => $imagename ) );
 692+ // If only one, then delete the image
 693+ // since it's about to be remove from that one
 694+ if( $db->numRows($result)==1 ) {
 695+ $unusedimages[] = $imagename;
 696+ }
 697+ // Clear out this revision's entry
 698+ $db->delete( 'flaggedimages', $where );
 699+ }
 700+ $this->deleteStableImages( $unusedimages );
 701+ return true;
 702+ }
 703+
 704+ function getPageCache( $article ) {
 705+ global $wgUser, $wgFlaggedRevsExpire;
 706+
 707+ wfProfileIn( __METHOD__ );
 708+
 709+ // Make sure it is valid
 710+ if ( !$article || !$article->getId() ) return NULL;
 711+ $cachekey = ParserCache::getKey( $article, $wgUser );
 712+
 713+ $db = wfGetDB( DB_SLAVE );
 714+ $cutoff = $db->timestamp( time() - $wgFlaggedRevsExpire );
 715+ // Replace the page cache if it is out of date
 716+ $result = $db->select(
 717+ array('flaggedcache'),
 718+ array('fc_cache'),
 719+ array('fc_key' => $cachekey, 'fc_date >= ' . $article->getTouched(), 'fc_date >= ' . $cutoff ),
 720+ __METHOD__);
 721+ if ( $row = $db->fetchObject($result) ) {
 722+ return $row->fc_cache;
 723+ }
 724+ return NULL;
 725+ }
 726+
 727+ function updatePageCache( $article, $value=NULL ) {
 728+ global $wgUser;
 729+ wfProfileIn( __METHOD__ );
 730+
 731+ // Make sure it is valid
 732+ if ( is_null($value) || !$article || !$article->getId() ) return false;
 733+ $cachekey = ParserCache::getKey( $article, $wgUser );
 734+ // Add cache mark
 735+ $timestamp = wfTimestampNow();
 736+ $value .= "\n<!-- Saved in stable version parser cache with key $cachekey and timestamp $timestamp -->";
 737+
 738+ $dbw = wfGetDB( DB_MASTER );
 739+ // Replace the page cache if it is out of date
 740+ $dbw->replace('flaggedcache',
 741+ array('fc_key'),
 742+ array('fc_key' => $cachekey, 'fc_cache' => $value, 'fc_date' => $timestamp),
 743+ __METHOD__);
 744+
 745+ return true;
 746+ }
 747+}
 748+
 749+# Load expert promotion UI
 750+include_once('SpecialMakevalidate.php');
 751+
 752+if( !function_exists( 'extAddSpecialPage' ) ) {
 753+ require( dirname(__FILE__) . '/../ExtensionFunctions.php' );
 754+}
 755+extAddSpecialPage( dirname(__FILE__) . '/FlaggedRevsPage.body.php', 'Revisionreview', 'Revisionreview' );
 756+
 757+# Load approve/unapprove UI
 758+$wgHooks['LoadAllMessages'][] = 'efLoadReviewMessages';
 759+
 760+$flaggedrevs = new FlaggedRevs();
 761+$wgHooks['BeforePageDisplay'][] = array($flaggedrevs, 'setPageContent');
 762+$wgHooks['DiffViewHeader'][] = array($flaggedrevs, 'addToDiff');
 763+$wgHooks['EditPage::showEditForm:initial'][] = array($flaggedrevs, 'addToEditView');
 764+$wgHooks['SkinTemplateTabs'][] = array($flaggedrevs, 'setCurrentTab');
 765+$wgHooks['PageHistoryBeforeList'][] = array($flaggedrevs, 'addToPageHist');
 766+$wgHooks['PageHistoryLineEnding'][] = array($flaggedrevs, 'addToHistLine');
 767+?>
Property changes on: branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevs.php
___________________________________________________________________
Added: svn:eol-style
1768 + native
Index: branches/jhb/phase3/extensions/FlaggedRevs/Makevalidate.class.php
@@ -0,0 +1,172 @@
 2+<?php
 3+
 4+global $IP;
 5+require_once( "$IP/includes/LogPage.php" );
 6+require_once( "$IP/includes/SpecialLog.php" );
 7+
 8+class MakeValidate extends SpecialPage {
 9+
 10+ var $target = '';
 11+
 12+ /**
 13+ * Constructor
 14+ */
 15+ function MakeValidate() {
 16+ SpecialPage::SpecialPage( 'Makevalidate', 'makevalidate' );
 17+ }
 18+
 19+ /**
 20+ * Main execution function
 21+ * @param $par Parameters passed to the page
 22+ */
 23+ function execute( $par ) {
 24+ global $wgRequest, $wgOut, $wgmakevalidatePrivileged, $wgUser;
 25+
 26+ if( !$wgUser->isAllowed( 'makevalidate' ) ) {
 27+ $wgOut->permissionRequired( 'makevalidate' );
 28+ return;
 29+ }
 30+
 31+ $this->setHeaders();
 32+
 33+ $this->target = $par
 34+ ? $par
 35+ : $wgRequest->getText( 'username', '' );
 36+
 37+ $wgOut->addWikiText( wfMsgNoTrans( 'makevalidate-header' ) );
 38+ $wgOut->addHtml( $this->makeSearchForm() );
 39+
 40+ if( $this->target != '' ) {
 41+ $wgOut->addHtml( wfElement( 'p', NULL, NULL ) );
 42+ $user = User::newFromName( $this->target );
 43+ if( is_object( $user ) && !is_null( $user ) ) {
 44+ global $wgVersion;
 45+ if( version_compare( $wgVersion, '1.9alpha' ) < 0 ) {
 46+ $user->loadFromDatabase();
 47+ } else {
 48+ $user->load();
 49+ }
 50+ # Valid username, check existence
 51+ if( $user->getID() ) {
 52+ if( $wgRequest->getCheck( 'dosearch' ) || !$wgRequest->wasPosted() || !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ), 'makevalidate' ) ) {
 53+ # Exists, check reviewerness
 54+ if( in_array( 'reviewer', $user->mGroups ) ) {
 55+ # Has a reviewer flag
 56+ $wgOut->addWikiText( wfMsg( 'makevalidate-isvalidator', $user->getName() ) );
 57+ $wgOut->addHtml( $this->makeGrantForm( MW_MAKEVALIDATE_REVOKE ) );
 58+ } else {
 59+ # Not a reviewer; show the grant form
 60+ $wgOut->addHtml( $this->makeGrantForm( MW_MAKEVALIDATE_GRANT ) );
 61+ }
 62+ } elseif( $wgRequest->getCheck( 'grant' ) ) {
 63+ # Grant the flag
 64+ $user->addGroup( 'reviewer' );
 65+ $this->addLogItem( 'grant', $user, trim( $wgRequest->getText( 'comment' ) ) );
 66+ $wgOut->addWikiText( wfMsg( 'makevalidate-granted', $user->getName() ) );
 67+ } elseif( $wgRequest->getCheck( 'revoke' ) ) {
 68+ # Revoke the flag
 69+ $user->removeGroup( 'reviewer' );
 70+ $this->addLogItem( 'revoke', $user, trim( $wgRequest->getText( 'comment' ) ) );
 71+ $wgOut->addWikiText( wfMsg( 'makevalidate-revoked', $user->getName() ) );
 72+ }
 73+ # Show log entries
 74+ $this->showLogEntries( $user );
 75+ } else {
 76+ # Doesn't exist
 77+ $wgOut->addWikiText( wfMsg( 'nosuchusershort', htmlspecialchars( $this->target ) ) );
 78+ }
 79+ } else {
 80+ # Invalid username
 81+ $wgOut->addWikiText( wfMsg( 'noname' ) );
 82+ }
 83+ }
 84+
 85+ }
 86+
 87+ /**
 88+ * Produce a form to allow for entering a username
 89+ * @return string
 90+ */
 91+ function makeSearchForm() {
 92+ $thisTitle = Title::makeTitle( NS_SPECIAL, $this->getName() );
 93+ $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) );
 94+ $form .= wfElement( 'label', array( 'for' => 'username' ), wfMsg( 'makevalidate-username' ) ) . ' ';
 95+ $form .= wfElement( 'input', array( 'type' => 'text', 'name' => 'username', 'id' => 'username', 'value' => $this->target ) ) . ' ';
 96+ $form .= wfElement( 'input', array( 'type' => 'submit', 'name' => 'dosearch', 'value' => wfMsg( 'makevalidate-search' ) ) );
 97+ $form .= wfCloseElement( 'form' );
 98+ return $form;
 99+ }
 100+
 101+ /**
 102+ * Produce a form to allow granting or revocation of the flag
 103+ * @param $type Either MW_makevalidate_GRANT or MW_makevalidate_REVOKE
 104+ * where the trailing name refers to what's enabled
 105+ * @return string
 106+ */
 107+ function makeGrantForm( $type ) {
 108+ global $wgUser;
 109+ $thisTitle = Title::makeTitle( NS_SPECIAL, $this->getName() );
 110+ if( $type == MW_MAKEVALIDATE_GRANT ) {
 111+ $grant = true;
 112+ $revoke = false;
 113+ } else {
 114+ $grant = false;
 115+ $revoke = true;
 116+ }
 117+
 118+ # Start the table
 119+ $form = wfOpenElement( 'form', array( 'method' => 'post', 'action' => $thisTitle->getLocalUrl() ) );
 120+ $form .= wfOpenElement( 'table' ) . wfOpenElement( 'tr' );
 121+ # Grant/revoke buttons
 122+ $form .= wfElement( 'td', array( 'align' => 'right' ), wfMsg( 'makevalidate-change' ) );
 123+ $form .= wfOpenElement( 'td' );
 124+ foreach( array( 'grant', 'revoke' ) as $button ) {
 125+ $attribs = array( 'type' => 'submit', 'name' => $button, 'value' => wfMsg( 'makevalidate-' . $button ) );
 126+ if( !$$button )
 127+ $attribs['disabled'] = 'disabled';
 128+ $form .= wfElement( 'input', $attribs );
 129+ }
 130+ $form .= wfCloseElement( 'td' ) . wfCloseElement( 'tr' );
 131+ # Comment field
 132+ $form .= wfOpenElement( 'td', array( 'align' => 'right' ) );
 133+ $form .= wfElement( 'label', array( 'for' => 'comment' ), wfMsg( 'makevalidate-comment' ) );
 134+ $form .= wfOpenElement( 'td' );
 135+ $form .= wfElement( 'input', array( 'type' => 'text', 'name' => 'comment', 'id' => 'comment', 'size' => 45 ) );
 136+ $form .= wfCloseElement( 'td' ) . wfCloseElement( 'tr' );
 137+ # End table
 138+ $form .= wfCloseElement( 'table' );
 139+ # Username
 140+ $form .= wfElement( 'input', array( 'type' => 'hidden', 'name' => 'username', 'value' => $this->target ) );
 141+ # Edit token
 142+ $form .= wfElement( 'input', array( 'type' => 'hidden', 'name' => 'token', 'value' => $wgUser->editToken( 'makevalidate' ) ) );
 143+ $form .= wfCloseElement( 'form' );
 144+ return $form;
 145+ }
 146+
 147+ /**
 148+ * Add logging entries for the specified action
 149+ * @param $type Either grant or revoke
 150+ * @param $target User receiving the action
 151+ * @param $comment Comment for the log item
 152+ */
 153+ function addLogItem( $type, &$target, $comment = '' ) {
 154+ $log = new LogPage( 'validate' );
 155+ $targetPage = $target->getUserPage();
 156+ $log->addEntry( $type, $targetPage, $comment );
 157+ }
 158+
 159+ /**
 160+ * Show the bot status log entries for the specified user
 161+ * @param $user User to show the log for
 162+ */
 163+ function showLogEntries( &$user ) {
 164+ global $wgOut;
 165+ $title = $user->getUserPage();
 166+ $wgOut->addHtml( wfElement( 'h2', NULL, htmlspecialchars( LogPage::logName( 'validate' ) ) ) );
 167+ $logViewer = new LogViewer( new LogReader( new FauxRequest( array( 'page' => $title->getPrefixedText(), 'type' => 'validate' ) ) ) );
 168+ $logViewer->showList( $wgOut );
 169+ }
 170+
 171+}
 172+
 173+?>
Index: branches/jhb/phase3/extensions/FlaggedRevs/FlaggedRevs.sql
@@ -0,0 +1,54 @@
 2+-- (c) Joerg Baach, Aaron Schulz, 2007
 3+
 4+-- Table structure for table `revisiontags`
 5+-- Replace /*$wgDBprefix*/ with the proper prefix
 6+
 7+-- This stores expanded revision wikitext caches
 8+-- along with rating/user/notes data
 9+CREATE TABLE /*$wgDBprefix*/flaggedrevs (
 10+ fr_id int(10) NOT NULL auto_increment,
 11+ fr_page_id int(10) NOT NULL,
 12+ fr_rev_id int(10) NOT NULL,
 13+ fr_acc int(2) NOT NULL,
 14+ fr_dep int(2) NOT NULL,
 15+ fr_sty int(2) NOT NULL,
 16+ fr_user int(5) NOT NULL,
 17+ fr_timestamp char(14) NOT NULL,
 18+ fr_comment mediumblob default NULL,
 19+
 20+ PRIMARY KEY fr_rev_id (fr_rev_id),
 21+ UNIQUE KEY (fr_id),
 22+ INDEX fr_page_rev (fr_page_id,fr_rev_id),
 23+ INDEX fr_acc_dep_sty (fr_acc,fr_dep,fr_sty)
 24+) TYPE=InnoDB;
 25+
 26+-- This stores expanded (transclusions resolved) revision text
 27+CREATE TABLE /*$wgDBprefix*/flaggedtext (
 28+ ft_id int(10) NOT NULL auto_increment,
 29+ ft_rev_id int(10) NOT NULL,
 30+ ft_text mediumblob NOT NULL default '',
 31+
 32+ PRIMARY KEY ft_id (ft_id),
 33+ UNIQUE KEY ft_rev_id (ft_rev_id)
 34+) TYPE=InnoDB;
 35+
 36+-- This stores image usage for the stable image directory
 37+-- Used for scripts that clear out unused images
 38+CREATE TABLE /*$wgDBprefix*/flaggedimages (
 39+ fi_id int(10) NOT NULL auto_increment,
 40+ fi_name varchar(255) NOT NULL,
 41+ fi_rev_id int(10) NOT NULL,
 42+
 43+ PRIMARY KEY (fi_name,fi_rev_id),
 44+ UNIQUE KEY (fi_id),
 45+ INDEX fi_name (fi_name)
 46+) TYPE=InnoDB;
 47+
 48+-- This stores cached text for page view
 49+CREATE TABLE /*$wgDBprefix*/flaggedcache (
 50+ fc_key char(255) binary NOT NULL default '',
 51+ fc_cache mediumblob NOT NULL default '',
 52+ fc_date char(14) NOT NULL,
 53+
 54+ PRIMARY KEY fc_key (fc_key)
 55+) TYPE=InnoDB;
\ No newline at end of file