00001 <?php
00010 class SpecialRevisionDelete extends UnlistedSpecialPage {
00012 var $skin;
00013
00015 var $submitClicked;
00016
00018 var $ids;
00019
00021 var $archiveName;
00022
00024 var $token;
00025
00027 var $targetObj;
00028
00030 var $typeName;
00031
00033 var $checks;
00034
00036 var $typeInfo;
00037
00039 var $list;
00040
00045 static $allowedTypes = array(
00046 'revision' => array(
00047 'check-label' => 'revdelete-hide-text',
00048 'deletion-bits' => Revision::DELETED_TEXT,
00049 'success' => 'revdelete-success',
00050 'failure' => 'revdelete-failure',
00051 'list-class' => 'RevDel_RevisionList',
00052 ),
00053 'archive' => array(
00054 'check-label' => 'revdelete-hide-text',
00055 'deletion-bits' => Revision::DELETED_TEXT,
00056 'success' => 'revdelete-success',
00057 'failure' => 'revdelete-failure',
00058 'list-class' => 'RevDel_ArchiveList',
00059 ),
00060 'oldimage'=> array(
00061 'check-label' => 'revdelete-hide-image',
00062 'deletion-bits' => File::DELETED_FILE,
00063 'success' => 'revdelete-success',
00064 'failure' => 'revdelete-failure',
00065 'list-class' => 'RevDel_FileList',
00066 ),
00067 'filearchive' => array(
00068 'check-label' => 'revdelete-hide-image',
00069 'deletion-bits' => File::DELETED_FILE,
00070 'success' => 'revdelete-success',
00071 'failure' => 'revdelete-failure',
00072 'list-class' => 'RevDel_ArchivedFileList',
00073 ),
00074 'logging' => array(
00075 'check-label' => 'revdelete-hide-name',
00076 'deletion-bits' => LogPage::DELETED_ACTION,
00077 'success' => 'logdelete-success',
00078 'failure' => 'logdelete-failure',
00079 'list-class' => 'RevDel_LogList',
00080 ),
00081 );
00082
00084 static $deprecatedTypeMap = array(
00085 'oldid' => 'revision',
00086 'artimestamp' => 'archive',
00087 'oldimage' => 'oldimage',
00088 'fileid' => 'filearchive',
00089 'logid' => 'logging',
00090 );
00091
00092 public function __construct() {
00093 parent::__construct( 'Revisiondelete', 'deletedhistory' );
00094 }
00095
00096 public function execute( $par ) {
00097 global $wgOut, $wgUser, $wgRequest;
00098 if( !$wgUser->isAllowed( 'deletedhistory' ) ) {
00099 $wgOut->permissionRequired( 'deletedhistory' );
00100 return;
00101 } else if( wfReadOnly() ) {
00102 $wgOut->readOnlyPage();
00103 return;
00104 }
00105 $this->mIsAllowed = $wgUser->isAllowed('deleterevision');
00106 $this->skin = $wgUser->getSkin();
00107 $this->setHeaders();
00108 $this->outputHeader();
00109 $this->submitClicked = $wgRequest->wasPosted() && $wgRequest->getBool( 'wpSubmit' );
00110 # Handle our many different possible input types.
00111 $ids = $wgRequest->getVal( 'ids' );
00112 if ( !is_null( $ids ) ) {
00113 # Allow CSV, for backwards compatibility, or a single ID for show/hide links
00114 $this->ids = explode( ',', $ids );
00115 } else {
00116 # Array input
00117 $this->ids = array_keys( $wgRequest->getArray('ids',array()) );
00118 }
00119
00120 $this->ids = array_unique( array_filter( $this->ids ) );
00121
00122 if ( $wgRequest->getVal( 'action' ) == 'historysubmit' ) {
00123 # For show/hide form submission from history page
00124 $this->targetObj = $GLOBALS['wgTitle'];
00125 $this->typeName = 'revision';
00126 } else {
00127 $this->typeName = $wgRequest->getVal( 'type' );
00128 $this->targetObj = Title::newFromText( $wgRequest->getText( 'target' ) );
00129 }
00130
00131 # For reviewing deleted files...
00132 $this->archiveName = $wgRequest->getVal( 'file' );
00133 $this->token = $wgRequest->getVal( 'token' );
00134 if ( $this->archiveName && $this->targetObj ) {
00135 $this->tryShowFile( $this->archiveName );
00136 return;
00137 }
00138
00139 if ( isset( self::$deprecatedTypeMap[$this->typeName] ) ) {
00140 $this->typeName = self::$deprecatedTypeMap[$this->typeName];
00141 }
00142
00143 # No targets?
00144 if( !isset( self::$allowedTypes[$this->typeName] ) || count( $this->ids ) == 0 ) {
00145 $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
00146 return;
00147 }
00148 $this->typeInfo = self::$allowedTypes[$this->typeName];
00149
00150 # If we have revisions, get the title from the first one
00151 # since they should all be from the same page. This allows
00152 # for more flexibility with page moves...
00153 if( $this->typeName == 'revision' ) {
00154 $rev = Revision::newFromId( $this->ids[0] );
00155 $this->targetObj = $rev ? $rev->getTitle() : $this->targetObj;
00156 }
00157
00158 $this->otherReason = $wgRequest->getVal( 'wpReason' );
00159 # We need a target page!
00160 if( is_null($this->targetObj) ) {
00161 $wgOut->addWikiMsg( 'undelete-header' );
00162 return;
00163 }
00164 # Give a link to the logs/hist for this page
00165 $this->showConvenienceLinks();
00166
00167 # Initialise checkboxes
00168 $this->checks = array(
00169 array( $this->typeInfo['check-label'], 'wpHidePrimary', $this->typeInfo['deletion-bits'] ),
00170 array( 'revdelete-hide-comment', 'wpHideComment', Revision::DELETED_COMMENT ),
00171 array( 'revdelete-hide-user', 'wpHideUser', Revision::DELETED_USER )
00172 );
00173 if( $wgUser->isAllowed('suppressrevision') ) {
00174 $this->checks[] = array( 'revdelete-hide-restricted',
00175 'wpHideRestricted', Revision::DELETED_RESTRICTED );
00176 }
00177
00178 # Either submit or create our form
00179 if( $this->mIsAllowed && $this->submitClicked ) {
00180 $this->submit( $wgRequest );
00181 } else {
00182 $this->showForm();
00183 }
00184
00185 $qc = $this->getLogQueryCond();
00186 # Show relevant lines from the deletion log
00187 $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
00188 LogEventsList::showLogExtract( $wgOut, 'delete',
00189 $this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) );
00190 # Show relevant lines from the suppression log
00191 if( $wgUser->isAllowed( 'suppressionlog' ) ) {
00192 $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'suppress' ) ) . "</h2>\n" );
00193 LogEventsList::showLogExtract( $wgOut, 'suppress',
00194 $this->targetObj->getPrefixedText(), '', array( 'lim' => 25, 'conds' => $qc ) );
00195 }
00196 }
00197
00201 protected function showConvenienceLinks() {
00202 global $wgOut, $wgUser, $wgLang;
00203 # Give a link to the logs/hist for this page
00204 if( $this->targetObj ) {
00205 $links = array();
00206 $links[] = $this->skin->linkKnown(
00207 SpecialPage::getTitleFor( 'Log' ),
00208 wfMsgHtml( 'viewpagelogs' ),
00209 array(),
00210 array( 'page' => $this->targetObj->getPrefixedText() )
00211 );
00212 if ( $this->targetObj->getNamespace() != NS_SPECIAL ) {
00213 # Give a link to the page history
00214 $links[] = $this->skin->linkKnown(
00215 $this->targetObj,
00216 wfMsgHtml( 'pagehist' ),
00217 array(),
00218 array( 'action' => 'history' )
00219 );
00220 # Link to deleted edits
00221 if( $wgUser->isAllowed('undelete') ) {
00222 $undelete = SpecialPage::getTitleFor( 'Undelete' );
00223 $links[] = $this->skin->linkKnown(
00224 $undelete,
00225 wfMsgHtml( 'deletedhist' ),
00226 array(),
00227 array( 'target' => $this->targetObj->getPrefixedDBkey() )
00228 );
00229 }
00230 }
00231 # Logs themselves don't have histories or archived revisions
00232 $wgOut->setSubtitle( '<p>' . $wgLang->pipeList( $links ) . '</p>' );
00233 }
00234 }
00235
00239 protected function getLogQueryCond() {
00240 $conds = array();
00241
00242 $conds['log_type'] = array('delete','suppress');
00243 $conds['log_action'] = $this->getList()->getLogAction();
00244 $conds['ls_field'] = RevisionDeleter::getRelationType( $this->typeName );
00245 $conds['ls_value'] = $this->ids;
00246 return $conds;
00247 }
00248
00253 protected function tryShowFile( $archiveName ) {
00254 global $wgOut, $wgRequest, $wgUser, $wgLang;
00255
00256 $repo = RepoGroup::singleton()->getLocalRepo();
00257 $oimage = $repo->newFromArchiveName( $this->targetObj, $archiveName );
00258 $oimage->load();
00259
00260 if ( !$oimage->exists() ) {
00261 $wgOut->addWikiMsg( 'revdelete-no-file' );
00262 return;
00263 }
00264 if( !$oimage->userCan(File::DELETED_FILE) ) {
00265 if( $oimage->isDeleted( File::DELETED_RESTRICTED ) ) {
00266 $wgOut->permissionRequired( 'suppressrevision' );
00267 } else {
00268 $wgOut->permissionRequired( 'deletedtext' );
00269 }
00270 return;
00271 }
00272 if ( !$wgUser->matchEditToken( $this->token, $archiveName ) ) {
00273 $wgOut->addWikiMsg( 'revdelete-show-file-confirm',
00274 $this->targetObj->getText(),
00275 $wgLang->date( $oimage->getTimestamp() ),
00276 $wgLang->time( $oimage->getTimestamp() ) );
00277 $wgOut->addHTML(
00278 Xml::openElement( 'form', array(
00279 'method' => 'POST',
00280 'action' => $this->getTitle()->getLocalUrl(
00281 'target=' . urlencode( $oimage->getName() ) .
00282 '&file=' . urlencode( $archiveName ) .
00283 '&token=' . urlencode( $wgUser->editToken( $archiveName ) ) )
00284 )
00285 ) .
00286 Xml::submitButton( wfMsg( 'revdelete-show-file-submit' ) ) .
00287 '</form>'
00288 );
00289 return;
00290 }
00291 $wgOut->disable();
00292 # We mustn't allow the output to be Squid cached, otherwise
00293 # if an admin previews a deleted image, and it's cached, then
00294 # a user without appropriate permissions can toddle off and
00295 # nab the image, and Squid will serve it
00296 $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
00297 $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
00298 $wgRequest->response()->header( 'Pragma: no-cache' );
00299
00300 # Stream the file to the client
00301 global $IP;
00302 require_once( "$IP/includes/StreamFile.php" );
00303 $key = $oimage->getStorageKey();
00304 $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
00305 wfStreamFile( $path );
00306 }
00307
00311 protected function getList() {
00312 if ( is_null( $this->list ) ) {
00313 $class = $this->typeInfo['list-class'];
00314 $this->list = new $class( $this, $this->targetObj, $this->ids );
00315 }
00316 return $this->list;
00317 }
00318
00323 protected function showForm() {
00324 global $wgOut, $wgUser, $wgLang;
00325 $UserAllowed = true;
00326
00327 if ( $this->typeName == 'logging' ) {
00328 $wgOut->addWikiMsg( 'logdelete-selected', $wgLang->formatNum( count($this->ids) ) );
00329 } else {
00330 $wgOut->addWikiMsg( 'revdelete-selected',
00331 $this->targetObj->getPrefixedText(), count( $this->ids ) );
00332 }
00333
00334 $wgOut->addHTML( "<ul>" );
00335
00336 $where = $revObjs = array();
00337
00338 $numRevisions = 0;
00339
00340 $list = $this->getList();
00341 for ( $list->reset(); $list->current(); $list->next() ) {
00342 $item = $list->current();
00343 if ( !$item->canView() ) {
00344 if( !$this->submitClicked ) {
00345 $wgOut->permissionRequired( 'suppressrevision' );
00346 return;
00347 }
00348 $UserAllowed = false;
00349 }
00350 $numRevisions++;
00351 $wgOut->addHTML( $item->getHTML() );
00352 }
00353
00354 if( !$numRevisions ) {
00355 $wgOut->showErrorPage( 'revdelete-nooldid-title', 'revdelete-nooldid-text' );
00356 return;
00357 }
00358
00359 $wgOut->addHTML( "</ul>" );
00360
00361 $this->addUsageText();
00362
00363
00364 if( !$UserAllowed ) return;
00365
00366
00367 if( $this->mIsAllowed ) {
00368 $out = Xml::openElement( 'form', array( 'method' => 'post',
00369 'action' => $this->getTitle()->getLocalUrl( array( 'action' => 'submit' ) ),
00370 'id' => 'mw-revdel-form-revisions' ) ) .
00371 Xml::fieldset( wfMsg( 'revdelete-legend' ) ) .
00372 $this->buildCheckBoxes() .
00373 Xml::openElement( 'table' ) .
00374 "<tr>\n" .
00375 '<td class="mw-label">' .
00376 Xml::label( wfMsg( 'revdelete-log' ), 'wpRevDeleteReasonList' ) .
00377 '</td>' .
00378 '<td class="mw-input">' .
00379 Xml::listDropDown( 'wpRevDeleteReasonList',
00380 wfMsgForContent( 'revdelete-reason-dropdown' ),
00381 wfMsgForContent( 'revdelete-reasonotherlist' ), '', 'wpReasonDropDown', 1
00382 ) .
00383 '</td>' .
00384 "</tr><tr>\n" .
00385 '<td class="mw-label">' .
00386 Xml::label( wfMsg( 'revdelete-otherreason' ), 'wpReason' ) .
00387 '</td>' .
00388 '<td class="mw-input">' .
00389 Xml::input( 'wpReason', 60, $this->otherReason, array( 'id' => 'wpReason' ) ) .
00390 '</td>' .
00391 "</tr><tr>\n" .
00392 '<td></td>' .
00393 '<td class="mw-submit">' .
00394 Xml::submitButton( wfMsgExt('revdelete-submit','parsemag',$numRevisions),
00395 array( 'name' => 'wpSubmit' ) ) .
00396 '</td>' .
00397 "</tr>\n" .
00398 Xml::closeElement( 'table' ) .
00399 Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
00400 Xml::hidden( 'target', $this->targetObj->getPrefixedText() ) .
00401 Xml::hidden( 'type', $this->typeName ) .
00402 Xml::hidden( 'ids', implode( ',', $this->ids ) ) .
00403 Xml::closeElement( 'fieldset' ) . "\n";
00404 } else {
00405 $out = '';
00406 }
00407 if( $this->mIsAllowed ) {
00408 $out .= Xml::closeElement( 'form' ) . "\n";
00409
00410 if( $wgUser->isAllowed( 'editinterface' ) ) {
00411 $title = Title::makeTitle( NS_MEDIAWIKI, 'revdelete-reason-dropdown' );
00412 $link = $wgUser->getSkin()->link(
00413 $title,
00414 wfMsgHtml( 'revdelete-edit-reasonlist' ),
00415 array(),
00416 array( 'action' => 'edit' )
00417 );
00418 $out .= Xml::tags( 'p', array( 'class' => 'mw-revdel-editreasons' ), $link ) . "\n";
00419 }
00420 }
00421 $wgOut->addHTML( $out );
00422 }
00423
00428 protected function addUsageText() {
00429 global $wgOut, $wgUser;
00430 $wgOut->addWikiMsg( 'revdelete-text' );
00431 if( $wgUser->isAllowed( 'suppressrevision' ) ) {
00432 $wgOut->addWikiMsg( 'revdelete-suppress-text' );
00433 }
00434 if( $this->mIsAllowed ) {
00435 $wgOut->addWikiMsg( 'revdelete-confirm' );
00436 }
00437 }
00438
00442 protected function buildCheckBoxes() {
00443 global $wgRequest;
00444
00445 $html = '<table>';
00446
00447 $list = $this->getList();
00448 if( $list->length() == 1 ) {
00449 $list->reset();
00450 $bitfield = $list->current()->getBits();
00451 if( $this->submitClicked ) {
00452 $bitfield = $this->extractBitfield( $this->extractBitParams($wgRequest), $bitfield );
00453 }
00454 foreach( $this->checks as $item ) {
00455 list( $message, $name, $field ) = $item;
00456 $innerHTML = Xml::checkLabel( wfMsg($message), $name, $name, $bitfield & $field );
00457 if( $field == Revision::DELETED_RESTRICTED )
00458 $innerHTML = "<b>$innerHTML</b>";
00459 $line = Xml::tags( 'td', array( 'class' => 'mw-input' ), $innerHTML );
00460 $html .= "<tr>$line</tr>\n";
00461 }
00462
00463 } else {
00464 $html .= '<tr>';
00465 $html .= '<th class="mw-revdel-checkbox">'.wfMsgHtml('revdelete-radio-same').'</th>';
00466 $html .= '<th class="mw-revdel-checkbox">'.wfMsgHtml('revdelete-radio-unset').'</th>';
00467 $html .= '<th class="mw-revdel-checkbox">'.wfMsgHtml('revdelete-radio-set').'</th>';
00468 $html .= "<th></th></tr>\n";
00469 foreach( $this->checks as $item ) {
00470 list( $message, $name, $field ) = $item;
00471
00472 if( $this->submitClicked ) {
00473 $selected = $wgRequest->getInt( $name, 0 );
00474 } else {
00475 $selected = -1;
00476 }
00477 $line = '<td class="mw-revdel-checkbox">' . Xml::radio( $name, -1, $selected == -1 ) . '</td>';
00478 $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 0, $selected == 0 ) . '</td>';
00479 $line .= '<td class="mw-revdel-checkbox">' . Xml::radio( $name, 1, $selected == 1 ) . '</td>';
00480 $label = wfMsgHtml($message);
00481 if( $field == Revision::DELETED_RESTRICTED ) {
00482 $label = "<b>$label</b>";
00483 }
00484 $line .= "<td>$label</td>";
00485 $html .= "<tr>$line</tr>\n";
00486 }
00487 }
00488
00489 $html .= '</table>';
00490 return $html;
00491 }
00492
00497 protected function submit( $request ) {
00498 global $wgUser, $wgOut;
00499 # Check edit token on submission
00500 if( $this->submitClicked && !$wgUser->matchEditToken( $request->getVal('wpEditToken') ) ) {
00501 $wgOut->addWikiMsg( 'sessionfailure' );
00502 return false;
00503 }
00504 $bitParams = $this->extractBitParams( $request );
00505 $listReason = $request->getText( 'wpRevDeleteReasonList', 'other' );
00506 $comment = $listReason;
00507 if( $comment != 'other' && $this->otherReason != '' ) {
00508
00509 $comment .= wfMsgForContent( 'colon-separator' ) . $this->otherReason;
00510 } elseif( $comment == 'other' ) {
00511 $comment = $this->otherReason;
00512 }
00513 # Can the user set this field?
00514 if( $bitParams[Revision::DELETED_RESTRICTED]==1 && !$wgUser->isAllowed('suppressrevision') ) {
00515 $wgOut->permissionRequired( 'suppressrevision' );
00516 return false;
00517 }
00518 # If the save went through, go to success message...
00519 $status = $this->save( $bitParams, $comment, $this->targetObj );
00520 if ( $status->isGood() ) {
00521 $this->success();
00522 return true;
00523 # ...otherwise, bounce back to form...
00524 } else {
00525 $this->failure( $status );
00526 }
00527 return false;
00528 }
00529
00533 protected function success() {
00534 global $wgOut;
00535 $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
00536 $wgOut->wrapWikiMsg( '<span class="success">$1</span>', $this->typeInfo['success'] );
00537 $this->list->reloadFromMaster();
00538 $this->showForm();
00539 }
00540
00544 protected function failure( $status ) {
00545 global $wgOut;
00546 $wgOut->setPagetitle( wfMsg( 'actionfailed' ) );
00547 $wgOut->addWikiText( $status->getWikiText( $this->typeInfo['failure'] ) );
00548 $this->showForm();
00549 }
00550
00556 protected function extractBitParams( $request ) {
00557 $bitfield = array();
00558 foreach( $this->checks as $item ) {
00559 list( , $name, $field ) = $item;
00560 $val = $request->getInt( $name, 0 );
00561 if( $val < -1 || $val > 1) {
00562 $val = -1;
00563 }
00564 $bitfield[$field] = $val;
00565 }
00566 if( !isset($bitfield[Revision::DELETED_RESTRICTED]) ) {
00567 $bitfield[Revision::DELETED_RESTRICTED] = 0;
00568 }
00569 return $bitfield;
00570 }
00571
00578 public static function extractBitfield( $bitPars, $oldfield ) {
00579
00580 $newBits = 0;
00581 foreach( $bitPars as $const => $val ) {
00582 if( $val == 1 ) {
00583 $newBits |= $const;
00584 } else if( $val == -1 ) {
00585 $newBits |= ($oldfield & $const);
00586 }
00587 }
00588 return $newBits;
00589 }
00590
00594 protected function save( $bitfield, $reason, $title ) {
00595 return $this->getList()->setVisibility(
00596 array( 'value' => $bitfield, 'comment' => $reason )
00597 );
00598 }
00599 }
00600
00605 class RevisionDeleter {
00617 protected static function checkItem( $desc, $field, $diff, $new, &$arr ) {
00618 if( $diff & $field ) {
00619 $arr[ ( $new & $field ) ? 0 : 1 ][] = $desc;
00620 }
00621 }
00622
00635 protected static function getChanges( $n, $o ) {
00636 $diff = $n ^ $o;
00637 $ret = array( 0 => array(), 1 => array(), 2 => array() );
00638
00639 self::checkItem( wfMsgForContent( 'revdelete-content' ),
00640 Revision::DELETED_TEXT, $diff, $n, $ret );
00641 self::checkItem( wfMsgForContent( 'revdelete-summary' ),
00642 Revision::DELETED_COMMENT, $diff, $n, $ret );
00643 self::checkItem( wfMsgForContent( 'revdelete-uname' ),
00644 Revision::DELETED_USER, $diff, $n, $ret );
00645
00646 if( $diff & Revision::DELETED_RESTRICTED ) {
00647 if( $n & Revision::DELETED_RESTRICTED )
00648 $ret[2][] = wfMsgForContent( 'revdelete-restricted' );
00649 else
00650 $ret[2][] = wfMsgForContent( 'revdelete-unrestricted' );
00651 }
00652 return $ret;
00653 }
00654
00665 public static function getLogMessage( $count, $nbitfield, $obitfield, $isForLog = false ) {
00666 global $wgLang;
00667 $s = '';
00668 $changes = self::getChanges( $nbitfield, $obitfield );
00669 if( count( $changes[0] ) ) {
00670 $s .= wfMsgForContent( 'revdelete-hid', implode( ', ', $changes[0] ) );
00671 }
00672 if( count( $changes[1] ) ) {
00673 if ($s) $s .= '; ';
00674 $s .= wfMsgForContent( 'revdelete-unhid', implode( ', ', $changes[1] ) );
00675 }
00676 if( count( $changes[2] ) ) {
00677 $s .= $s ? ' (' . $changes[2][0] . ')' : $changes[2][0];
00678 }
00679 $msg = $isForLog ? 'logdelete-log-message' : 'revdelete-log-message';
00680 return wfMsgExt( $msg, array( 'parsemag', 'content' ), $s, $wgLang->formatNum($count) );
00681
00682 }
00683
00684
00685
00686
00687
00688 public static function getRelationType( $typeName ) {
00689 if ( isset( SpecialRevisionDelete::$deprecatedTypeMap[$typeName] ) ) {
00690 $typeName = SpecialRevisionDelete::$deprecatedTypeMap[$typeName];
00691 }
00692 if ( isset( SpecialRevisionDelete::$allowedTypes[$typeName] ) ) {
00693 $class = SpecialRevisionDelete::$allowedTypes[$typeName]['list-class'];
00694 $list = new $class( null, null, null );
00695 return $list->getIdField();
00696 } else {
00697 return null;
00698 }
00699 }
00700 }
00701
00705 abstract class RevDel_List {
00706 var $special, $title, $ids, $res, $current;
00707 var $type = null;
00708 var $idField = null;
00709 var $dateField = false;
00710 var $authorIdField = false;
00711 var $authorNameField = false;
00712
00718 public function __construct( $special, $title, $ids ) {
00719 $this->special = $special;
00720 $this->title = $title;
00721 $this->ids = $ids;
00722 }
00723
00727 public function getType() {
00728 return $this->type;
00729 }
00730
00734 public function getIdField() {
00735 return $this->idField;
00736 }
00737
00741 public function getTimestampField() {
00742 return $this->dateField;
00743 }
00744
00748 public function getAuthorIdField() {
00749 return $this->authorIdField;
00750 }
00751
00755 public function getAuthorNameField() {
00756 return $this->authorNameField;
00757 }
00767 public function setVisibility( $params ) {
00768 $bitPars = $params['value'];
00769 $comment = $params['comment'];
00770
00771 $this->res = false;
00772 $dbw = wfGetDB( DB_MASTER );
00773 $this->doQuery( $dbw );
00774 $dbw->begin();
00775 $status = Status::newGood();
00776 $missing = array_flip( $this->ids );
00777 $this->clearFileOps();
00778 $idsForLog = array();
00779 $authorIds = $authorIPs = array();
00780
00781 for ( $this->reset(); $this->current(); $this->next() ) {
00782 $item = $this->current();
00783 unset( $missing[ $item->getId() ] );
00784
00785 $oldBits = $item->getBits();
00786
00787 $newBits = SpecialRevisionDelete::extractBitfield( $bitPars, $oldBits );
00788
00789 if ( $oldBits == $newBits ) {
00790 $status->warning( 'revdelete-no-change', $item->formatDate(), $item->formatTime() );
00791 $status->failCount++;
00792 continue;
00793 } elseif ( $oldBits == 0 && $newBits != 0 ) {
00794 $opType = 'hide';
00795 } elseif ( $oldBits != 0 && $newBits == 0 ) {
00796 $opType = 'show';
00797 } else {
00798 $opType = 'modify';
00799 }
00800
00801 if ( $item->isHideCurrentOp( $newBits ) ) {
00802
00803 $status->error( 'revdelete-hide-current', $item->formatDate(), $item->formatTime() );
00804 $status->failCount++;
00805 continue;
00806 }
00807 if ( !$item->canView() ) {
00808
00809 $msg = ($opType == 'show') ?
00810 'revdelete-show-no-access' : 'revdelete-modify-no-access';
00811 $status->error( $msg, $item->formatDate(), $item->formatTime() );
00812 $status->failCount++;
00813 continue;
00814 }
00815
00816 if( $newBits == Revision::DELETED_RESTRICTED ) {
00817 $status->warning( 'revdelete-only-restricted', $item->formatDate(), $item->formatTime() );
00818 $status->failCount++;
00819 continue;
00820 }
00821
00822
00823 $ok = $item->setBits( $newBits );
00824
00825 if ( $ok ) {
00826 $idsForLog[] = $item->getId();
00827 $status->successCount++;
00828 if( $item->getAuthorId() > 0 ) {
00829 $authorIds[] = $item->getAuthorId();
00830 } else if( IP::isIPAddress( $item->getAuthorName() ) ) {
00831 $authorIPs[] = $item->getAuthorName();
00832 }
00833 } else {
00834 $status->error( 'revdelete-concurrent-change', $item->formatDate(), $item->formatTime() );
00835 $status->failCount++;
00836 }
00837 }
00838
00839
00840 foreach ( $missing as $id => $unused ) {
00841 $status->error( 'revdelete-modify-missing', $id );
00842 $status->failCount++;
00843 }
00844
00845 if ( $status->successCount == 0 ) {
00846 $status->ok = false;
00847 $dbw->rollback();
00848 return $status;
00849 }
00850
00851
00852 $successCount = $status->successCount;
00853
00854
00855 $status->merge( $this->doPreCommitUpdates() );
00856 if ( !$status->isOK() ) {
00857
00858 $dbw->rollback();
00859 return $status;
00860 }
00861
00862
00863 $this->updateLog( array(
00864 'title' => $this->title,
00865 'count' => $successCount,
00866 'newBits' => $newBits,
00867 'oldBits' => $oldBits,
00868 'comment' => $comment,
00869 'ids' => $idsForLog,
00870 'authorIds' => $authorIds,
00871 'authorIPs' => $authorIPs
00872 ) );
00873 $dbw->commit();
00874
00875
00876 $status->merge( $this->doPostCommitUpdates() );
00877 return $status;
00878 }
00879
00884 function reloadFromMaster() {
00885 $dbw = wfGetDB( DB_MASTER );
00886 $this->res = $this->doQuery( $dbw );
00887 }
00888
00900 protected function updateLog( $params ) {
00901
00902 $field = RevisionDeleter::getRelationType( $this->getType() );
00903 if( !$field ) {
00904 throw new MWException( "Bad log URL param type!" );
00905 }
00906
00907 if ( ( $params['newBits'] | $params['oldBits'] ) & $this->getSuppressBit() ) {
00908 $logType = 'suppress';
00909 } else {
00910 $logType = 'delete';
00911 }
00912
00913 $logParams = $this->getLogParams( $params );
00914
00915 $log = new LogPage( $logType );
00916 $logid = $log->addEntry( $this->getLogAction(), $params['title'],
00917 $params['comment'], $logParams );
00918
00919 $log->addRelations( $field, $params['ids'], $logid );
00920 $log->addRelations( 'target_author_id', $params['authorIds'], $logid );
00921 $log->addRelations( 'target_author_ip', $params['authorIPs'], $logid );
00922 }
00923
00927 public function getLogAction() {
00928 return 'revision';
00929 }
00930
00936 public function getLogParams( $params ) {
00937 return array(
00938 $this->getType(),
00939 implode( ',', $params['ids'] ),
00940 "ofield={$params['oldBits']}",
00941 "nfield={$params['newBits']}"
00942 );
00943 }
00944
00948 protected function initCurrent() {
00949 $row = $this->res->current();
00950 if ( $row ) {
00951 $this->current = $this->newItem( $row );
00952 } else {
00953 $this->current = false;
00954 }
00955 }
00956
00961 public function reset() {
00962 if ( !$this->res ) {
00963 $this->res = $this->doQuery( wfGetDB( DB_SLAVE ) );
00964 } else {
00965 $this->res->rewind();
00966 }
00967 $this->initCurrent();
00968 return $this->current;
00969 }
00970
00974 public function current() {
00975 return $this->current;
00976 }
00977
00981 public function next() {
00982 $this->res->next();
00983 $this->initCurrent();
00984 return $this->current;
00985 }
00986
00990 public function length() {
00991 if( !$this->res ) {
00992 return 0;
00993 } else {
00994 return $this->res->numRows();
00995 }
00996 }
00997
01002 public function clearFileOps() {
01003 }
01004
01010 public function doPreCommitUpdates() {
01011 return Status::newGood();
01012 }
01013
01019 public function doPostCommitUpdates() {
01020 return Status::newGood();
01021 }
01022
01027 abstract public function newItem( $row );
01028
01033 abstract public function doQuery( $db );
01034
01038 abstract public function getSuppressBit();
01039 }
01040
01044 abstract class RevDel_Item {
01046 var $special;
01047
01049 var $list;
01050
01052 var $row;
01053
01058 public function __construct( $list, $row ) {
01059 $this->special = $list->special;
01060 $this->list = $list;
01061 $this->row = $row;
01062 }
01063
01067 public function getId() {
01068 $field = $this->list->getIdField();
01069 return $this->row->$field;
01070 }
01071
01075 public function formatDate() {
01076 global $wgLang;
01077 return $wgLang->date( $this->getTimestamp() );
01078 }
01079
01083 public function formatTime() {
01084 global $wgLang;
01085 return $wgLang->time( $this->getTimestamp() );
01086 }
01087
01091 public function getTimestamp() {
01092 $field = $this->list->getTimestampField();
01093 return wfTimestamp( TS_MW, $this->row->$field );
01094 }
01095
01099 public function getAuthorId() {
01100 $field = $this->list->getAuthorIdField();
01101 return intval( $this->row->$field );
01102 }
01103
01107 public function getAuthorName() {
01108 $field = $this->list->getAuthorNameField();
01109 return strval( $this->row->$field );
01110 }
01111
01117 public function isHideCurrentOp( $newBits ) {
01118 return false;
01119 }
01120
01124 abstract public function canView();
01125
01129 abstract public function canViewContent();
01130
01134 abstract public function getBits();
01135
01140 abstract public function getHTML();
01141
01152 abstract public function setBits( $newBits );
01153 }
01154
01158 class RevDel_RevisionList extends RevDel_List {
01159 var $currentRevId;
01160 var $type = 'revision';
01161 var $idField = 'rev_id';
01162 var $dateField = 'rev_timestamp';
01163 var $authorIdField = 'rev_user';
01164 var $authorNameField = 'rev_user_text';
01165
01166 public function doQuery( $db ) {
01167 $ids = array_map( 'intval', $this->ids );
01168 return $db->select( array('revision','page'), '*',
01169 array(
01170 'rev_page' => $this->title->getArticleID(),
01171 'rev_id' => $ids,
01172 'rev_page = page_id'
01173 ),
01174 __METHOD__,
01175 array( 'ORDER BY' => 'rev_id DESC' )
01176 );
01177 }
01178
01179 public function newItem( $row ) {
01180 return new RevDel_RevisionItem( $this, $row );
01181 }
01182
01183 public function getCurrent() {
01184 if ( is_null( $this->currentRevId ) ) {
01185 $dbw = wfGetDB( DB_MASTER );
01186 $this->currentRevId = $dbw->selectField(
01187 'page', 'page_latest', $this->title->pageCond(), __METHOD__ );
01188 }
01189 return $this->currentRevId;
01190 }
01191
01192 public function getSuppressBit() {
01193 return Revision::DELETED_RESTRICTED;
01194 }
01195
01196 public function doPreCommitUpdates() {
01197 $this->title->invalidateCache();
01198 return Status::newGood();
01199 }
01200
01201 public function doPostCommitUpdates() {
01202 $this->title->purgeSquid();
01203
01204 wfRunHooks( 'ArticleRevisionVisiblitySet', array( &$this->title ) );
01205 return Status::newGood();
01206 }
01207 }
01208
01212 class RevDel_RevisionItem extends RevDel_Item {
01213 var $revision;
01214
01215 public function __construct( $list, $row ) {
01216 parent::__construct( $list, $row );
01217 $this->revision = new Revision( $row );
01218 }
01219
01220 public function canView() {
01221 return $this->revision->userCan( Revision::DELETED_RESTRICTED );
01222 }
01223
01224 public function canViewContent() {
01225 return $this->revision->userCan( Revision::DELETED_TEXT );
01226 }
01227
01228 public function getBits() {
01229 return $this->revision->mDeleted;
01230 }
01231
01232 public function setBits( $bits ) {
01233 $dbw = wfGetDB( DB_MASTER );
01234
01235 $dbw->update( 'revision',
01236 array( 'rev_deleted' => $bits ),
01237 array(
01238 'rev_id' => $this->revision->getId(),
01239 'rev_page' => $this->revision->getPage(),
01240 'rev_deleted' => $this->getBits()
01241 ),
01242 __METHOD__
01243 );
01244 if ( !$dbw->affectedRows() ) {
01245
01246 return false;
01247 }
01248
01249 $dbw->update( 'recentchanges',
01250 array(
01251 'rc_deleted' => $bits,
01252 'rc_patrolled' => 1
01253 ),
01254 array(
01255 'rc_this_oldid' => $this->revision->getId(),
01256
01257 'rc_timestamp' => $dbw->timestamp( $this->revision->getTimestamp() ),
01258 ),
01259 __METHOD__
01260 );
01261 return true;
01262 }
01263
01264 public function isDeleted() {
01265 return $this->revision->isDeleted( Revision::DELETED_TEXT );
01266 }
01267
01268 public function isHideCurrentOp( $newBits ) {
01269 return ( $newBits & Revision::DELETED_TEXT )
01270 && $this->list->getCurrent() == $this->getId();
01271 }
01272
01277 protected function getRevisionLink() {
01278 global $wgLang;
01279 $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
01280 if ( $this->isDeleted() && !$this->canViewContent() ) {
01281 return $date;
01282 }
01283 return $this->special->skin->link(
01284 $this->list->title,
01285 $date,
01286 array(),
01287 array(
01288 'oldid' => $this->revision->getId(),
01289 'unhide' => 1
01290 )
01291 );
01292 }
01293
01298 protected function getDiffLink() {
01299 if ( $this->isDeleted() && !$this->canViewContent() ) {
01300 return wfMsgHtml('diff');
01301 } else {
01302 return
01303 $this->special->skin->link(
01304 $this->list->title,
01305 wfMsgHtml('diff'),
01306 array(),
01307 array(
01308 'diff' => $this->revision->getId(),
01309 'oldid' => 'prev',
01310 'unhide' => 1
01311 ),
01312 array(
01313 'known',
01314 'noclasses'
01315 )
01316 );
01317 }
01318 }
01319
01320 public function getHTML() {
01321 $difflink = $this->getDiffLink();
01322 $revlink = $this->getRevisionLink();
01323 $userlink = $this->special->skin->revUserLink( $this->revision );
01324 $comment = $this->special->skin->revComment( $this->revision );
01325 if ( $this->isDeleted() ) {
01326 $revlink = "<span class=\"history-deleted\">$revlink</span>";
01327 }
01328 return "<li>($difflink) $revlink $userlink $comment</li>";
01329 }
01330 }
01331
01335 class RevDel_ArchiveList extends RevDel_RevisionList {
01336 var $type = 'archive';
01337 var $idField = 'ar_timestamp';
01338 var $dateField = 'ar_timestamp';
01339 var $authorIdField = 'ar_user';
01340 var $authorNameField = 'ar_user_text';
01341
01342 public function doQuery( $db ) {
01343 $timestamps = array();
01344 foreach ( $this->ids as $id ) {
01345 $timestamps[] = $db->timestamp( $id );
01346 }
01347 return $db->select( 'archive', '*',
01348 array(
01349 'ar_namespace' => $this->title->getNamespace(),
01350 'ar_title' => $this->title->getDBkey(),
01351 'ar_timestamp' => $timestamps
01352 ),
01353 __METHOD__,
01354 array( 'ORDER BY' => 'ar_timestamp DESC' )
01355 );
01356 }
01357
01358 public function newItem( $row ) {
01359 return new RevDel_ArchiveItem( $this, $row );
01360 }
01361
01362 public function doPreCommitUpdates() {
01363 return Status::newGood();
01364 }
01365
01366 public function doPostCommitUpdates() {
01367 return Status::newGood();
01368 }
01369 }
01370
01374 class RevDel_ArchiveItem extends RevDel_RevisionItem {
01375 public function __construct( $list, $row ) {
01376 RevDel_Item::__construct( $list, $row );
01377 $this->revision = Revision::newFromArchiveRow( $row,
01378 array( 'page' => $this->list->title->getArticleId() ) );
01379 }
01380
01381 public function getId() {
01382 # Convert DB timestamp to MW timestamp
01383 return $this->revision->getTimestamp();
01384 }
01385
01386 public function setBits( $bits ) {
01387 $dbw = wfGetDB( DB_MASTER );
01388 $dbw->update( 'archive',
01389 array( 'ar_deleted' => $bits ),
01390 array( 'ar_namespace' => $this->list->title->getNamespace(),
01391 'ar_title' => $this->list->title->getDBkey(),
01392
01393 'ar_timestamp' => $this->row->ar_timestamp,
01394 'ar_rev_id' => $this->row->ar_rev_id,
01395 'ar_deleted' => $this->getBits()
01396 ),
01397 __METHOD__ );
01398 return (bool)$dbw->affectedRows();
01399 }
01400
01401 protected function getRevisionLink() {
01402 global $wgLang;
01403 $undelete = SpecialPage::getTitleFor( 'Undelete' );
01404 $date = $wgLang->timeanddate( $this->revision->getTimestamp(), true );
01405 if ( $this->isDeleted() && !$this->canViewContent() ) {
01406 return $date;
01407 }
01408 return $this->special->skin->link( $undelete, $date, array(),
01409 array(
01410 'target' => $this->list->title->getPrefixedText(),
01411 'timestamp' => $this->revision->getTimestamp()
01412 ) );
01413 }
01414
01415 protected function getDiffLink() {
01416 if ( $this->isDeleted() && !$this->canViewContent() ) {
01417 return wfMsgHtml( 'diff' );
01418 }
01419 $undelete = SpecialPage::getTitleFor( 'Undelete' );
01420 return $this->special->skin->link( $undelete, wfMsgHtml('diff'), array(),
01421 array(
01422 'target' => $this->list->title->getPrefixedText(),
01423 'diff' => 'prev',
01424 'timestamp' => $this->revision->getTimestamp()
01425 ) );
01426 }
01427 }
01428
01432 class RevDel_FileList extends RevDel_List {
01433 var $type = 'oldimage';
01434 var $idField = 'oi_archive_name';
01435 var $dateField = 'oi_timestamp';
01436 var $authorIdField = 'oi_user';
01437 var $authorNameField = 'oi_user_text';
01438 var $storeBatch, $deleteBatch, $cleanupBatch;
01439
01440 public function doQuery( $db ) {
01441 $archiveName = array();
01442 foreach( $this->ids as $timestamp ) {
01443 $archiveNames[] = $timestamp . '!' . $this->title->getDBkey();
01444 }
01445 return $db->select( 'oldimage', '*',
01446 array(
01447 'oi_name' => $this->title->getDBkey(),
01448 'oi_archive_name' => $archiveNames
01449 ),
01450 __METHOD__,
01451 array( 'ORDER BY' => 'oi_timestamp DESC' )
01452 );
01453 }
01454
01455 public function newItem( $row ) {
01456 return new RevDel_FileItem( $this, $row );
01457 }
01458
01459 public function clearFileOps() {
01460 $this->deleteBatch = array();
01461 $this->storeBatch = array();
01462 $this->cleanupBatch = array();
01463 }
01464
01465 public function doPreCommitUpdates() {
01466 $status = Status::newGood();
01467 $repo = RepoGroup::singleton()->getLocalRepo();
01468 if ( $this->storeBatch ) {
01469 $status->merge( $repo->storeBatch( $this->storeBatch, FileRepo::OVERWRITE_SAME ) );
01470 }
01471 if ( !$status->isOK() ) {
01472 return $status;
01473 }
01474 if ( $this->deleteBatch ) {
01475 $status->merge( $repo->deleteBatch( $this->deleteBatch ) );
01476 }
01477 if ( !$status->isOK() ) {
01478
01479
01480 return $status;
01481 }
01482 if ( $this->cleanupBatch ) {
01483 $status->merge( $repo->cleanupDeletedBatch( $this->cleanupBatch ) );
01484 }
01485 return $status;
01486 }
01487
01488 public function doPostCommitUpdates() {
01489 $file = wfLocalFile( $this->title );
01490 $file->purgeCache();
01491 $file->purgeDescription();
01492 return Status::newGood();
01493 }
01494
01495 public function getSuppressBit() {
01496 return File::DELETED_RESTRICTED;
01497 }
01498 }
01499
01503 class RevDel_FileItem extends RevDel_Item {
01504 var $file;
01505
01506 public function __construct( $list, $row ) {
01507 parent::__construct( $list, $row );
01508 $this->file = RepoGroup::singleton()->getLocalRepo()->newFileFromRow( $row );
01509 }
01510
01511 public function getId() {
01512 $parts = explode( '!', $this->row->oi_archive_name );
01513 return $parts[0];
01514 }
01515
01516 public function canView() {
01517 return $this->file->userCan( File::DELETED_RESTRICTED );
01518 }
01519
01520 public function canViewContent() {
01521 return $this->file->userCan( File::DELETED_FILE );
01522 }
01523
01524 public function getBits() {
01525 return $this->file->getVisibility();
01526 }
01527
01528 public function setBits( $bits ) {
01529 # Queue the file op
01530 # FIXME: move to LocalFile.php
01531 if ( $this->isDeleted() ) {
01532 if ( $bits & File::DELETED_FILE ) {
01533 # Still deleted
01534 } else {
01535 # Newly undeleted
01536 $key = $this->file->getStorageKey();
01537 $srcRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
01538 $this->list->storeBatch[] = array(
01539 $this->file->repo->getVirtualUrl( 'deleted' ) . '/' . $srcRel,
01540 'public',
01541 $this->file->getRel()
01542 );
01543 $this->list->cleanupBatch[] = $key;
01544 }
01545 } elseif ( $bits & File::DELETED_FILE ) {
01546 # Newly deleted
01547 $key = $this->file->getStorageKey();
01548 $dstRel = $this->file->repo->getDeletedHashPath( $key ) . $key;
01549 $this->list->deleteBatch[] = array( $this->file->getRel(), $dstRel );
01550 }
01551
01552 # Do the database operations
01553 $dbw = wfGetDB( DB_MASTER );
01554 $dbw->update( 'oldimage',
01555 array( 'oi_deleted' => $bits ),
01556 array(
01557 'oi_name' => $this->row->oi_name,
01558 'oi_timestamp' => $this->row->oi_timestamp,
01559 'oi_deleted' => $this->getBits()
01560 ),
01561 __METHOD__
01562 );
01563 return (bool)$dbw->affectedRows();
01564 }
01565
01566 public function isDeleted() {
01567 return $this->file->isDeleted( File::DELETED_FILE );
01568 }
01569
01574 protected function getLink() {
01575 global $wgLang, $wgUser;
01576 $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
01577 if ( $this->isDeleted() ) {
01578 # Hidden files...
01579 if ( !$this->canViewContent() ) {
01580 $link = $date;
01581 } else {
01582 $link = $this->special->skin->link(
01583 $this->special->getTitle(),
01584 $date, array(),
01585 array(
01586 'target' => $this->list->title->getPrefixedText(),
01587 'file' => $this->file->getArchiveName(),
01588 'token' => $wgUser->editToken( $this->file->getArchiveName() )
01589 )
01590 );
01591 }
01592 return '<span class="history-deleted">' . $link . '</span>';
01593 } else {
01594 # Regular files...
01595 $url = $this->file->getUrl();
01596 return Xml::element( 'a', array( 'href' => $this->file->getUrl() ), $date );
01597 }
01598 }
01603 protected function getUserTools() {
01604 if( $this->file->userCan( Revision::DELETED_USER ) ) {
01605 $link = $this->special->skin->userLink( $this->file->user, $this->file->user_text ) .
01606 $this->special->skin->userToolLinks( $this->file->user, $this->file->user_text );
01607 } else {
01608 $link = wfMsgHtml( 'rev-deleted-user' );
01609 }
01610 if( $this->file->isDeleted( Revision::DELETED_USER ) ) {
01611 return '<span class="history-deleted">' . $link . '</span>';
01612 }
01613 return $link;
01614 }
01615
01622 protected function getComment() {
01623 if( $this->file->userCan( File::DELETED_COMMENT ) ) {
01624 $block = $this->special->skin->commentBlock( $this->file->description );
01625 } else {
01626 $block = ' ' . wfMsgHtml( 'rev-deleted-comment' );
01627 }
01628 if( $this->file->isDeleted( File::DELETED_COMMENT ) ) {
01629 return "<span class=\"history-deleted\">$block</span>";
01630 }
01631 return $block;
01632 }
01633
01634 public function getHTML() {
01635 global $wgLang;
01636 $data =
01637 wfMsg(
01638 'widthheight',
01639 $wgLang->formatNum( $this->file->getWidth() ),
01640 $wgLang->formatNum( $this->file->getHeight() )
01641 ) .
01642 ' (' .
01643 wfMsgExt( 'nbytes', 'parsemag', $wgLang->formatNum( $this->file->getSize() ) ) .
01644 ')';
01645 $pageLink = $this->getLink();
01646
01647 return '<li>' . $this->getLink() . ' ' . $this->getUserTools() . ' ' .
01648 $data . ' ' . $this->getComment(). '</li>';
01649 }
01650 }
01651
01655 class RevDel_ArchivedFileList extends RevDel_FileList {
01656 var $type = 'filearchive';
01657 var $idField = 'fa_id';
01658 var $dateField = 'fa_timestamp';
01659 var $authorIdField = 'fa_user';
01660 var $authorNameField = 'fa_user_text';
01661
01662 public function doQuery( $db ) {
01663 $ids = array_map( 'intval', $this->ids );
01664 return $db->select( 'filearchive', '*',
01665 array(
01666 'fa_name' => $this->title->getDBkey(),
01667 'fa_id' => $ids
01668 ),
01669 __METHOD__,
01670 array( 'ORDER BY' => 'fa_id DESC' )
01671 );
01672 }
01673
01674 public function newItem( $row ) {
01675 return new RevDel_ArchivedFileItem( $this, $row );
01676 }
01677 }
01678
01682 class RevDel_ArchivedFileItem extends RevDel_FileItem {
01683 public function __construct( $list, $row ) {
01684 RevDel_Item::__construct( $list, $row );
01685 $this->file = ArchivedFile::newFromRow( $row );
01686 }
01687
01688 public function getId() {
01689 return $this->row->fa_id;
01690 }
01691
01692 public function setBits( $bits ) {
01693 $dbw = wfGetDB( DB_MASTER );
01694 $dbw->update( 'filearchive',
01695 array( 'fa_deleted' => $bits ),
01696 array(
01697 'fa_id' => $this->row->fa_id,
01698 'fa_deleted' => $this->getBits(),
01699 ),
01700 __METHOD__
01701 );
01702 return (bool)$dbw->affectedRows();
01703 }
01704
01705 protected function getLink() {
01706 global $wgLang, $wgUser;
01707 $date = $wgLang->timeanddate( $this->file->getTimestamp(), true );
01708 $undelete = SpecialPage::getTitleFor( 'Undelete' );
01709 $key = $this->file->getKey();
01710 # Hidden files...
01711 if( !$this->canViewContent() ) {
01712 $link = $date;
01713 } else {
01714 $link = $this->special->skin->link( $undelete, $date, array(),
01715 array(
01716 'target' => $this->list->title->getPrefixedText(),
01717 'file' => $key,
01718 'token' => $wgUser->editToken( $key )
01719 )
01720 );
01721 }
01722 if( $this->isDeleted() ) {
01723 $link = '<span class="history-deleted">' . $link . '</span>';
01724 }
01725 return $link;
01726 }
01727 }
01728
01732 class RevDel_LogList extends RevDel_List {
01733 var $type = 'logging';
01734 var $idField = 'log_id';
01735 var $dateField = 'log_timestamp';
01736 var $authorIdField = 'log_user';
01737 var $authorNameField = 'log_user_text';
01738
01739 public function doQuery( $db ) {
01740 global $wgMessageCache;
01741 $wgMessageCache->loadAllMessages();
01742 $ids = array_map( 'intval', $this->ids );
01743 return $db->select( 'logging', '*',
01744 array( 'log_id' => $ids ),
01745 __METHOD__,
01746 array( 'ORDER BY' => 'log_id DESC' )
01747 );
01748 }
01749
01750 public function newItem( $row ) {
01751 return new RevDel_LogItem( $this, $row );
01752 }
01753
01754 public function getSuppressBit() {
01755 return Revision::DELETED_RESTRICTED;
01756 }
01757
01758 public function getLogAction() {
01759 return 'event';
01760 }
01761
01762 public function getLogParams( $params ) {
01763 return array(
01764 implode( ',', $params['ids'] ),
01765 "ofield={$params['oldBits']}",
01766 "nfield={$params['newBits']}"
01767 );
01768 }
01769 }
01770
01774 class RevDel_LogItem extends RevDel_Item {
01775 public function canView() {
01776 return LogEventsList::userCan( $this->row, Revision::DELETED_RESTRICTED );
01777 }
01778
01779 public function canViewContent() {
01780 return true;
01781 }
01782
01783 public function getBits() {
01784 return $this->row->log_deleted;
01785 }
01786
01787 public function setBits( $bits ) {
01788 $dbw = wfGetDB( DB_MASTER );
01789 $dbw->update( 'recentchanges',
01790 array(
01791 'rc_deleted' => $bits,
01792 'rc_patrolled' => 1
01793 ),
01794 array(
01795 'rc_logid' => $this->row->log_id,
01796 'rc_timestamp' => $this->row->log_timestamp
01797 ),
01798 __METHOD__
01799 );
01800 $dbw->update( 'logging',
01801 array( 'log_deleted' => $bits ),
01802 array(
01803 'log_id' => $this->row->log_id,
01804 'log_deleted' => $this->getBits()
01805 ),
01806 __METHOD__
01807 );
01808 return (bool)$dbw->affectedRows();
01809 }
01810
01811 public function getHTML() {
01812 global $wgLang;
01813
01814 $date = htmlspecialchars( $wgLang->timeanddate( $this->row->log_timestamp ) );
01815 $paramArray = LogPage::extractParams( $this->row->log_params );
01816 $title = Title::makeTitle( $this->row->log_namespace, $this->row->log_title );
01817
01818
01819 $loglink = $this->special->skin->link(
01820 SpecialPage::getTitleFor( 'Log' ),
01821 wfMsgHtml( 'log' ),
01822 array(),
01823 array( 'page' => $title->getPrefixedText() )
01824 );
01825
01826 if( !$this->canView() ) {
01827 $action = '<span class="history-deleted">' . wfMsgHtml('rev-deleted-event') . '</span>';
01828 } else {
01829 $action = LogPage::actionText( $this->row->log_type, $this->row->log_action, $title,
01830 $this->special->skin, $paramArray, true, true );
01831 if( $this->row->log_deleted & LogPage::DELETED_ACTION )
01832 $action = '<span class="history-deleted">' . $action . '</span>';
01833 }
01834
01835 $userLink = $this->special->skin->userLink( $this->row->log_user,
01836 User::WhoIs( $this->row->log_user ) );
01837 if( LogEventsList::isDeleted($this->row,LogPage::DELETED_USER) ) {
01838 $userLink = '<span class="history-deleted">' . $userLink . '</span>';
01839 }
01840
01841 $comment = $wgLang->getDirMark() . $this->special->skin->commentBlock( $this->row->log_comment );
01842 if( LogEventsList::isDeleted($this->row,LogPage::DELETED_COMMENT) ) {
01843 $comment = '<span class="history-deleted">' . $comment . '</span>';
01844 }
01845 return "<li>($loglink) $date $userLink $action $comment</li>";
01846 }
01847 }