]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/specials/SpecialRevisiondelete.php
Mediawiki 1.15.2
[autoinstalls/mediawiki.git] / includes / specials / SpecialRevisiondelete.php
1 <?php
2 /**
3  * Special page allowing users with the appropriate permissions to view
4  * and hide revisions. Log items can also be hidden.
5  *
6  * @file
7  * @ingroup SpecialPage
8  */
9
10 class SpecialRevisionDelete extends UnlistedSpecialPage {
11
12         public function __construct() {
13                 parent::__construct( 'Revisiondelete', 'deleterevision' );
14                 $this->includable( false );
15         }
16
17         public function execute( $par ) {
18                 global $wgOut, $wgUser, $wgRequest;
19                 if( wfReadOnly() ) {
20                         $wgOut->readOnlyPage();
21                         return;
22                 }
23                 if( !$wgUser->isAllowed( 'deleterevision' ) ) {
24                         $wgOut->permissionRequired( 'deleterevision' );
25                         return;
26                 }
27                 $this->skin =& $wgUser->getSkin();
28                 # Set title and such
29                 $this->setHeaders();
30                 $this->outputHeader();
31                 $this->wasPosted = $wgRequest->wasPosted();
32                 # Handle our many different possible input types
33                 $this->target = $wgRequest->getText( 'target' );
34                 $this->oldids = $wgRequest->getArray( 'oldid' );
35                 $this->artimestamps = $wgRequest->getArray( 'artimestamp' );
36                 $this->logids = $wgRequest->getArray( 'logid' );
37                 $this->oldimgs = $wgRequest->getArray( 'oldimage' );
38                 $this->fileids = $wgRequest->getArray( 'fileid' );
39                 # For reviewing deleted files...
40                 $this->file = $wgRequest->getVal( 'file' );
41                 # Only one target set at a time please!
42                 $i = (bool)$this->file + (bool)$this->oldids + (bool)$this->logids
43                         + (bool)$this->artimestamps + (bool)$this->fileids + (bool)$this->oldimgs;
44                 # No targets?
45                 if( $i == 0 ) {
46                         $wgOut->showErrorPage( 'notargettitle', 'notargettext' );
47                         return;
48                 }
49                 # Too many targets?
50                 if( $i !== 1 ) {
51                         $wgOut->showErrorPage( 'revdelete-toomanytargets-title', 'revdelete-toomanytargets-text' );
52                         return;
53                 }
54                 $this->page = Title::newFromUrl( $this->target );
55                 # If we have revisions, get the title from the first one
56                 # since they should all be from the same page. This allows 
57                 # for more flexibility with page moves...
58                 if( count($this->oldids) > 0 ) {
59                         $rev = Revision::newFromId( $this->oldids[0] );
60                         $this->page = $rev ? $rev->getTitle() : $this->page;
61                 }
62                 # We need a target page!
63                 if( is_null($this->page) ) {
64                         $wgOut->addWikiMsg( 'undelete-header' );
65                         return;
66                 }
67                 # Logs must have a type given
68                 if( $this->logids && !strpos($this->page->getDBKey(),'/') ) {
69                         $wgOut->showErrorPage( 'revdelete-nologtype-title', 'revdelete-nologtype-text' );
70                         return;
71                 }
72                 # For reviewing deleted files...show it now if allowed
73                 if( $this->file ) {
74                         $oimage = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $this->page, $this->file );
75                         $oimage->load();
76                         // Check if user is allowed to see this file
77                         if( !$oimage->userCan(File::DELETED_FILE) ) {
78                                 $wgOut->permissionRequired( 'suppressrevision' );
79                         } else {
80                                 $this->showFile( $this->file );
81                         }
82                         return;
83                 }
84                 # Give a link to the logs/hist for this page
85                 if( !is_null($this->page) && $this->page->getNamespace() > -1 ) {
86                         $links = array();
87
88                         $logtitle = SpecialPage::getTitleFor( 'Log' );
89                         $links[] = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'viewpagelogs' ),
90                                 wfArrayToCGI( array( 'page' => $this->page->getPrefixedUrl() ) ) );
91                         # Give a link to the page history
92                         $links[] = $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml( 'pagehist' ),
93                                 wfArrayToCGI( array( 'action' => 'history' ) ) );
94                         # Link to deleted edits
95                         if( $wgUser->isAllowed('undelete') ) {
96                                 $undelete = SpecialPage::getTitleFor( 'Undelete' );
97                                 $links[] = $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml( 'deletedhist' ),
98                                         wfArrayToCGI( array( 'target' => $this->page->getPrefixedDBkey() ) ) );
99                         }
100                         # Logs themselves don't have histories or archived revisions
101                         $wgOut->setSubtitle( '<p>'.implode($links,' / ').'</p>' );
102                 }
103                 # Lock the operation and the form context
104                 $this->secureOperation();
105                 # Either submit or create our form
106                 if( $this->wasPosted ) {
107                         $this->submit( $wgRequest );
108                 } else if( $this->deleteKey == 'oldid' || $this->deleteKey == 'artimestamp' ) {
109                         $this->showRevs();
110                 } else if( $this->deleteKey == 'fileid' || $this->deleteKey == 'oldimage' ) {
111                         $this->showImages();
112                 } else if( $this->deleteKey == 'logid' ) {
113                         $this->showLogItems();
114                 }
115                 # Show relevant lines from the deletion log. This will show even if said ID
116                 # does not exist...might be helpful
117                 $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
118                 LogEventsList::showLogExtract( $wgOut, 'delete', $this->page->getPrefixedText() );
119                 if( $wgUser->isAllowed( 'suppressionlog' ) ){
120                         $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" );
121                         LogEventsList::showLogExtract( $wgOut, 'suppress', $this->page->getPrefixedText() );
122                 }
123         }
124
125         private function secureOperation() {
126                 global $wgUser;
127                 $this->deleteKey = '';
128                 // At this point, we should only have one of these
129                 if( $this->oldids ) {
130                         $this->revisions = $this->oldids;
131                         $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT );
132                         $this->deleteKey = 'oldid';
133                 } else if( $this->artimestamps ) {
134                         $this->archrevs = $this->artimestamps;
135                         $hide_content_name = array( 'revdelete-hide-text', 'wpHideText', Revision::DELETED_TEXT );
136                         $this->deleteKey = 'artimestamp';
137                 } else if( $this->oldimgs ) {
138                         $this->ofiles = $this->oldimgs;
139                         $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE );
140                         $this->deleteKey = 'oldimage';
141                 } else if( $this->fileids ) {
142                         $this->afiles = $this->fileids;
143                         $hide_content_name = array( 'revdelete-hide-image', 'wpHideImage', File::DELETED_FILE );
144                         $this->deleteKey = 'fileid';
145                 } else if( $this->logids ) {
146                         $this->events = $this->logids;
147                         $hide_content_name = array( 'revdelete-hide-name', 'wpHideName', LogPage::DELETED_ACTION );
148                         $this->deleteKey = 'logid';
149                 }
150                 // Our checkbox messages depends one what we are doing,
151                 // e.g. we don't hide "text" for logs or images
152                 $this->checks = array(
153                         $hide_content_name,
154                         array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ),
155                         array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER )
156                 );
157                 if( $wgUser->isAllowed('suppressrevision') ) {
158                         $this->checks[] = array( 'revdelete-hide-restricted', 'wpHideRestricted', Revision::DELETED_RESTRICTED );
159                 }
160         }
161
162         /**
163          * Show a deleted file version requested by the visitor.
164          */
165         private function showFile( $key ) {
166                 global $wgOut, $wgRequest;
167                 $wgOut->disable();
168
169                 # We mustn't allow the output to be Squid cached, otherwise
170                 # if an admin previews a deleted image, and it's cached, then
171                 # a user without appropriate permissions can toddle off and
172                 # nab the image, and Squid will serve it
173                 $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
174                 $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
175                 $wgRequest->response()->header( 'Pragma: no-cache' );
176
177                 $store = FileStore::get( 'deleted' );
178                 $store->stream( $key );
179         }
180
181         /**
182          * This lets a user set restrictions for live and archived revisions
183          */
184         private function showRevs() {
185                 global $wgOut, $wgUser;
186                 $UserAllowed = true;
187
188                 $count = ($this->deleteKey=='oldid') ?
189                         count($this->revisions) : count($this->archrevs);
190                 $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(), $count );
191
192                 $bitfields = 0;
193                 $wgOut->addHTML( "<ul>" );
194
195                 $where = $revObjs = array();
196                 $dbr = wfGetDB( DB_MASTER );
197                 
198                 $revisions = 0;
199                 // Live revisions...
200                 if( $this->deleteKey=='oldid' ) {
201                         // Run through and pull all our data in one query
202                         foreach( $this->revisions as $revid ) {
203                                 $where[] = intval($revid);
204                         }
205                         $result = $dbr->select( array('revision','page'), '*',
206                                 array(
207                                         'rev_page' => $this->page->getArticleID(),
208                                         'rev_id' => $where,
209                                         'rev_page = page_id' ),
210                                 __METHOD__ );
211                         while( $row = $dbr->fetchObject( $result ) ) {
212                                 $revObjs[$row->rev_id] = new Revision( $row );
213                         }
214                         foreach( $this->revisions as $revid ) {
215                                 // Hiding top revisison is bad
216                                 if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) {
217                                         continue;
218                                 } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) {
219                                 // If a rev is hidden from sysops
220                                         if( !$this->wasPosted ) {
221                                                 $wgOut->permissionRequired( 'suppressrevision' );
222                                                 return;
223                                         }
224                                         $UserAllowed = false;
225                                 }
226                                 $revisions++;
227                                 $wgOut->addHTML( $this->historyLine( $revObjs[$revid] ) );
228                                 $bitfields |= $revObjs[$revid]->mDeleted;
229                         }
230                 // The archives...
231                 } else {
232                         // Run through and pull all our data in one query
233                         foreach( $this->archrevs as $timestamp ) {
234                                 $where[] = $dbr->timestamp( $timestamp );
235                         }
236                         $result = $dbr->select( 'archive', '*',
237                                 array(
238                                         'ar_namespace' => $this->page->getNamespace(),
239                                         'ar_title' => $this->page->getDBKey(),
240                                         'ar_timestamp' => $where ),
241                                 __METHOD__ );
242                         while( $row = $dbr->fetchObject( $result ) ) {
243                                 $timestamp = wfTimestamp( TS_MW, $row->ar_timestamp );
244                                 $revObjs[$timestamp] = new Revision( array(
245                                         'page'       => $this->page->getArticleId(),
246                                         'id'         => $row->ar_rev_id,
247                                         'text'       => $row->ar_text_id,
248                                         'comment'    => $row->ar_comment,
249                                         'user'       => $row->ar_user,
250                                         'user_text'  => $row->ar_user_text,
251                                         'timestamp'  => $timestamp,
252                                         'minor_edit' => $row->ar_minor_edit,
253                                         'text_id'    => $row->ar_text_id,
254                                         'deleted'    => $row->ar_deleted,
255                                         'len'        => $row->ar_len) );
256                         }
257                         foreach( $this->archrevs as $timestamp ) {
258                                 if( !isset($revObjs[$timestamp]) ) {
259                                         continue;
260                                 } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) {
261                                 // If a rev is hidden from sysops
262                                         if( !$this->wasPosted ) {
263                                                 $wgOut->permissionRequired( 'suppressrevision' );
264                                                 return;
265                                         }
266                                         $UserAllowed = false;
267                                 }
268                                 $revisions++;
269                                 $wgOut->addHTML( $this->historyLine( $revObjs[$timestamp] ) );
270                                 $bitfields |= $revObjs[$timestamp]->mDeleted;
271                         }
272                 }
273                 if( !$revisions ) {
274                         $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
275                         return;
276                 }
277                 
278                 $wgOut->addHTML( "</ul>" );
279                 // Explanation text
280                 $this->addUsageText();
281
282                 // Normal sysops can always see what they did, but can't always change it
283                 if( !$UserAllowed ) return;
284
285                 $items = array(
286                         Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
287                         Xml::submitButton( wfMsg( 'revdelete-submit' ) )
288                 );
289                 $hidden = array(
290                         Xml::hidden( 'wpEditToken', $wgUser->editToken() ),
291                         Xml::hidden( 'target', $this->page->getPrefixedText() ),
292                         Xml::hidden( 'type', $this->deleteKey )
293                 );
294                 if( $this->deleteKey=='oldid' ) {
295                         foreach( $revObjs as $rev )
296                                 $hidden[] = Xml::hidden( 'oldid[]', $rev->getId() );
297                 } else {
298                         foreach( $revObjs as $rev )
299                                 $hidden[] = Xml::hidden( 'artimestamp[]', $rev->getTimestamp() );
300                 }
301                 $special = SpecialPage::getTitleFor( 'Revisiondelete' );
302                 $wgOut->addHTML(
303                         Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), 
304                                 'id' => 'mw-revdel-form-revisions' ) ) .
305                         Xml::openElement( 'fieldset' ) .
306                         xml::element( 'legend', null,  wfMsg( 'revdelete-legend' ) )
307                 );
308
309                 $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) );
310                 foreach( $items as $item ) {
311                         $wgOut->addHTML( Xml::tags( 'p', null, $item ) );
312                 }
313                 foreach( $hidden as $item ) {
314                         $wgOut->addHTML( $item );
315                 }
316                 $wgOut->addHTML(
317                         Xml::closeElement( 'fieldset' ) .
318                         Xml::closeElement( 'form' ) . "\n"
319                 );
320         }
321
322         /**
323          * This lets a user set restrictions for archived images
324          */
325         private function showImages() {
326                 global $wgOut, $wgUser, $wgLang;
327                 $UserAllowed = true;
328
329                 $count = ($this->deleteKey=='oldimage') ? count($this->ofiles) : count($this->afiles);
330                 $wgOut->addWikiMsg( 'revdelete-selected', $this->page->getPrefixedText(),
331                         $wgLang->formatNum($count) );
332
333                 $bitfields = 0;
334                 $wgOut->addHTML( "<ul>" );
335
336                 $where = $filesObjs = array();
337                 $dbr = wfGetDB( DB_MASTER );
338                 // Live old revisions...
339                 $revisions = 0;
340                 if( $this->deleteKey=='oldimage' ) {
341                         // Run through and pull all our data in one query
342                         foreach( $this->ofiles as $timestamp ) {
343                                 $where[] = $timestamp.'!'.$this->page->getDBKey();
344                         }
345                         $result = $dbr->select( 'oldimage', '*',
346                                 array(
347                                         'oi_name' => $this->page->getDBKey(),
348                                         'oi_archive_name' => $where ),
349                                 __METHOD__ );
350                         while( $row = $dbr->fetchObject( $result ) ) {
351                                 $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
352                                 $filesObjs[$row->oi_archive_name]->user = $row->oi_user;
353                                 $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text;
354                         }
355                         // Check through our images
356                         foreach( $this->ofiles as $timestamp ) {
357                                 $archivename = $timestamp.'!'.$this->page->getDBKey();
358                                 if( !isset($filesObjs[$archivename]) ) {
359                                         continue;
360                                 } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) {
361                                         // If a rev is hidden from sysops
362                                         if( !$this->wasPosted ) {
363                                                 $wgOut->permissionRequired( 'suppressrevision' );
364                                                 return;
365                                         }
366                                         $UserAllowed = false;
367                                 }
368                                 $revisions++;
369                                 // Inject history info
370                                 $wgOut->addHTML( $this->fileLine( $filesObjs[$archivename] ) );
371                                 $bitfields |= $filesObjs[$archivename]->deleted;
372                         }
373                 // Archived files...
374                 } else {
375                         // Run through and pull all our data in one query
376                         foreach( $this->afiles as $id ) {
377                                 $where[] = intval($id);
378                         }
379                         $result = $dbr->select( 'filearchive', '*',
380                                 array(
381                                         'fa_name' => $this->page->getDBKey(),
382                                         'fa_id' => $where ),
383                                 __METHOD__ );
384                         while( $row = $dbr->fetchObject( $result ) ) {
385                                 $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row );
386                         }
387
388                         foreach( $this->afiles as $fileid ) {
389                                 if( !isset($filesObjs[$fileid]) ) {
390                                         continue;
391                                 } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) {
392                                         // If a rev is hidden from sysops
393                                         if( !$this->wasPosted ) {
394                                                 $wgOut->permissionRequired( 'suppressrevision' );
395                                                 return;
396                                         }
397                                         $UserAllowed = false;
398                                 }
399                                 $revisions++;
400                                 // Inject history info
401                                 $wgOut->addHTML( $this->archivedfileLine( $filesObjs[$fileid] ) );
402                                 $bitfields |= $filesObjs[$fileid]->deleted;
403                         }
404                 }
405                 if( !$revisions ) {
406                         $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
407                         return;
408                 }
409                 
410                 $wgOut->addHTML( "</ul>" );
411                 // Explanation text
412                 $this->addUsageText();
413                 // Normal sysops can always see what they did, but can't always change it
414                 if( !$UserAllowed ) return;
415
416                 $items = array(
417                         Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
418                         Xml::submitButton( wfMsg( 'revdelete-submit' ) )
419                 );
420                 $hidden = array(
421                         Xml::hidden( 'wpEditToken', $wgUser->editToken() ),
422                         Xml::hidden( 'target', $this->page->getPrefixedText() ),
423                         Xml::hidden( 'type', $this->deleteKey )
424                 );
425                 if( $this->deleteKey=='oldimage' ) {
426                         foreach( $this->ofiles as $filename )
427                                 $hidden[] = Xml::hidden( 'oldimage[]', $filename );
428                 } else {
429                         foreach( $this->afiles as $fileid )
430                                 $hidden[] = Xml::hidden( 'fileid[]', $fileid );
431                 }
432                 $special = SpecialPage::getTitleFor( 'Revisiondelete' );
433                 $wgOut->addHTML(
434                         Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), 
435                                 'id' => 'mw-revdel-form-filerevisions' ) ) .
436                         Xml::fieldset( wfMsg( 'revdelete-legend' ) )
437                 );
438
439                 $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) );
440                 foreach( $items as $item ) {
441                         $wgOut->addHTML( "<p>$item</p>" );
442                 }
443                 foreach( $hidden as $item ) {
444                         $wgOut->addHTML( $item );
445                 }
446
447                 $wgOut->addHTML(
448                         Xml::closeElement( 'fieldset' ) .
449                         Xml::closeElement( 'form' ) . "\n"
450                 );
451         }
452
453         /**
454          * This lets a user set restrictions for log items
455          */
456         private function showLogItems() {
457                 global $wgOut, $wgUser, $wgMessageCache, $wgLang;
458                 $UserAllowed = true;
459
460                 $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->events) ) );
461
462                 $bitfields = 0;
463                 $wgOut->addHTML( "<ul>" );
464
465                 $where = $logRows = array();
466                 $dbr = wfGetDB( DB_MASTER );
467                 // Run through and pull all our data in one query
468                 $logItems = 0;
469                 foreach( $this->events as $logid ) {
470                         $where[] = intval($logid);
471                 }
472                 list($log,$logtype) = explode( '/',$this->page->getDBKey(), 2 );
473                 $result = $dbr->select( 'logging', '*',
474                         array(
475                                 'log_type' => $logtype,
476                                 'log_id' => $where ),
477                         __METHOD__ );
478                 while( $row = $dbr->fetchObject( $result ) ) {
479                         $logRows[$row->log_id] = $row;
480                 }
481                 $wgMessageCache->loadAllMessages();
482                 foreach( $this->events as $logid ) {
483                         // Don't hide from oversight log!!!
484                         if( !isset( $logRows[$logid] ) || $logRows[$logid]->log_type=='suppress' ) {
485                                 continue;
486                         } else if( !LogEventsList::userCan( $logRows[$logid],Revision::DELETED_RESTRICTED) ) {
487                         // If an event is hidden from sysops
488                                 if( !$this->wasPosted ) {
489                                         $wgOut->permissionRequired( 'suppressrevision' );
490                                         return;
491                                 }
492                                 $UserAllowed = false;
493                         }
494                         $logItems++;
495                         $wgOut->addHTML( $this->logLine( $logRows[$logid] ) );
496                         $bitfields |= $logRows[$logid]->log_deleted;
497                 }
498                 if( !$logItems ) {
499                         $wgOut->showErrorPage( 'revdelete-nologid-title', 'revdelete-nologid-text' );
500                         return;
501                 }
502                 
503                 $wgOut->addHTML( "</ul>" );
504                 // Explanation text
505                 $this->addUsageText();
506                 // Normal sysops can always see what they did, but can't always change it
507                 if( !$UserAllowed ) return;
508
509                 $items = array(
510                         Xml::inputLabel( wfMsg( 'revdelete-log' ), 'wpReason', 'wpReason', 60 ),
511                         Xml::submitButton( wfMsg( 'revdelete-submit' ) ) );
512                 $hidden = array(
513                         Xml::hidden( 'wpEditToken', $wgUser->editToken() ),
514                         Xml::hidden( 'target', $this->page->getPrefixedText() ),
515                         Xml::hidden( 'type', $this->deleteKey ) );
516                 foreach( $this->events as $logid ) {
517                         $hidden[] = Xml::hidden( 'logid[]', $logid );
518                 }
519
520                 $special = SpecialPage::getTitleFor( 'Revisiondelete' );
521                 $wgOut->addHTML(
522                         Xml::openElement( 'form', array( 'method' => 'post', 'action' => $special->getLocalUrl( 'action=submit' ), 
523                                 'id' => 'mw-revdel-form-logs' ) ) .
524                         Xml::fieldset( wfMsg( 'revdelete-legend' ) )
525                 );
526                 
527                 $wgOut->addHTML( $this->buildCheckBoxes( $bitfields ) );
528                 foreach( $items as $item ) {
529                         $wgOut->addHTML( "<p>$item</p>" );
530                 }
531                 foreach( $hidden as $item ) {
532                         $wgOut->addHTML( $item );
533                 }
534
535                 $wgOut->addHTML(
536                         Xml::closeElement( 'fieldset' ) .
537                         Xml::closeElement( 'form' ) . "\n"
538                 );
539         }
540         
541         private function addUsageText() {
542                 global $wgOut, $wgUser;
543                 $wgOut->addWikiMsg( 'revdelete-text' );
544                 if( $wgUser->isAllowed( 'suppressrevision' ) ) {
545                         $wgOut->addWikiMsg( 'revdelete-suppress-text' );
546                 }
547         }
548         
549         /**
550         * @param int $bitfields, aggregate bitfield of all the bitfields
551         * @returns string HTML
552         */
553         private function buildCheckBoxes( $bitfields ) {
554                 $html = '';
555                 // FIXME: all items checked for just one rev are checked, even if not set for the others
556                 foreach( $this->checks as $item ) {
557                         list( $message, $name, $field ) = $item;
558                         $line = Xml::tags( 'div', null, Xml::checkLabel( wfMsg($message), $name, $name,
559                                 $bitfields & $field ) );
560                         if( $field == Revision::DELETED_RESTRICTED ) $line = "<b>$line</b>";
561                         $html .= $line;
562                 }
563                 return $html;
564         }
565
566         /**
567          * @param Revision $rev
568          * @returns string
569          */
570         private function historyLine( $rev ) {
571                 global $wgLang, $wgUser;
572
573                 $date = $wgLang->timeanddate( $rev->getTimestamp() );
574                 $difflink = $del = '';
575                 // Live revisions
576                 if( $this->deleteKey=='oldid' ) {
577                         $tokenParams = '&unhide=1&token='.urlencode( $wgUser->editToken( $rev->getId() ) );
578                         $revlink = $this->skin->makeLinkObj( $this->page, $date, 'oldid='.$rev->getId() . $tokenParams );
579                         $difflink = '(' . $this->skin->makeKnownLinkObj( $this->page, wfMsgHtml('diff'),
580                                 'diff=' . $rev->getId() . '&oldid=prev' . $tokenParams ) . ')';
581                 // Archived revisions
582                 } else {
583                         $undelete = SpecialPage::getTitleFor( 'Undelete' );
584                         $target = $this->page->getPrefixedText();
585                         $revlink = $this->skin->makeLinkObj( $undelete, $date,
586                                 "target=$target&timestamp=" . $rev->getTimestamp() );
587                         $difflink = '(' . $this->skin->makeKnownLinkObj( $undelete, wfMsgHtml('diff'),
588                                 "target=$target&diff=prev&timestamp=" . $rev->getTimestamp() ) . ')';
589                 }
590                 // Check permissions; items may be "suppressed"
591                 if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
592                         $revlink = '<span class="history-deleted">'.$revlink.'</span>';
593                         $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
594                         if( !$rev->userCan(Revision::DELETED_TEXT) ) {
595                                 $revlink = '<span class="history-deleted">'.$date.'</span>';
596                                 $difflink = '(' . wfMsgHtml('diff') . ')';
597                         }
598                 }
599                 $userlink = $this->skin->revUserLink( $rev );
600                 $comment = $this->skin->revComment( $rev );
601
602                 return "<li>$difflink $revlink $userlink $comment{$del}</li>";
603         }
604
605         /**
606          * @param File $file
607          * @returns string
608          */
609         private function fileLine( $file ) {
610                 global $wgLang, $wgTitle;
611
612                 $target = $this->page->getPrefixedText();
613                 $date = $wgLang->timeanddate( $file->getTimestamp(), true  );
614
615                 $del = '';
616                 # Hidden files...
617                 if( $file->isDeleted(File::DELETED_FILE) ) {
618                         $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
619                         if( !$file->userCan(File::DELETED_FILE) ) {
620                                 $pageLink = $date;
621                         } else {
622                                 $pageLink = $this->skin->makeKnownLinkObj( $wgTitle, $date,
623                                         "target=$target&file=$file->sha1.".$file->getExtension() );
624                         }
625                         $pageLink = '<span class="history-deleted">' . $pageLink . '</span>';
626                 # Regular files...
627                 } else {
628                         $url = $file->getUrlRel();
629                         $pageLink = "<a href=\"{$url}\">{$date}</a>";
630                 }
631
632                 $data = wfMsg( 'widthheight',
633                                         $wgLang->formatNum( $file->getWidth() ),
634                                         $wgLang->formatNum( $file->getHeight() ) ) .
635                         ' (' . wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $file->getSize() ) ) . ')';
636                 $data = htmlspecialchars( $data );
637
638                 return "<li>$pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del</li>";
639         }
640
641         /**
642          * @param ArchivedFile $file
643          * @returns string
644          */
645         private function archivedfileLine( $file ) {
646                 global $wgLang;
647
648                 $target = $this->page->getPrefixedText();
649                 $date = $wgLang->timeanddate( $file->getTimestamp(), true  );
650
651                 $undelete = SpecialPage::getTitleFor( 'Undelete' );
652                 $pageLink = $this->skin->makeKnownLinkObj( $undelete, $date, "target=$target&file={$file->getKey()}" );
653
654                 $del = '';
655                 if( $file->isDeleted(File::DELETED_FILE) ) {
656                         $del = ' <tt>' . wfMsgHtml( 'deletedrev' ) . '</tt>';
657                 }
658
659                 $data = wfMsg( 'widthheight',
660                                         $wgLang->formatNum( $file->getWidth() ),
661                                         $wgLang->formatNum( $file->getHeight() ) ) .
662                         ' (' . wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $file->getSize() ) ) . ')';
663                 $data = htmlspecialchars( $data );
664
665                 return "<li> $pageLink ".$this->fileUserTools( $file )." $data ".$this->fileComment( $file )."$del</li>";
666         }
667
668         /**
669          * @param Array $row row
670          * @returns string
671          */
672         private function logLine( $row ) {
673                 global $wgLang;
674
675                 $date = $wgLang->timeanddate( $row->log_timestamp );
676                 $paramArray = LogPage::extractParams( $row->log_params );
677                 $title = Title::makeTitle( $row->log_namespace, $row->log_title );
678
679                 $logtitle = SpecialPage::getTitleFor( 'Log' );
680                 $loglink = $this->skin->makeKnownLinkObj( $logtitle, wfMsgHtml( 'log' ),
681                         wfArrayToCGI( array( 'page' => $title->getPrefixedUrl() ) ) );
682                 // Action text
683                 if( !LogEventsList::userCan($row,LogPage::DELETED_ACTION) ) {
684                         $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
685                 } else {
686                         $action = LogPage::actionText( $row->log_type, $row->log_action, $title,
687                                 $this->skin, $paramArray, true, true );
688                         if( $row->log_deleted & LogPage::DELETED_ACTION )
689                                 $action = '<span class="history-deleted">' . $action . '</span>';
690                 }
691                 // User links
692                 $userLink = $this->skin->userLink( $row->log_user, User::WhoIs($row->log_user) );
693                 if( LogEventsList::isDeleted($row,LogPage::DELETED_USER) ) {
694                         $userLink = '<span class="history-deleted">' . $userLink . '</span>';
695                 }
696                 // Comment
697                 $comment = $wgLang->getDirMark() . $this->skin->commentBlock( $row->log_comment );
698                 if( LogEventsList::isDeleted($row,LogPage::DELETED_COMMENT) ) {
699                         $comment = '<span class="history-deleted">' . $comment . '</span>';
700                 }
701                 return "<li>($loglink) $date $userLink $action $comment</li>";
702         }
703
704         /**
705          * Generate a user tool link cluster if the current user is allowed to view it
706          * @param ArchivedFile $file
707          * @return string HTML
708          */
709         private function fileUserTools( $file ) {
710                 if( $file->userCan( Revision::DELETED_USER ) ) {
711                         $link = $this->skin->userLink( $file->user, $file->user_text ) .
712                                 $this->skin->userToolLinks( $file->user, $file->user_text );
713                 } else {
714                         $link = wfMsgHtml( 'rev-deleted-user' );
715                 }
716                 if( $file->isDeleted( Revision::DELETED_USER ) ) {
717                         return '<span class="history-deleted">' . $link . '</span>';
718                 }
719                 return $link;
720         }
721
722         /**
723          * Wrap and format the given file's comment block, if the current
724          * user is allowed to view it.
725          *
726          * @param ArchivedFile $file
727          * @return string HTML
728          */
729         private function fileComment( $file ) {
730                 if( $file->userCan( File::DELETED_COMMENT ) ) {
731                         $block = $this->skin->commentBlock( $file->description );
732                 } else {
733                         $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
734                 }
735                 if( $file->isDeleted( File::DELETED_COMMENT ) ) {
736                         return "<span class=\"history-deleted\">$block</span>";
737                 }
738                 return $block;
739         }
740
741         /**
742          * @param WebRequest $request
743          */
744         private function submit( $request ) {
745                 global $wgUser, $wgOut;
746                 # Check edit token on submission
747                 if( $this->wasPosted && !$wgUser->matchEditToken( $request->getVal('wpEditToken') ) ) {
748                         $wgOut->addWikiMsg( 'sessionfailure' );
749                         return false;
750                 }
751                 $bitfield = $this->extractBitfield( $request );
752                 $comment = $request->getText( 'wpReason' );
753                 # Can the user set this field?
754                 if( $bitfield & Revision::DELETED_RESTRICTED && !$wgUser->isAllowed('suppressrevision') ) {
755                         $wgOut->permissionRequired( 'suppressrevision' );
756                         return false;
757                 }
758                 # If the save went through, go to success message. Otherwise
759                 # bounce back to form...
760                 if( $this->save( $bitfield, $comment, $this->page ) ) {
761                         $this->success();
762                 } else if( $request->getCheck( 'oldid' ) || $request->getCheck( 'artimestamp' ) ) {
763                         return $this->showRevs();
764                 } else if( $request->getCheck( 'logid' ) ) {
765                         return $this->showLogs();
766                 } else if( $request->getCheck( 'oldimage' ) || $request->getCheck( 'fileid' ) ) {
767                         return $this->showImages();
768                 }
769         }
770
771         private function success() {
772                 global $wgOut;
773
774                 $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
775
776                 $wrap = '<span class="success">$1</span>';
777
778                 if( $this->deleteKey=='logid' ) {
779                         $wgOut->wrapWikiMsg( $wrap, 'logdelete-success' );
780                         $this->showLogItems();
781                 } else if( $this->deleteKey=='oldid' || $this->deleteKey=='artimestamp' ) {
782                                 $wgOut->wrapWikiMsg( $wrap, 'revdelete-success' );
783                         $this->showRevs();
784                 } else if( $this->deleteKey=='fileid' ) {
785                         $wgOut->wrapWikiMsg( $wrap, 'revdelete-success' );
786                         $this->showImages();
787                 } else if( $this->deleteKey=='oldimage' ) {
788                         $wgOut->wrapWikiMsg( $wrap, 'revdelete-success' );
789                         $this->showImages();
790                 }
791         }
792
793         /**
794          * Put together a rev_deleted bitfield from the submitted checkboxes
795          * @param WebRequest $request
796          * @return int
797          */
798         private function extractBitfield( $request ) {
799                 $bitfield = 0;
800                 foreach( $this->checks as $item ) {
801                         list( /* message */ , $name, $field ) = $item;
802                         if( $request->getCheck( $name ) ) {
803                                 $bitfield |= $field;
804                         }
805                 }
806                 return $bitfield;
807         }
808
809         private function save( $bitfield, $reason, $title ) {
810                 $dbw = wfGetDB( DB_MASTER );
811                 // Don't allow simply locking the interface for no reason
812                 if( $bitfield == Revision::DELETED_RESTRICTED ) {
813                         $bitfield = 0;
814                 }
815                 $deleter = new RevisionDeleter( $dbw );
816                 // By this point, only one of the below should be set
817                 if( isset($this->revisions) ) {
818                         return $deleter->setRevVisibility( $title, $this->revisions, $bitfield, $reason );
819                 } else if( isset($this->archrevs) ) {
820                         return $deleter->setArchiveVisibility( $title, $this->archrevs, $bitfield, $reason );
821                 } else if( isset($this->ofiles) ) {
822                         return $deleter->setOldImgVisibility( $title, $this->ofiles, $bitfield, $reason );
823                 } else if( isset($this->afiles) ) {
824                         return $deleter->setArchFileVisibility( $title, $this->afiles, $bitfield, $reason );
825                 } else if( isset($this->events) ) {
826                         return $deleter->setEventVisibility( $title, $this->events, $bitfield, $reason );
827                 }
828         }
829 }
830
831 /**
832  * Implements the actions for Revision Deletion.
833  * @ingroup SpecialPage
834  */
835 class RevisionDeleter {
836         function __construct( $db ) {
837                 $this->dbw = $db;
838         }
839
840         /**
841          * @param $title, the page these events apply to
842          * @param array $items list of revision ID numbers
843          * @param int $bitfield new rev_deleted value
844          * @param string $comment Comment for log records
845          */
846         function setRevVisibility( $title, $items, $bitfield, $comment ) {
847                 global $wgOut;
848
849                 $userAllowedAll = $success = true;
850                 $revIDs = array();
851                 $revCount = 0;
852                 // Run through and pull all our data in one query
853                 foreach( $items as $revid ) {
854                         $where[] = intval($revid);
855                 }
856                 $result = $this->dbw->select( 'revision', '*',
857                         array(
858                                 'rev_page' => $title->getArticleID(),
859                                 'rev_id' => $where ),
860                         __METHOD__ );
861                 while( $row = $this->dbw->fetchObject( $result ) ) {
862                         $revObjs[$row->rev_id] = new Revision( $row );
863                 }
864                 // To work!
865                 foreach( $items as $revid ) {
866                         if( !isset($revObjs[$revid]) || $revObjs[$revid]->isCurrent() ) {
867                                 $success = false;
868                                 continue; // Must exist
869                         } else if( !$revObjs[$revid]->userCan(Revision::DELETED_RESTRICTED) ) {
870                         $userAllowedAll=false;
871                                 continue;
872                         }
873                         // For logging, maintain a count of revisions
874                         if( $revObjs[$revid]->mDeleted != $bitfield ) {
875                                 $revCount++;
876                                 $revIDs[]=$revid;
877
878                                 $this->updateRevision( $revObjs[$revid], $bitfield );
879                                 $this->updateRecentChangesEdits( $revObjs[$revid], $bitfield, false );
880                         }
881                 }
882                 // Clear caches...
883                 // Don't log or touch if nothing changed
884                 if( $revCount > 0 ) {
885                         $this->updateLog( $title, $revCount, $bitfield, $revObjs[$revid]->mDeleted,
886                                 $comment, $title, 'oldid', $revIDs );
887                         $this->updatePage( $title );
888                 }
889                 // Where all revs allowed to be set?
890                 if( !$userAllowedAll ) {
891                         //FIXME: still might be confusing???
892                         $wgOut->permissionRequired( 'suppressrevision' );
893                         return false;
894                 }
895
896                 return $success;
897         }
898
899          /**
900          * @param $title, the page these events apply to
901          * @param array $items list of revision ID numbers
902          * @param int $bitfield new rev_deleted value
903          * @param string $comment Comment for log records
904          */
905         function setArchiveVisibility( $title, $items, $bitfield, $comment ) {
906                 global $wgOut;
907
908                 $userAllowedAll = $success = true;
909                 $count = 0;
910                 $Id_set = array();
911                 // Run through and pull all our data in one query
912                 foreach( $items as $timestamp ) {
913                         $where[] = $this->dbw->timestamp( $timestamp );
914                 }
915                 $result = $this->dbw->select( 'archive', '*',
916                         array(
917                                 'ar_namespace' => $title->getNamespace(),
918                                 'ar_title' => $title->getDBKey(),
919                                 'ar_timestamp' => $where ),
920                         __METHOD__ );
921                 while( $row = $this->dbw->fetchObject( $result ) ) {
922                         $timestamp = wfTimestamp( TS_MW, $row->ar_timestamp );
923                         $revObjs[$timestamp] = new Revision( array(
924                                 'page'       => $title->getArticleId(),
925                                 'id'         => $row->ar_rev_id,
926                                 'text'       => $row->ar_text_id,
927                                 'comment'    => $row->ar_comment,
928                                 'user'       => $row->ar_user,
929                                 'user_text'  => $row->ar_user_text,
930                                 'timestamp'  => $timestamp,
931                                 'minor_edit' => $row->ar_minor_edit,
932                                 'text_id'    => $row->ar_text_id,
933                                 'deleted'    => $row->ar_deleted,
934                                 'len'        => $row->ar_len) );
935                 }
936                 // To work!
937                 foreach( $items as $timestamp ) {
938                         // This will only select the first revision with this timestamp.
939                         // Since they are all selected/deleted at once, we can just check the
940                         // permissions of one. UPDATE is done via timestamp, so all revs are set.
941                         if( !is_object($revObjs[$timestamp]) ) {
942                                 $success = false;
943                                 continue; // Must exist
944                         } else if( !$revObjs[$timestamp]->userCan(Revision::DELETED_RESTRICTED) ) {
945                         $userAllowedAll=false;
946                                 continue;
947                         }
948                         // Which revisions did we change anything about?
949                         if( $revObjs[$timestamp]->mDeleted != $bitfield ) {
950                            $Id_set[]=$timestamp;
951                            $count++;
952
953                            $this->updateArchive( $revObjs[$timestamp], $title, $bitfield );
954                         }
955                 }
956                 // For logging, maintain a count of revisions
957                 if( $count > 0 ) {
958                         $this->updateLog( $title, $count, $bitfield, $revObjs[$timestamp]->mDeleted,
959                                 $comment, $title, 'artimestamp', $Id_set );
960                 }
961                 // Where all revs allowed to be set?
962                 if( !$userAllowedAll ) {
963                         $wgOut->permissionRequired( 'suppressrevision' );
964                         return false;
965                 }
966
967                 return $success;
968         }
969
970          /**
971          * @param $title, the page these events apply to
972          * @param array $items list of revision ID numbers
973          * @param int $bitfield new rev_deleted value
974          * @param string $comment Comment for log records
975          */
976         function setOldImgVisibility( $title, $items, $bitfield, $comment ) {
977                 global $wgOut;
978
979                 $userAllowedAll = $success = true;
980                 $count = 0;
981                 $set = array();
982                 // Run through and pull all our data in one query
983                 foreach( $items as $timestamp ) {
984                         $where[] = $timestamp.'!'.$title->getDBKey();
985                 }
986                 $result = $this->dbw->select( 'oldimage', '*',
987                         array(
988                                 'oi_name' => $title->getDBKey(),
989                                 'oi_archive_name' => $where ),
990                         __METHOD__ );
991                 while( $row = $this->dbw->fetchObject( $result ) ) {
992                         $filesObjs[$row->oi_archive_name] = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
993                         $filesObjs[$row->oi_archive_name]->user = $row->oi_user;
994                         $filesObjs[$row->oi_archive_name]->user_text = $row->oi_user_text;
995                 }
996                 // To work!
997                 foreach( $items as $timestamp ) {
998                         $archivename = $timestamp.'!'.$title->getDBKey();
999                         if( !isset($filesObjs[$archivename]) ) {
1000                                 $success = false;
1001                                 continue; // Must exist
1002                         } else if( !$filesObjs[$archivename]->userCan(File::DELETED_RESTRICTED) ) {
1003                         $userAllowedAll=false;
1004                                 continue;
1005                         }
1006
1007                         $transaction = true;
1008                         // Which revisions did we change anything about?
1009                         if( $filesObjs[$archivename]->deleted != $bitfield ) {
1010                                 $count++;
1011
1012                                 $this->dbw->begin();
1013                                 $this->updateOldFiles( $filesObjs[$archivename], $bitfield );
1014                                 // If this image is currently hidden...
1015                                 if( $filesObjs[$archivename]->deleted & File::DELETED_FILE ) {
1016                                         if( $bitfield & File::DELETED_FILE ) {
1017                                                 # Leave it alone if we are not changing this...
1018                                                 $set[]=$archivename;
1019                                                 $transaction = true;
1020                                         } else {
1021                                                 # We are moving this out
1022                                                 $transaction = $this->makeOldImagePublic( $filesObjs[$archivename] );
1023                                                 $set[]=$transaction;
1024                                         }
1025                                 // Is it just now becoming hidden?
1026                                 } else if( $bitfield & File::DELETED_FILE ) {
1027                                         $transaction = $this->makeOldImagePrivate( $filesObjs[$archivename] );
1028                                         $set[]=$transaction;
1029                                 } else {
1030                                         $set[]=$timestamp;
1031                                 }
1032                                 // If our file operations fail, then revert back the db
1033                                 if( $transaction==false ) {
1034                                         $this->dbw->rollback();
1035                                         return false;
1036                                 }
1037                                 $this->dbw->commit();
1038                         }
1039                 }
1040
1041                 // Log if something was changed
1042                 if( $count > 0 ) {
1043                         $this->updateLog( $title, $count, $bitfield, $filesObjs[$archivename]->deleted,
1044                                 $comment, $title, 'oldimage', $set );
1045                         # Purge page/history
1046                         $file = wfLocalFile( $title );
1047                         $file->purgeCache();
1048                         $file->purgeHistory();
1049                         # Invalidate cache for all pages using this file
1050                         $update = new HTMLCacheUpdate( $title, 'imagelinks' );
1051                         $update->doUpdate();
1052                 }
1053                 // Where all revs allowed to be set?
1054                 if( !$userAllowedAll ) {
1055                         $wgOut->permissionRequired( 'suppressrevision' );
1056                         return false;
1057                 }
1058
1059                 return $success;
1060         }
1061
1062          /**
1063          * @param $title, the page these events apply to
1064          * @param array $items list of revision ID numbers
1065          * @param int $bitfield new rev_deleted value
1066          * @param string $comment Comment for log records
1067          */
1068         function setArchFileVisibility( $title, $items, $bitfield, $comment ) {
1069                 global $wgOut;
1070
1071                 $userAllowedAll = $success = true;
1072                 $count = 0;
1073                 $Id_set = array();
1074
1075                 // Run through and pull all our data in one query
1076                 foreach( $items as $id ) {
1077                         $where[] = intval($id);
1078                 }
1079                 $result = $this->dbw->select( 'filearchive', '*',
1080                         array( 'fa_name' => $title->getDBKey(),
1081                                 'fa_id' => $where ),
1082                         __METHOD__ );
1083                 while( $row = $this->dbw->fetchObject( $result ) ) {
1084                         $filesObjs[$row->fa_id] = ArchivedFile::newFromRow( $row );
1085                 }
1086                 // To work!
1087                 foreach( $items as $fileid ) {
1088                         if( !isset($filesObjs[$fileid]) ) {
1089                                 $success = false;
1090                                 continue; // Must exist
1091                         } else if( !$filesObjs[$fileid]->userCan(File::DELETED_RESTRICTED) ) {
1092                         $userAllowedAll=false;
1093                                 continue;
1094                         }
1095                         // Which revisions did we change anything about?
1096                         if( $filesObjs[$fileid]->deleted != $bitfield ) {
1097                            $Id_set[]=$fileid;
1098                            $count++;
1099
1100                            $this->updateArchFiles( $filesObjs[$fileid], $bitfield );
1101                         }
1102                 }
1103                 // Log if something was changed
1104                 if( $count > 0 ) {
1105                         $this->updateLog( $title, $count, $bitfield, $comment,
1106                                 $filesObjs[$fileid]->deleted, $title, 'fileid', $Id_set );
1107                 }
1108                 // Where all revs allowed to be set?
1109                 if( !$userAllowedAll ) {
1110                         $wgOut->permissionRequired( 'suppressrevision' );
1111                         return false;
1112                 }
1113
1114                 return $success;
1115         }
1116
1117         /**
1118          * @param $title, the log page these events apply to
1119          * @param array $items list of log ID numbers
1120          * @param int $bitfield new log_deleted value
1121          * @param string $comment Comment for log records
1122          */
1123         function setEventVisibility( $title, $items, $bitfield, $comment ) {
1124                 global $wgOut;
1125
1126                 $userAllowedAll = $success = true;
1127                 $count = 0;
1128                 $log_Ids = array();
1129
1130                 // Run through and pull all our data in one query
1131                 foreach( $items as $logid ) {
1132                         $where[] = intval($logid);
1133                 }
1134                 list($log,$logtype) = explode( '/',$title->getDBKey(), 2 );
1135                 $result = $this->dbw->select( 'logging', '*',
1136                         array(
1137                                 'log_type' => $logtype,
1138                                 'log_id' => $where ),
1139                         __METHOD__ );
1140                 while( $row = $this->dbw->fetchObject( $result ) ) {
1141                         $logRows[$row->log_id] = $row;
1142                 }
1143                 // To work!
1144                 foreach( $items as $logid ) {
1145                         if( !isset($logRows[$logid]) ) {
1146                                 $success = false;
1147                                 continue; // Must exist
1148                         } else if( !LogEventsList::userCan($logRows[$logid], LogPage::DELETED_RESTRICTED)
1149                                  || $logRows[$logid]->log_type == 'suppress' ) {
1150                         // Don't hide from oversight log!!!
1151                         $userAllowedAll=false;
1152                         continue;
1153                         }
1154                         // Which logs did we change anything about?
1155                         if( $logRows[$logid]->log_deleted != $bitfield ) {
1156                                 $log_Ids[]=$logid;
1157                                 $count++;
1158
1159                                 $this->updateLogs( $logRows[$logid], $bitfield );
1160                                 $this->updateRecentChangesLog( $logRows[$logid], $bitfield, true );
1161                         }
1162                 }
1163                 // Don't log or touch if nothing changed
1164                 if( $count > 0 ) {
1165                         $this->updateLog( $title, $count, $bitfield, $logRows[$logid]->log_deleted,
1166                                 $comment, $title, 'logid', $log_Ids );
1167                 }
1168                 // Were all revs allowed to be set?
1169                 if( !$userAllowedAll ) {
1170                         $wgOut->permissionRequired( 'suppressrevision' );
1171                         return false;
1172                 }
1173
1174                 return $success;
1175         }
1176
1177         /**
1178          * Moves an image to a safe private location
1179          * Caller is responsible for clearing caches
1180          * @param File $oimage
1181          * @returns mixed, timestamp string on success, false on failure
1182          */
1183         function makeOldImagePrivate( $oimage ) {
1184                 $transaction = new FSTransaction();
1185                 if( !FileStore::lock() ) {
1186                         wfDebug( __METHOD__.": failed to acquire file store lock, aborting\n" );
1187                         return false;
1188                 }
1189                 $oldpath = $oimage->getArchivePath() . DIRECTORY_SEPARATOR . $oimage->archive_name;
1190                 // Dupe the file into the file store
1191                 if( file_exists( $oldpath ) ) {
1192                         // Is our directory configured?
1193                         if( $store = FileStore::get( 'deleted' ) ) {
1194                                 if( !$oimage->sha1 ) {
1195                                         $oimage->upgradeRow(); // sha1 may be missing
1196                                 }
1197                                 $key = $oimage->sha1 . '.' . $oimage->getExtension();
1198                                 $transaction->add( $store->insert( $key, $oldpath, FileStore::DELETE_ORIGINAL ) );
1199                         } else {
1200                                 $group = null;
1201                                 $key = null;
1202                                 $transaction = false; // Return an error and do nothing
1203                         }
1204                 } else {
1205                         wfDebug( __METHOD__." deleting already-missing '$oldpath'; moving on to database\n" );
1206                         $group = null;
1207                         $key = '';
1208                         $transaction = new FSTransaction(); // empty
1209                 }
1210
1211                 if( $transaction === false ) {
1212                         // Fail to restore?
1213                         wfDebug( __METHOD__.": import to file store failed, aborting\n" );
1214                         throw new MWException( "Could not archive and delete file $oldpath" );
1215                         return false;
1216                 }
1217
1218                 wfDebug( __METHOD__.": set db items, applying file transactions\n" );
1219                 $transaction->commit();
1220                 FileStore::unlock();
1221
1222                 $m = explode('!',$oimage->archive_name,2);
1223                 $timestamp = $m[0];
1224
1225                 return $timestamp;
1226         }
1227
1228         /**
1229          * Moves an image from a safe private location
1230          * Caller is responsible for clearing caches
1231          * @param File $oimage
1232          * @returns mixed, string timestamp on success, false on failure
1233          */
1234         function makeOldImagePublic( $oimage ) {
1235                 $transaction = new FSTransaction();
1236                 if( !FileStore::lock() ) {
1237                         wfDebug( __METHOD__." could not acquire filestore lock\n" );
1238                         return false;
1239                 }
1240
1241                 $store = FileStore::get( 'deleted' );
1242                 if( !$store ) {
1243                         wfDebug( __METHOD__.": skipping row with no file.\n" );
1244                         return false;
1245                 }
1246
1247                 $key = $oimage->sha1.'.'.$oimage->getExtension();
1248                 $destDir = $oimage->getArchivePath();
1249                 if( !is_dir( $destDir ) ) {
1250                         wfMkdirParents( $destDir );
1251                 }
1252                 $destPath = $destDir . DIRECTORY_SEPARATOR . $oimage->archive_name;
1253                 // Check if any other stored revisions use this file;
1254                 // if so, we shouldn't remove the file from the hidden
1255                 // archives so they will still work. Check hidden files first.
1256                 $useCount = $this->dbw->selectField( 'oldimage', '1',
1257                         array( 'oi_sha1' => $oimage->sha1,
1258                                 'oi_deleted & '.File::DELETED_FILE => File::DELETED_FILE ),
1259                         __METHOD__, array( 'FOR UPDATE' ) );
1260                 // Check the rest of the deleted archives too.
1261                 // (these are the ones that don't show in the image history)
1262                 if( !$useCount ) {
1263                         $useCount = $this->dbw->selectField( 'filearchive', '1',
1264                                 array( 'fa_storage_group' => 'deleted', 'fa_storage_key' => $key ),
1265                                 __METHOD__, array( 'FOR UPDATE' ) );
1266                 }
1267
1268                 if( $useCount == 0 ) {
1269                         wfDebug( __METHOD__.": nothing else using {$oimage->sha1}, will deleting after\n" );
1270                         $flags = FileStore::DELETE_ORIGINAL;
1271                 } else {
1272                         $flags = 0;
1273                 }
1274                 $transaction->add( $store->export( $key, $destPath, $flags ) );
1275
1276                 wfDebug( __METHOD__.": set db items, applying file transactions\n" );
1277                 $transaction->commit();
1278                 FileStore::unlock();
1279
1280                 $m = explode('!',$oimage->archive_name,2);
1281                 $timestamp = $m[0];
1282
1283                 return $timestamp;
1284         }
1285
1286         /**
1287          * Update the revision's rev_deleted field
1288          * @param Revision $rev
1289          * @param int $bitfield new rev_deleted bitfield value
1290          */
1291         function updateRevision( $rev, $bitfield ) {
1292                 $this->dbw->update( 'revision',
1293                         array( 'rev_deleted' => $bitfield ),
1294                         array( 'rev_id' => $rev->getId(),
1295                                 'rev_page' => $rev->getPage() ),
1296                         __METHOD__ );
1297         }
1298
1299         /**
1300          * Update the revision's rev_deleted field
1301          * @param Revision $rev
1302          * @param Title $title
1303          * @param int $bitfield new rev_deleted bitfield value
1304          */
1305         function updateArchive( $rev, $title, $bitfield ) {
1306                 $this->dbw->update( 'archive',
1307                         array( 'ar_deleted' => $bitfield ),
1308                         array( 'ar_namespace' => $title->getNamespace(),
1309                                 'ar_title'     => $title->getDBKey(),
1310                                 'ar_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ),
1311                                 'ar_rev_id' => $rev->getId() ),
1312                         __METHOD__ );
1313         }
1314
1315         /**
1316          * Update the images's oi_deleted field
1317          * @param File $file
1318          * @param int $bitfield new rev_deleted bitfield value
1319          */
1320         function updateOldFiles( $file, $bitfield ) {
1321                 $this->dbw->update( 'oldimage',
1322                         array( 'oi_deleted' => $bitfield ),
1323                         array( 'oi_name' => $file->getName(),
1324                                 'oi_timestamp' => $this->dbw->timestamp( $file->getTimestamp() ) ),
1325                         __METHOD__ );
1326         }
1327
1328         /**
1329          * Update the images's fa_deleted field
1330          * @param ArchivedFile $file
1331          * @param int $bitfield new rev_deleted bitfield value
1332          */
1333         function updateArchFiles( $file, $bitfield ) {
1334                 $this->dbw->update( 'filearchive',
1335                         array( 'fa_deleted' => $bitfield ),
1336                         array( 'fa_id' => $file->getId() ),
1337                         __METHOD__ );
1338         }
1339
1340         /**
1341          * Update the logging log_deleted field
1342          * @param Row $row
1343          * @param int $bitfield new rev_deleted bitfield value
1344          */
1345         function updateLogs( $row, $bitfield ) {
1346                 $this->dbw->update( 'logging',
1347                         array( 'log_deleted' => $bitfield ),
1348                         array( 'log_id' => $row->log_id ),
1349                         __METHOD__ );
1350         }
1351
1352         /**
1353          * Update the revision's recentchanges record if fields have been hidden
1354          * @param Revision $rev
1355          * @param int $bitfield new rev_deleted bitfield value
1356          */
1357         function updateRecentChangesEdits( $rev, $bitfield ) {
1358                 $this->dbw->update( 'recentchanges',
1359                         array( 'rc_deleted' => $bitfield,
1360                                    'rc_patrolled' => 1 ),
1361                         array( 'rc_this_oldid' => $rev->getId(),
1362                                 'rc_timestamp' => $this->dbw->timestamp( $rev->getTimestamp() ) ),
1363                         __METHOD__ );
1364         }
1365
1366         /**
1367          * Update the revision's recentchanges record if fields have been hidden
1368          * @param Row $row
1369          * @param int $bitfield new rev_deleted bitfield value
1370          */
1371         function updateRecentChangesLog( $row, $bitfield ) {
1372                 $this->dbw->update( 'recentchanges',
1373                         array( 'rc_deleted' => $bitfield,
1374                                    'rc_patrolled' => 1 ),
1375                         array( 'rc_logid' => $row->log_id,
1376                                 'rc_timestamp' => $row->log_timestamp ),
1377                         __METHOD__ );
1378         }
1379
1380         /**
1381          * Touch the page's cache invalidation timestamp; this forces cached
1382          * history views to refresh, so any newly hidden or shown fields will
1383          * update properly.
1384          * @param Title $title
1385          */
1386         function updatePage( $title ) {
1387                 $title->invalidateCache();
1388                 $title->purgeSquid();
1389                 $title->touchLinks();
1390                 // Extensions that require referencing previous revisions may need this
1391                 wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$title ) );
1392         }
1393
1394         /**
1395          * Checks for a change in the bitfield for a certain option and updates the
1396          * provided array accordingly.
1397          *
1398          * @param String $desc Description to add to the array if the option was
1399          * enabled / disabled.
1400          * @param int $field The bitmask describing the single option.
1401          * @param int $diff The xor of the old and new bitfields.
1402          * @param array $arr The array to update.
1403          */
1404         function checkItem ( $desc, $field, $diff, $new, &$arr ) {
1405                 if ( $diff & $field ) {
1406                         $arr [ ( $new & $field ) ? 0 : 1 ][] = $desc;
1407                 }
1408         }
1409
1410         /**
1411          * Gets an array describing the changes made to the visibilit of the revision.
1412          * If the resulting array is $arr, then $arr[0] will contain an array of strings
1413          * describing the items that were hidden, $arr[2] will contain an array of strings
1414          * describing the items that were unhidden, and $arr[3] will contain an array with
1415          * a single string, which can be one of "applied restrictions to sysops",
1416          * "removed restrictions from sysops", or null.
1417          *
1418          * @param int $n The new bitfield.
1419          * @param int $o The old bitfield.
1420          * @return An array as described above.
1421          */
1422         function getChanges ( $n, $o ) {
1423                 $diff = $n ^ $o;
1424                 $ret = array ( 0 => array(), 1 => array(), 2 => array() );
1425
1426                 $this->checkItem ( wfMsgForContent ( 'revdelete-content' ),
1427                                 Revision::DELETED_TEXT, $diff, $n, $ret );
1428                 $this->checkItem ( wfMsgForContent ( 'revdelete-summary' ),
1429                                 Revision::DELETED_COMMENT, $diff, $n, $ret );
1430                 $this->checkItem ( wfMsgForContent ( 'revdelete-uname' ),
1431                                 Revision::DELETED_USER, $diff, $n, $ret );
1432
1433                 // Restriction application to sysops
1434                 if ( $diff & Revision::DELETED_RESTRICTED ) {
1435                         if ( $n & Revision::DELETED_RESTRICTED )
1436                                 $ret[2][] = wfMsgForContent ( 'revdelete-restricted' );
1437                         else
1438                                 $ret[2][] = wfMsgForContent ( 'revdelete-unrestricted' );
1439                 }
1440
1441                 return $ret;
1442         }
1443
1444         /**
1445          * Gets a log message to describe the given revision visibility change. This
1446          * message will be of the form "[hid {content, edit summary, username}];
1447          * [unhid {...}][applied restrictions to sysops] for $count revisions: $comment".
1448          *
1449          * @param int $count The number of effected revisions.
1450          * @param int $nbitfield The new bitfield for the revision.
1451          * @param int $obitfield The old bitfield for the revision.
1452          * @param string $comment The comment associated with the change.
1453          * @param bool $isForLog
1454          */
1455         function getLogMessage ( $count, $nbitfield, $obitfield, $comment, $isForLog = false ) {
1456                 global $wgContLang;
1457
1458                 $s = '';
1459                 $changes = $this->getChanges( $nbitfield, $obitfield );
1460
1461                 if ( count ( $changes[0] ) ) {
1462                         $s .= wfMsgForContent ( 'revdelete-hid', implode ( ', ', $changes[0] ) );
1463                 }
1464
1465                 if ( count ( $changes[1] ) ) {
1466                         if ($s) $s .= '; ';
1467
1468                         $s .= wfMsgForContent ( 'revdelete-unhid', implode ( ', ', $changes[1] ) );
1469                 }
1470
1471                 if ( count ( $changes[2] )) {
1472                         if ($s)
1473                                 $s .= ' (' . $changes[2][0] . ')';
1474                         else
1475                                 $s = $changes[2][0];
1476                 }
1477
1478                 $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
1479                 $ret = wfMsgExt ( $msg, array( 'parsemag', 'content' ),
1480                         $s, $wgContLang->formatNum( $count ) );
1481
1482                 if ( $comment )
1483                         $ret .= ": $comment";
1484
1485                 return $ret;
1486
1487         }
1488
1489         /**
1490          * Record a log entry on the action
1491          * @param Title $title, page where item was removed from
1492          * @param int $count the number of revisions altered for this page
1493          * @param int $nbitfield the new _deleted value
1494          * @param int $obitfield the old _deleted value
1495          * @param string $comment
1496          * @param Title $target, the relevant page
1497          * @param string $param, URL param
1498          * @param Array $items
1499          */
1500         function updateLog( $title, $count, $nbitfield, $obitfield, $comment, $target, $param, $items = array() ) {
1501                 // Put things hidden from sysops in the oversight log
1502                 $logtype = ( ($nbitfield | $obitfield) & Revision::DELETED_RESTRICTED ) ? 'suppress' : 'delete';
1503                 $log = new LogPage( $logtype );
1504
1505                 $reason = $this->getLogMessage ( $count, $nbitfield, $obitfield, $comment, $param == 'logid' );
1506
1507                 if( $param == 'logid' ) {
1508                         $params = array( implode( ',', $items) );
1509                         $log->addEntry( 'event', $title, $reason, $params );
1510                 } else {
1511                         // Add params for effected page and ids
1512                         $params = array( $param, implode( ',', $items) );
1513                         $log->addEntry( 'revision', $title, $reason, $params );
1514                 }
1515         }
1516 }