MediaWiki
REL1_24
|
00001 <?php 00029 class PageArchive { 00031 protected $title; 00032 00034 protected $fileStatus; 00035 00037 protected $revisionStatus; 00038 00040 protected $config; 00041 00042 function __construct( $title, Config $config = null ) { 00043 if ( is_null( $title ) ) { 00044 throw new MWException( __METHOD__ . ' given a null title.' ); 00045 } 00046 $this->title = $title; 00047 if ( $config === null ) { 00048 wfDebug( __METHOD__ . ' did not have a Config object passed to it' ); 00049 $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' ); 00050 } 00051 $this->config = $config; 00052 } 00053 00061 public static function listAllPages() { 00062 $dbr = wfGetDB( DB_SLAVE ); 00063 00064 return self::listPages( $dbr, '' ); 00065 } 00066 00075 public static function listPagesByPrefix( $prefix ) { 00076 $dbr = wfGetDB( DB_SLAVE ); 00077 00078 $title = Title::newFromText( $prefix ); 00079 if ( $title ) { 00080 $ns = $title->getNamespace(); 00081 $prefix = $title->getDBkey(); 00082 } else { 00083 // Prolly won't work too good 00084 // @todo handle bare namespace names cleanly? 00085 $ns = 0; 00086 } 00087 00088 $conds = array( 00089 'ar_namespace' => $ns, 00090 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ), 00091 ); 00092 00093 return self::listPages( $dbr, $conds ); 00094 } 00095 00101 protected static function listPages( $dbr, $condition ) { 00102 return $dbr->select( 00103 array( 'archive' ), 00104 array( 00105 'ar_namespace', 00106 'ar_title', 00107 'count' => 'COUNT(*)' 00108 ), 00109 $condition, 00110 __METHOD__, 00111 array( 00112 'GROUP BY' => array( 'ar_namespace', 'ar_title' ), 00113 'ORDER BY' => array( 'ar_namespace', 'ar_title' ), 00114 'LIMIT' => 100, 00115 ) 00116 ); 00117 } 00118 00125 function listRevisions() { 00126 $dbr = wfGetDB( DB_SLAVE ); 00127 00128 $tables = array( 'archive' ); 00129 00130 $fields = array( 00131 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 00132 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1', 00133 ); 00134 00135 if ( $this->config->get( 'ContentHandlerUseDB' ) ) { 00136 $fields[] = 'ar_content_format'; 00137 $fields[] = 'ar_content_model'; 00138 } 00139 00140 $conds = array( 'ar_namespace' => $this->title->getNamespace(), 00141 'ar_title' => $this->title->getDBkey() ); 00142 00143 $options = array( 'ORDER BY' => 'ar_timestamp DESC' ); 00144 00145 $join_conds = array(); 00146 00147 ChangeTags::modifyDisplayQuery( 00148 $tables, 00149 $fields, 00150 $conds, 00151 $join_conds, 00152 $options 00153 ); 00154 00155 return $dbr->select( $tables, 00156 $fields, 00157 $conds, 00158 __METHOD__, 00159 $options, 00160 $join_conds 00161 ); 00162 } 00163 00172 function listFiles() { 00173 if ( $this->title->getNamespace() != NS_FILE ) { 00174 return null; 00175 } 00176 00177 $dbr = wfGetDB( DB_SLAVE ); 00178 return $dbr->select( 00179 'filearchive', 00180 ArchivedFile::selectFields(), 00181 array( 'fa_name' => $this->title->getDBkey() ), 00182 __METHOD__, 00183 array( 'ORDER BY' => 'fa_timestamp DESC' ) 00184 ); 00185 } 00186 00194 function getRevision( $timestamp ) { 00195 $dbr = wfGetDB( DB_SLAVE ); 00196 00197 $fields = array( 00198 'ar_rev_id', 00199 'ar_text', 00200 'ar_comment', 00201 'ar_user', 00202 'ar_user_text', 00203 'ar_timestamp', 00204 'ar_minor_edit', 00205 'ar_flags', 00206 'ar_text_id', 00207 'ar_deleted', 00208 'ar_len', 00209 'ar_sha1', 00210 ); 00211 00212 if ( $this->config->get( 'ContentHandlerUseDB' ) ) { 00213 $fields[] = 'ar_content_format'; 00214 $fields[] = 'ar_content_model'; 00215 } 00216 00217 $row = $dbr->selectRow( 'archive', 00218 $fields, 00219 array( 'ar_namespace' => $this->title->getNamespace(), 00220 'ar_title' => $this->title->getDBkey(), 00221 'ar_timestamp' => $dbr->timestamp( $timestamp ) ), 00222 __METHOD__ ); 00223 00224 if ( $row ) { 00225 return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) ); 00226 } 00227 00228 return null; 00229 } 00230 00241 function getPreviousRevision( $timestamp ) { 00242 $dbr = wfGetDB( DB_SLAVE ); 00243 00244 // Check the previous deleted revision... 00245 $row = $dbr->selectRow( 'archive', 00246 'ar_timestamp', 00247 array( 'ar_namespace' => $this->title->getNamespace(), 00248 'ar_title' => $this->title->getDBkey(), 00249 'ar_timestamp < ' . 00250 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), 00251 __METHOD__, 00252 array( 00253 'ORDER BY' => 'ar_timestamp DESC', 00254 'LIMIT' => 1 ) ); 00255 $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false; 00256 00257 $row = $dbr->selectRow( array( 'page', 'revision' ), 00258 array( 'rev_id', 'rev_timestamp' ), 00259 array( 00260 'page_namespace' => $this->title->getNamespace(), 00261 'page_title' => $this->title->getDBkey(), 00262 'page_id = rev_page', 00263 'rev_timestamp < ' . 00264 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ), 00265 __METHOD__, 00266 array( 00267 'ORDER BY' => 'rev_timestamp DESC', 00268 'LIMIT' => 1 ) ); 00269 $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false; 00270 $prevLiveId = $row ? intval( $row->rev_id ) : null; 00271 00272 if ( $prevLive && $prevLive > $prevDeleted ) { 00273 // Most prior revision was live 00274 return Revision::newFromId( $prevLiveId ); 00275 } elseif ( $prevDeleted ) { 00276 // Most prior revision was deleted 00277 return $this->getRevision( $prevDeleted ); 00278 } 00279 00280 // No prior revision on this page. 00281 return null; 00282 } 00283 00290 function getTextFromRow( $row ) { 00291 if ( is_null( $row->ar_text_id ) ) { 00292 // An old row from MediaWiki 1.4 or previous. 00293 // Text is embedded in this row in classic compression format. 00294 return Revision::getRevisionText( $row, 'ar_' ); 00295 } 00296 00297 // New-style: keyed to the text storage backend. 00298 $dbr = wfGetDB( DB_SLAVE ); 00299 $text = $dbr->selectRow( 'text', 00300 array( 'old_text', 'old_flags' ), 00301 array( 'old_id' => $row->ar_text_id ), 00302 __METHOD__ ); 00303 00304 return Revision::getRevisionText( $text ); 00305 } 00306 00315 function getLastRevisionText() { 00316 $dbr = wfGetDB( DB_SLAVE ); 00317 $row = $dbr->selectRow( 'archive', 00318 array( 'ar_text', 'ar_flags', 'ar_text_id' ), 00319 array( 'ar_namespace' => $this->title->getNamespace(), 00320 'ar_title' => $this->title->getDBkey() ), 00321 __METHOD__, 00322 array( 'ORDER BY' => 'ar_timestamp DESC' ) ); 00323 00324 if ( $row ) { 00325 return $this->getTextFromRow( $row ); 00326 } 00327 00328 return null; 00329 } 00330 00336 function isDeleted() { 00337 $dbr = wfGetDB( DB_SLAVE ); 00338 $n = $dbr->selectField( 'archive', 'COUNT(ar_title)', 00339 array( 'ar_namespace' => $this->title->getNamespace(), 00340 'ar_title' => $this->title->getDBkey() ), 00341 __METHOD__ 00342 ); 00343 00344 return ( $n > 0 ); 00345 } 00346 00361 function undelete( $timestamps, $comment = '', $fileVersions = array(), 00362 $unsuppress = false, User $user = null 00363 ) { 00364 // If both the set of text revisions and file revisions are empty, 00365 // restore everything. Otherwise, just restore the requested items. 00366 $restoreAll = empty( $timestamps ) && empty( $fileVersions ); 00367 00368 $restoreText = $restoreAll || !empty( $timestamps ); 00369 $restoreFiles = $restoreAll || !empty( $fileVersions ); 00370 00371 if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) { 00372 $img = wfLocalFile( $this->title ); 00373 $this->fileStatus = $img->restore( $fileVersions, $unsuppress ); 00374 if ( !$this->fileStatus->isOK() ) { 00375 return false; 00376 } 00377 $filesRestored = $this->fileStatus->successCount; 00378 } else { 00379 $filesRestored = 0; 00380 } 00381 00382 if ( $restoreText ) { 00383 $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment ); 00384 if ( !$this->revisionStatus->isOK() ) { 00385 return false; 00386 } 00387 00388 $textRestored = $this->revisionStatus->getValue(); 00389 } else { 00390 $textRestored = 0; 00391 } 00392 00393 // Touch the log! 00394 00395 if ( $textRestored && $filesRestored ) { 00396 $reason = wfMessage( 'undeletedrevisions-files' ) 00397 ->numParams( $textRestored, $filesRestored )->inContentLanguage()->text(); 00398 } elseif ( $textRestored ) { 00399 $reason = wfMessage( 'undeletedrevisions' )->numParams( $textRestored ) 00400 ->inContentLanguage()->text(); 00401 } elseif ( $filesRestored ) { 00402 $reason = wfMessage( 'undeletedfiles' )->numParams( $filesRestored ) 00403 ->inContentLanguage()->text(); 00404 } else { 00405 wfDebug( "Undelete: nothing undeleted...\n" ); 00406 00407 return false; 00408 } 00409 00410 if ( trim( $comment ) != '' ) { 00411 $reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment; 00412 } 00413 00414 if ( $user === null ) { 00415 global $wgUser; 00416 $user = $wgUser; 00417 } 00418 00419 $logEntry = new ManualLogEntry( 'delete', 'restore' ); 00420 $logEntry->setPerformer( $user ); 00421 $logEntry->setTarget( $this->title ); 00422 $logEntry->setComment( $reason ); 00423 00424 wfRunHooks( 'ArticleUndeleteLogEntry', array( $this, &$logEntry, $user ) ); 00425 00426 $logid = $logEntry->insert(); 00427 $logEntry->publish( $logid ); 00428 00429 return array( $textRestored, $filesRestored, $reason ); 00430 } 00431 00444 private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) { 00445 if ( wfReadOnly() ) { 00446 throw new ReadOnlyError(); 00447 } 00448 00449 $restoreAll = empty( $timestamps ); 00450 $dbw = wfGetDB( DB_MASTER ); 00451 00452 # Does this page already exist? We'll have to update it... 00453 $article = WikiPage::factory( $this->title ); 00454 # Load latest data for the current page (bug 31179) 00455 $article->loadPageData( 'fromdbmaster' ); 00456 $oldcountable = $article->isCountable(); 00457 00458 $page = $dbw->selectRow( 'page', 00459 array( 'page_id', 'page_latest' ), 00460 array( 'page_namespace' => $this->title->getNamespace(), 00461 'page_title' => $this->title->getDBkey() ), 00462 __METHOD__, 00463 array( 'FOR UPDATE' ) // lock page 00464 ); 00465 00466 if ( $page ) { 00467 $makepage = false; 00468 # Page already exists. Import the history, and if necessary 00469 # we'll update the latest revision field in the record. 00470 00471 $previousRevId = $page->page_latest; 00472 00473 # Get the time span of this page 00474 $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp', 00475 array( 'rev_id' => $previousRevId ), 00476 __METHOD__ ); 00477 00478 if ( $previousTimestamp === false ) { 00479 wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" ); 00480 00481 $status = Status::newGood( 0 ); 00482 $status->warning( 'undeleterevision-missing' ); 00483 00484 return $status; 00485 } 00486 } else { 00487 # Have to create a new article... 00488 $makepage = true; 00489 $previousRevId = 0; 00490 $previousTimestamp = 0; 00491 } 00492 00493 $oldWhere = array( 00494 'ar_namespace' => $this->title->getNamespace(), 00495 'ar_title' => $this->title->getDBkey(), 00496 ); 00497 if ( !$restoreAll ) { 00498 $oldWhere['ar_timestamp'] = array_map( array( &$dbw, 'timestamp' ), $timestamps ); 00499 } 00500 00501 $fields = array( 00502 'ar_rev_id', 00503 'ar_text', 00504 'ar_comment', 00505 'ar_user', 00506 'ar_user_text', 00507 'ar_timestamp', 00508 'ar_minor_edit', 00509 'ar_flags', 00510 'ar_text_id', 00511 'ar_deleted', 00512 'ar_page_id', 00513 'ar_len', 00514 'ar_sha1' 00515 ); 00516 00517 if ( $this->config->get( 'ContentHandlerUseDB' ) ) { 00518 $fields[] = 'ar_content_format'; 00519 $fields[] = 'ar_content_model'; 00520 } 00521 00525 $result = $dbw->select( 'archive', 00526 $fields, 00527 $oldWhere, 00528 __METHOD__, 00529 /* options */ array( 'ORDER BY' => 'ar_timestamp' ) 00530 ); 00531 00532 $rev_count = $result->numRows(); 00533 if ( !$rev_count ) { 00534 wfDebug( __METHOD__ . ": no revisions to restore\n" ); 00535 00536 $status = Status::newGood( 0 ); 00537 $status->warning( "undelete-no-results" ); 00538 00539 return $status; 00540 } 00541 00542 $result->seek( $rev_count - 1 ); // move to last 00543 $row = $result->fetchObject(); // get newest archived rev 00544 $oldPageId = (int)$row->ar_page_id; // pass this to ArticleUndelete hook 00545 $result->seek( 0 ); // move back 00546 00547 // grab the content to check consistency with global state before restoring the page. 00548 $revision = Revision::newFromArchiveRow( $row, 00549 array( 00550 'title' => $article->getTitle(), // used to derive default content model 00551 ) 00552 ); 00553 $user = User::newFromName( $revision->getRawUserText(), false ); 00554 $content = $revision->getContent( Revision::RAW ); 00555 00556 //NOTE: article ID may not be known yet. prepareSave() should not modify the database. 00557 $status = $content->prepareSave( $article, 0, -1, $user ); 00558 00559 if ( !$status->isOK() ) { 00560 return $status; 00561 } 00562 00563 if ( $makepage ) { 00564 // Check the state of the newest to-be version... 00565 if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { 00566 return Status::newFatal( "undeleterevdel" ); 00567 } 00568 // Safe to insert now... 00569 $newid = $article->insertOn( $dbw ); 00570 $pageId = $newid; 00571 } else { 00572 // Check if a deleted revision will become the current revision... 00573 if ( $row->ar_timestamp > $previousTimestamp ) { 00574 // Check the state of the newest to-be version... 00575 if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { 00576 return Status::newFatal( "undeleterevdel" ); 00577 } 00578 } 00579 00580 $newid = false; 00581 $pageId = $article->getId(); 00582 } 00583 00584 $revision = null; 00585 $restored = 0; 00586 00587 foreach ( $result as $row ) { 00588 // Check for key dupes due to shitty archive integrity. 00589 if ( $row->ar_rev_id ) { 00590 $exists = $dbw->selectField( 'revision', '1', 00591 array( 'rev_id' => $row->ar_rev_id ), __METHOD__ ); 00592 if ( $exists ) { 00593 continue; // don't throw DB errors 00594 } 00595 } 00596 // Insert one revision at a time...maintaining deletion status 00597 // unless we are specifically removing all restrictions... 00598 $revision = Revision::newFromArchiveRow( $row, 00599 array( 00600 'page' => $pageId, 00601 'title' => $this->title, 00602 'deleted' => $unsuppress ? 0 : $row->ar_deleted 00603 ) ); 00604 00605 $revision->insertOn( $dbw ); 00606 $restored++; 00607 00608 wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) ); 00609 } 00610 # Now that it's safely stored, take it out of the archive 00611 $dbw->delete( 'archive', 00612 $oldWhere, 00613 __METHOD__ ); 00614 00615 // Was anything restored at all? 00616 if ( $restored == 0 ) { 00617 return Status::newGood( 0 ); 00618 } 00619 00620 $created = (bool)$newid; 00621 00622 // Attach the latest revision to the page... 00623 $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId ); 00624 if ( $created || $wasnew ) { 00625 // Update site stats, link tables, etc 00626 $user = User::newFromName( $revision->getRawUserText(), false ); 00627 $article->doEditUpdates( 00628 $revision, 00629 $user, 00630 array( 'created' => $created, 'oldcountable' => $oldcountable ) 00631 ); 00632 } 00633 00634 wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment, $oldPageId ) ); 00635 00636 if ( $this->title->getNamespace() == NS_FILE ) { 00637 $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); 00638 $update->doUpdate(); 00639 } 00640 00641 return Status::newGood( $restored ); 00642 } 00643 00647 function getFileStatus() { 00648 return $this->fileStatus; 00649 } 00650 00654 function getRevisionStatus() { 00655 return $this->revisionStatus; 00656 } 00657 } 00658 00665 class SpecialUndelete extends SpecialPage { 00666 private $mAction; 00667 private $mTarget; 00668 private $mTimestamp; 00669 private $mRestore; 00670 private $mInvert; 00671 private $mFilename; 00672 private $mTargetTimestamp; 00673 private $mAllowed; 00674 private $mCanView; 00675 private $mComment; 00676 private $mToken; 00677 00679 private $mTargetObj; 00680 00681 function __construct() { 00682 parent::__construct( 'Undelete', 'deletedhistory' ); 00683 } 00684 00685 function loadRequest( $par ) { 00686 $request = $this->getRequest(); 00687 $user = $this->getUser(); 00688 00689 $this->mAction = $request->getVal( 'action' ); 00690 if ( $par !== null && $par !== '' ) { 00691 $this->mTarget = $par; 00692 } else { 00693 $this->mTarget = $request->getVal( 'target' ); 00694 } 00695 00696 $this->mTargetObj = null; 00697 00698 if ( $this->mTarget !== null && $this->mTarget !== '' ) { 00699 $this->mTargetObj = Title::newFromURL( $this->mTarget ); 00700 } 00701 00702 $this->mSearchPrefix = $request->getText( 'prefix' ); 00703 $time = $request->getVal( 'timestamp' ); 00704 $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; 00705 $this->mFilename = $request->getVal( 'file' ); 00706 00707 $posted = $request->wasPosted() && 00708 $user->matchEditToken( $request->getVal( 'wpEditToken' ) ); 00709 $this->mRestore = $request->getCheck( 'restore' ) && $posted; 00710 $this->mInvert = $request->getCheck( 'invert' ) && $posted; 00711 $this->mPreview = $request->getCheck( 'preview' ) && $posted; 00712 $this->mDiff = $request->getCheck( 'diff' ); 00713 $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) ); 00714 $this->mComment = $request->getText( 'wpComment' ); 00715 $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' ); 00716 $this->mToken = $request->getVal( 'token' ); 00717 00718 if ( $this->isAllowed( 'undelete' ) && !$user->isBlocked() ) { 00719 $this->mAllowed = true; // user can restore 00720 $this->mCanView = true; // user can view content 00721 } elseif ( $this->isAllowed( 'deletedtext' ) ) { 00722 $this->mAllowed = false; // user cannot restore 00723 $this->mCanView = true; // user can view content 00724 $this->mRestore = false; 00725 } else { // user can only view the list of revisions 00726 $this->mAllowed = false; 00727 $this->mCanView = false; 00728 $this->mTimestamp = ''; 00729 $this->mRestore = false; 00730 } 00731 00732 if ( $this->mRestore || $this->mInvert ) { 00733 $timestamps = array(); 00734 $this->mFileVersions = array(); 00735 foreach ( $request->getValues() as $key => $val ) { 00736 $matches = array(); 00737 if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { 00738 array_push( $timestamps, $matches[1] ); 00739 } 00740 00741 if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { 00742 $this->mFileVersions[] = intval( $matches[1] ); 00743 } 00744 } 00745 rsort( $timestamps ); 00746 $this->mTargetTimestamp = $timestamps; 00747 } 00748 } 00749 00758 private function isAllowed( $permission, User $user = null ) { 00759 $user = $user ? : $this->getUser(); 00760 if ( $this->mTargetObj !== null ) { 00761 return $this->mTargetObj->userCan( $permission, $user ); 00762 } else { 00763 return $user->isAllowed( $permission ); 00764 } 00765 } 00766 00767 function userCanExecute( User $user ) { 00768 return $this->isAllowed( $this->mRestriction, $user ); 00769 } 00770 00771 function execute( $par ) { 00772 $user = $this->getUser(); 00773 00774 $this->setHeaders(); 00775 $this->outputHeader(); 00776 00777 $this->loadRequest( $par ); 00778 $this->checkPermissions(); // Needs to be after mTargetObj is set 00779 00780 $out = $this->getOutput(); 00781 00782 if ( is_null( $this->mTargetObj ) ) { 00783 $out->addWikiMsg( 'undelete-header' ); 00784 00785 # Not all users can just browse every deleted page from the list 00786 if ( $user->isAllowed( 'browsearchive' ) ) { 00787 $this->showSearchForm(); 00788 } 00789 00790 return; 00791 } 00792 00793 if ( $this->mAllowed ) { 00794 $out->setPageTitle( $this->msg( 'undeletepage' ) ); 00795 } else { 00796 $out->setPageTitle( $this->msg( 'viewdeletedpage' ) ); 00797 } 00798 00799 $this->getSkin()->setRelevantTitle( $this->mTargetObj ); 00800 00801 if ( $this->mTimestamp !== '' ) { 00802 $this->showRevision( $this->mTimestamp ); 00803 } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) { 00804 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); 00805 // Check if user is allowed to see this file 00806 if ( !$file->exists() ) { 00807 $out->addWikiMsg( 'filedelete-nofile', $this->mFilename ); 00808 } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) { 00809 if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) { 00810 throw new PermissionsError( 'suppressrevision' ); 00811 } else { 00812 throw new PermissionsError( 'deletedtext' ); 00813 } 00814 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) { 00815 $this->showFileConfirmationForm( $this->mFilename ); 00816 } else { 00817 $this->showFile( $this->mFilename ); 00818 } 00819 } elseif ( $this->mRestore && $this->mAction == 'submit' ) { 00820 $this->undelete(); 00821 } else { 00822 $this->showHistory(); 00823 } 00824 } 00825 00826 function showSearchForm() { 00827 $out = $this->getOutput(); 00828 $out->setPageTitle( $this->msg( 'undelete-search-title' ) ); 00829 $out->addHTML( 00830 Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) . 00831 Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) . 00832 Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . 00833 Html::rawElement( 00834 'label', 00835 array( 'for' => 'prefix' ), 00836 $this->msg( 'undelete-search-prefix' )->parse() 00837 ) . 00838 Xml::input( 00839 'prefix', 00840 20, 00841 $this->mSearchPrefix, 00842 array( 'id' => 'prefix', 'autofocus' => true ) 00843 ) . ' ' . 00844 Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) . 00845 Xml::closeElement( 'fieldset' ) . 00846 Xml::closeElement( 'form' ) 00847 ); 00848 00849 # List undeletable articles 00850 if ( $this->mSearchPrefix ) { 00851 $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix ); 00852 $this->showList( $result ); 00853 } 00854 } 00855 00862 private function showList( $result ) { 00863 $out = $this->getOutput(); 00864 00865 if ( $result->numRows() == 0 ) { 00866 $out->addWikiMsg( 'undelete-no-results' ); 00867 00868 return false; 00869 } 00870 00871 $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) ); 00872 00873 $undelete = $this->getPageTitle(); 00874 $out->addHTML( "<ul>\n" ); 00875 foreach ( $result as $row ) { 00876 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); 00877 if ( $title !== null ) { 00878 $item = Linker::linkKnown( 00879 $undelete, 00880 htmlspecialchars( $title->getPrefixedText() ), 00881 array(), 00882 array( 'target' => $title->getPrefixedText() ) 00883 ); 00884 } else { 00885 // The title is no longer valid, show as text 00886 $item = Html::element( 00887 'span', 00888 array( 'class' => 'mw-invalidtitle' ), 00889 Linker::getInvalidTitleDescription( 00890 $this->getContext(), 00891 $row->ar_namespace, 00892 $row->ar_title 00893 ) 00894 ); 00895 } 00896 $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse(); 00897 $out->addHTML( "<li>{$item} ({$revs})</li>\n" ); 00898 } 00899 $result->free(); 00900 $out->addHTML( "</ul>\n" ); 00901 00902 return true; 00903 } 00904 00905 private function showRevision( $timestamp ) { 00906 if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) { 00907 return; 00908 } 00909 00910 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() ); 00911 if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) { 00912 return; 00913 } 00914 $rev = $archive->getRevision( $timestamp ); 00915 00916 $out = $this->getOutput(); 00917 $user = $this->getUser(); 00918 00919 if ( !$rev ) { 00920 $out->addWikiMsg( 'undeleterevision-missing' ); 00921 00922 return; 00923 } 00924 00925 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { 00926 if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { 00927 $out->wrapWikiMsg( 00928 "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 00929 $rev->isDeleted( Revision::DELETED_RESTRICTED ) ? 00930 'rev-suppressed-text-permission' : 'rev-deleted-text-permission' 00931 ); 00932 00933 return; 00934 } 00935 00936 $out->wrapWikiMsg( 00937 "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 00938 $rev->isDeleted( Revision::DELETED_RESTRICTED ) ? 00939 'rev-suppressed-text-view' : 'rev-deleted-text-view' 00940 ); 00941 $out->addHTML( '<br />' ); 00942 // and we are allowed to see... 00943 } 00944 00945 if ( $this->mDiff ) { 00946 $previousRev = $archive->getPreviousRevision( $timestamp ); 00947 if ( $previousRev ) { 00948 $this->showDiff( $previousRev, $rev ); 00949 if ( $this->mDiffOnly ) { 00950 return; 00951 } 00952 00953 $out->addHTML( '<hr />' ); 00954 } else { 00955 $out->addWikiMsg( 'undelete-nodiff' ); 00956 } 00957 } 00958 00959 $link = Linker::linkKnown( 00960 $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ), 00961 htmlspecialchars( $this->mTargetObj->getPrefixedText() ) 00962 ); 00963 00964 $lang = $this->getLanguage(); 00965 00966 // date and time are separate parameters to facilitate localisation. 00967 // $time is kept for backward compat reasons. 00968 $time = $lang->userTimeAndDate( $timestamp, $user ); 00969 $d = $lang->userDate( $timestamp, $user ); 00970 $t = $lang->userTime( $timestamp, $user ); 00971 $userLink = Linker::revUserTools( $rev ); 00972 00973 $content = $rev->getContent( Revision::FOR_THIS_USER, $user ); 00974 00975 $isText = ( $content instanceof TextContent ); 00976 00977 if ( $this->mPreview || $isText ) { 00978 $openDiv = '<div id="mw-undelete-revision" class="mw-warning">'; 00979 } else { 00980 $openDiv = '<div id="mw-undelete-revision">'; 00981 } 00982 $out->addHTML( $openDiv ); 00983 00984 // Revision delete links 00985 if ( !$this->mDiff ) { 00986 $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); 00987 if ( $revdel ) { 00988 $out->addHTML( "$revdel " ); 00989 } 00990 } 00991 00992 $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params( 00993 $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' ); 00994 00995 if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) { 00996 return; 00997 } 00998 00999 if ( $this->mPreview || !$isText ) { 01000 // NOTE: non-text content has no source view, so always use rendered preview 01001 01002 // Hide [edit]s 01003 $popts = $out->parserOptions(); 01004 $popts->setEditSection( false ); 01005 01006 $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true ); 01007 $out->addParserOutput( $pout ); 01008 } 01009 01010 if ( $isText ) { 01011 // source view for textual content 01012 $sourceView = Xml::element( 01013 'textarea', 01014 array( 01015 'readonly' => 'readonly', 01016 'cols' => $user->getIntOption( 'cols' ), 01017 'rows' => $user->getIntOption( 'rows' ) 01018 ), 01019 $content->getNativeData() . "\n" 01020 ); 01021 01022 $previewButton = Xml::element( 'input', array( 01023 'type' => 'submit', 01024 'name' => 'preview', 01025 'value' => $this->msg( 'showpreview' )->text() 01026 ) ); 01027 } else { 01028 $sourceView = ''; 01029 $previewButton = ''; 01030 } 01031 01032 $diffButton = Xml::element( 'input', array( 01033 'name' => 'diff', 01034 'type' => 'submit', 01035 'value' => $this->msg( 'showdiff' )->text() ) ); 01036 01037 $out->addHTML( 01038 $sourceView . 01039 Xml::openElement( 'div', array( 01040 'style' => 'clear: both' ) ) . 01041 Xml::openElement( 'form', array( 01042 'method' => 'post', 01043 'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) . 01044 Xml::element( 'input', array( 01045 'type' => 'hidden', 01046 'name' => 'target', 01047 'value' => $this->mTargetObj->getPrefixedDBkey() ) ) . 01048 Xml::element( 'input', array( 01049 'type' => 'hidden', 01050 'name' => 'timestamp', 01051 'value' => $timestamp ) ) . 01052 Xml::element( 'input', array( 01053 'type' => 'hidden', 01054 'name' => 'wpEditToken', 01055 'value' => $user->getEditToken() ) ) . 01056 $previewButton . 01057 $diffButton . 01058 Xml::closeElement( 'form' ) . 01059 Xml::closeElement( 'div' ) 01060 ); 01061 } 01062 01071 function showDiff( $previousRev, $currentRev ) { 01072 $diffContext = clone $this->getContext(); 01073 $diffContext->setTitle( $currentRev->getTitle() ); 01074 $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) ); 01075 01076 $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext ); 01077 $diffEngine->showDiffStyle(); 01078 01079 $formattedDiff = $diffEngine->generateContentDiffBody( 01080 $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ), 01081 $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) 01082 ); 01083 01084 $formattedDiff = $diffEngine->addHeader( 01085 $formattedDiff, 01086 $this->diffHeader( $previousRev, 'o' ), 01087 $this->diffHeader( $currentRev, 'n' ) 01088 ); 01089 01090 $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" ); 01091 } 01092 01098 private function diffHeader( $rev, $prefix ) { 01099 $isDeleted = !( $rev->getId() && $rev->getTitle() ); 01100 if ( $isDeleted ) { 01102 $targetPage = $this->getPageTitle(); 01103 $targetQuery = array( 01104 'target' => $this->mTargetObj->getPrefixedText(), 01105 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() ) 01106 ); 01107 } else { 01109 $targetPage = $rev->getTitle(); 01110 $targetQuery = array( 'oldid' => $rev->getId() ); 01111 } 01112 01113 // Add show/hide deletion links if available 01114 $user = $this->getUser(); 01115 $lang = $this->getLanguage(); 01116 $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); 01117 01118 if ( $rdel ) { 01119 $rdel = " $rdel"; 01120 } 01121 01122 $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : ''; 01123 01124 $tags = wfGetDB( DB_SLAVE )->selectField( 01125 'tag_summary', 01126 'ts_tags', 01127 array( 'ts_rev_id' => $rev->getId() ), 01128 __METHOD__ 01129 ); 01130 $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff' ); 01131 01132 // FIXME This is reimplementing DifferenceEngine#getRevisionHeader 01133 // and partially #showDiffPage, but worse 01134 return '<div id="mw-diff-' . $prefix . 'title1"><strong>' . 01135 Linker::link( 01136 $targetPage, 01137 $this->msg( 01138 'revisionasof', 01139 $lang->userTimeAndDate( $rev->getTimestamp(), $user ), 01140 $lang->userDate( $rev->getTimestamp(), $user ), 01141 $lang->userTime( $rev->getTimestamp(), $user ) 01142 )->escaped(), 01143 array(), 01144 $targetQuery 01145 ) . 01146 '</strong></div>' . 01147 '<div id="mw-diff-' . $prefix . 'title2">' . 01148 Linker::revUserTools( $rev ) . '<br />' . 01149 '</div>' . 01150 '<div id="mw-diff-' . $prefix . 'title3">' . 01151 $minor . Linker::revComment( $rev ) . $rdel . '<br />' . 01152 '</div>' . 01153 '<div id="mw-diff-' . $prefix . 'title5">' . 01154 $tagSummary[0] . '<br />' . 01155 '</div>'; 01156 } 01157 01162 private function showFileConfirmationForm( $key ) { 01163 $out = $this->getOutput(); 01164 $lang = $this->getLanguage(); 01165 $user = $this->getUser(); 01166 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); 01167 $out->addWikiMsg( 'undelete-show-file-confirm', 01168 $this->mTargetObj->getText(), 01169 $lang->userDate( $file->getTimestamp(), $user ), 01170 $lang->userTime( $file->getTimestamp(), $user ) ); 01171 $out->addHTML( 01172 Xml::openElement( 'form', array( 01173 'method' => 'POST', 01174 'action' => $this->getPageTitle()->getLocalURL( array( 01175 'target' => $this->mTarget, 01176 'file' => $key, 01177 'token' => $user->getEditToken( $key ), 01178 ) ), 01179 ) 01180 ) . 01181 Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) . 01182 '</form>' 01183 ); 01184 } 01185 01190 private function showFile( $key ) { 01191 $this->getOutput()->disable(); 01192 01193 # We mustn't allow the output to be Squid cached, otherwise 01194 # if an admin previews a deleted image, and it's cached, then 01195 # a user without appropriate permissions can toddle off and 01196 # nab the image, and Squid will serve it 01197 $response = $this->getRequest()->response(); 01198 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01199 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); 01200 $response->header( 'Pragma: no-cache' ); 01201 01202 $repo = RepoGroup::singleton()->getLocalRepo(); 01203 $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key; 01204 $repo->streamFile( $path ); 01205 } 01206 01207 private function showHistory() { 01208 $out = $this->getOutput(); 01209 if ( $this->mAllowed ) { 01210 $out->addModules( 'mediawiki.special.undelete' ); 01211 } 01212 $out->wrapWikiMsg( 01213 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", 01214 array( 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ) 01215 ); 01216 01217 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() ); 01218 wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) ); 01219 /* 01220 $text = $archive->getLastRevisionText(); 01221 if( is_null( $text ) ) { 01222 $out->addWikiMsg( 'nohistory' ); 01223 return; 01224 } 01225 */ 01226 $out->addHTML( '<div class="mw-undelete-history">' ); 01227 if ( $this->mAllowed ) { 01228 $out->addWikiMsg( 'undeletehistory' ); 01229 $out->addWikiMsg( 'undeleterevdel' ); 01230 } else { 01231 $out->addWikiMsg( 'undeletehistorynoadmin' ); 01232 } 01233 $out->addHTML( '</div>' ); 01234 01235 # List all stored revisions 01236 $revisions = $archive->listRevisions(); 01237 $files = $archive->listFiles(); 01238 01239 $haveRevisions = $revisions && $revisions->numRows() > 0; 01240 $haveFiles = $files && $files->numRows() > 0; 01241 01242 # Batch existence check on user and talk pages 01243 if ( $haveRevisions ) { 01244 $batch = new LinkBatch(); 01245 foreach ( $revisions as $row ) { 01246 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); 01247 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) ); 01248 } 01249 $batch->execute(); 01250 $revisions->seek( 0 ); 01251 } 01252 if ( $haveFiles ) { 01253 $batch = new LinkBatch(); 01254 foreach ( $files as $row ) { 01255 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); 01256 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) ); 01257 } 01258 $batch->execute(); 01259 $files->seek( 0 ); 01260 } 01261 01262 if ( $this->mAllowed ) { 01263 $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ); 01264 # Start the form here 01265 $top = Xml::openElement( 01266 'form', 01267 array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) 01268 ); 01269 $out->addHTML( $top ); 01270 } 01271 01272 # Show relevant lines from the deletion log: 01273 $deleteLogPage = new LogPage( 'delete' ); 01274 $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" ); 01275 LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj ); 01276 # Show relevant lines from the suppression log: 01277 $suppressLogPage = new LogPage( 'suppress' ); 01278 if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) { 01279 $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" ); 01280 LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj ); 01281 } 01282 01283 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { 01284 # Format the user-visible controls (comment field, submission button) 01285 # in a nice little table 01286 if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) { 01287 $unsuppressBox = 01288 "<tr> 01289 <td> </td> 01290 <td class='mw-input'>" . 01291 Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(), 01292 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ) . 01293 "</td> 01294 </tr>"; 01295 } else { 01296 $unsuppressBox = ''; 01297 } 01298 01299 $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) . 01300 Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . 01301 "<tr> 01302 <td colspan='2' class='mw-undelete-extrahelp'>" . 01303 $this->msg( 'undeleteextrahelp' )->parseAsBlock() . 01304 "</td> 01305 </tr> 01306 <tr> 01307 <td class='mw-label'>" . 01308 Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) . 01309 "</td> 01310 <td class='mw-input'>" . 01311 Xml::input( 01312 'wpComment', 01313 50, 01314 $this->mComment, 01315 array( 'id' => 'wpComment', 'autofocus' => true ) 01316 ) . 01317 "</td> 01318 </tr> 01319 <tr> 01320 <td> </td> 01321 <td class='mw-submit'>" . 01322 Xml::submitButton( 01323 $this->msg( 'undeletebtn' )->text(), 01324 array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) 01325 ) . ' ' . 01326 Xml::submitButton( 01327 $this->msg( 'undeleteinvert' )->text(), 01328 array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) 01329 ) . 01330 "</td> 01331 </tr>" . 01332 $unsuppressBox . 01333 Xml::closeElement( 'table' ) . 01334 Xml::closeElement( 'fieldset' ); 01335 01336 $out->addHTML( $table ); 01337 } 01338 01339 $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" ); 01340 01341 if ( $haveRevisions ) { 01342 # The page's stored (deleted) history: 01343 $out->addHTML( '<ul>' ); 01344 $remaining = $revisions->numRows(); 01345 $earliestLiveTime = $this->mTargetObj->getEarliestRevTime(); 01346 01347 foreach ( $revisions as $row ) { 01348 $remaining--; 01349 $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) ); 01350 } 01351 $revisions->free(); 01352 $out->addHTML( '</ul>' ); 01353 } else { 01354 $out->addWikiMsg( 'nohistory' ); 01355 } 01356 01357 if ( $haveFiles ) { 01358 $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" ); 01359 $out->addHTML( '<ul>' ); 01360 foreach ( $files as $row ) { 01361 $out->addHTML( $this->formatFileRow( $row ) ); 01362 } 01363 $files->free(); 01364 $out->addHTML( '</ul>' ); 01365 } 01366 01367 if ( $this->mAllowed ) { 01368 # Slip in the hidden controls here 01369 $misc = Html::hidden( 'target', $this->mTarget ); 01370 $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ); 01371 $misc .= Xml::closeElement( 'form' ); 01372 $out->addHTML( $misc ); 01373 } 01374 01375 return true; 01376 } 01377 01378 private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) { 01379 $rev = Revision::newFromArchiveRow( $row, 01380 array( 01381 'title' => $this->mTargetObj 01382 ) ); 01383 01384 $revTextSize = ''; 01385 $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); 01386 // Build checkboxen... 01387 if ( $this->mAllowed ) { 01388 if ( $this->mInvert ) { 01389 if ( in_array( $ts, $this->mTargetTimestamp ) ) { 01390 $checkBox = Xml::check( "ts$ts" ); 01391 } else { 01392 $checkBox = Xml::check( "ts$ts", true ); 01393 } 01394 } else { 01395 $checkBox = Xml::check( "ts$ts" ); 01396 } 01397 } else { 01398 $checkBox = ''; 01399 } 01400 01401 // Build page & diff links... 01402 $user = $this->getUser(); 01403 if ( $this->mCanView ) { 01404 $titleObj = $this->getPageTitle(); 01405 # Last link 01406 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { 01407 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); 01408 $last = $this->msg( 'diff' )->escaped(); 01409 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) { 01410 $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); 01411 $last = Linker::linkKnown( 01412 $titleObj, 01413 $this->msg( 'diff' )->escaped(), 01414 array(), 01415 array( 01416 'target' => $this->mTargetObj->getPrefixedText(), 01417 'timestamp' => $ts, 01418 'diff' => 'prev' 01419 ) 01420 ); 01421 } else { 01422 $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); 01423 $last = $this->msg( 'diff' )->escaped(); 01424 } 01425 } else { 01426 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); 01427 $last = $this->msg( 'diff' )->escaped(); 01428 } 01429 01430 // User links 01431 $userLink = Linker::revUserTools( $rev ); 01432 01433 // Minor edit 01434 $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : ''; 01435 01436 // Revision text size 01437 $size = $row->ar_len; 01438 if ( !is_null( $size ) ) { 01439 $revTextSize = Linker::formatRevisionSize( $size ); 01440 } 01441 01442 // Edit summary 01443 $comment = Linker::revComment( $rev ); 01444 01445 // Tags 01446 $attribs = array(); 01447 list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'deletedhistory' ); 01448 if ( $classes ) { 01449 $attribs['class'] = implode( ' ', $classes ); 01450 } 01451 01452 // Revision delete links 01453 $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); 01454 01455 $revisionRow = $this->msg( 'undelete-revision-row' ) 01456 ->rawParams( 01457 $checkBox, 01458 $revdlink, 01459 $last, 01460 $pageLink, 01461 $userLink, 01462 $minor, 01463 $revTextSize, 01464 $comment, 01465 $tagSummary 01466 ) 01467 ->escaped(); 01468 01469 return Xml::tags( 'li', $attribs, $revisionRow ) . "\n"; 01470 } 01471 01472 private function formatFileRow( $row ) { 01473 $file = ArchivedFile::newFromRow( $row ); 01474 $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); 01475 $user = $this->getUser(); 01476 01477 $checkBox = ''; 01478 if ( $this->mCanView && $row->fa_storage_key ) { 01479 if ( $this->mAllowed ) { 01480 $checkBox = Xml::check( 'fileid' . $row->fa_id ); 01481 } 01482 $key = urlencode( $row->fa_storage_key ); 01483 $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key ); 01484 } else { 01485 $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user ); 01486 } 01487 $userLink = $this->getFileUser( $file ); 01488 $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text(); 01489 $bytes = $this->msg( 'parentheses' ) 01490 ->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() ) 01491 ->plain(); 01492 $data = htmlspecialchars( $data . ' ' . $bytes ); 01493 $comment = $this->getFileComment( $file ); 01494 01495 // Add show/hide deletion links if available 01496 $canHide = $this->isAllowed( 'deleterevision' ); 01497 if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) { 01498 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) { 01499 // Revision was hidden from sysops 01500 $revdlink = Linker::revDeleteLinkDisabled( $canHide ); 01501 } else { 01502 $query = array( 01503 'type' => 'filearchive', 01504 'target' => $this->mTargetObj->getPrefixedDBkey(), 01505 'ids' => $row->fa_id 01506 ); 01507 $revdlink = Linker::revDeleteLink( $query, 01508 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide ); 01509 } 01510 } else { 01511 $revdlink = ''; 01512 } 01513 01514 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n"; 01515 } 01516 01525 function getPageLink( $rev, $titleObj, $ts ) { 01526 $user = $this->getUser(); 01527 $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); 01528 01529 if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { 01530 return '<span class="history-deleted">' . $time . '</span>'; 01531 } 01532 01533 $link = Linker::linkKnown( 01534 $titleObj, 01535 htmlspecialchars( $time ), 01536 array(), 01537 array( 01538 'target' => $this->mTargetObj->getPrefixedText(), 01539 'timestamp' => $ts 01540 ) 01541 ); 01542 01543 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { 01544 $link = '<span class="history-deleted">' . $link . '</span>'; 01545 } 01546 01547 return $link; 01548 } 01549 01560 function getFileLink( $file, $titleObj, $ts, $key ) { 01561 $user = $this->getUser(); 01562 $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); 01563 01564 if ( !$file->userCan( File::DELETED_FILE, $user ) ) { 01565 return '<span class="history-deleted">' . $time . '</span>'; 01566 } 01567 01568 $link = Linker::linkKnown( 01569 $titleObj, 01570 htmlspecialchars( $time ), 01571 array(), 01572 array( 01573 'target' => $this->mTargetObj->getPrefixedText(), 01574 'file' => $key, 01575 'token' => $user->getEditToken( $key ) 01576 ) 01577 ); 01578 01579 if ( $file->isDeleted( File::DELETED_FILE ) ) { 01580 $link = '<span class="history-deleted">' . $link . '</span>'; 01581 } 01582 01583 return $link; 01584 } 01585 01592 function getFileUser( $file ) { 01593 if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) { 01594 return '<span class="history-deleted">' . 01595 $this->msg( 'rev-deleted-user' )->escaped() . 01596 '</span>'; 01597 } 01598 01599 $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) . 01600 Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() ); 01601 01602 if ( $file->isDeleted( File::DELETED_USER ) ) { 01603 $link = '<span class="history-deleted">' . $link . '</span>'; 01604 } 01605 01606 return $link; 01607 } 01608 01615 function getFileComment( $file ) { 01616 if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) { 01617 return '<span class="history-deleted"><span class="comment">' . 01618 $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>'; 01619 } 01620 01621 $link = Linker::commentBlock( $file->getRawDescription() ); 01622 01623 if ( $file->isDeleted( File::DELETED_COMMENT ) ) { 01624 $link = '<span class="history-deleted">' . $link . '</span>'; 01625 } 01626 01627 return $link; 01628 } 01629 01630 function undelete() { 01631 if ( $this->getConfig()->get( 'UploadMaintenance' ) && $this->mTargetObj->getNamespace() == NS_FILE ) { 01632 throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' ); 01633 } 01634 01635 if ( wfReadOnly() ) { 01636 throw new ReadOnlyError; 01637 } 01638 01639 $out = $this->getOutput(); 01640 $archive = new PageArchive( $this->mTargetObj, $this->getConfig() ); 01641 wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) ); 01642 $ok = $archive->undelete( 01643 $this->mTargetTimestamp, 01644 $this->mComment, 01645 $this->mFileVersions, 01646 $this->mUnsuppress, 01647 $this->getUser() 01648 ); 01649 01650 if ( is_array( $ok ) ) { 01651 if ( $ok[1] ) { // Undeleted file count 01652 wfRunHooks( 'FileUndeleteComplete', array( 01653 $this->mTargetObj, $this->mFileVersions, 01654 $this->getUser(), $this->mComment ) ); 01655 } 01656 01657 $link = Linker::linkKnown( $this->mTargetObj ); 01658 $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() ); 01659 } else { 01660 $out->setPageTitle( $this->msg( 'undelete-error' ) ); 01661 } 01662 01663 // Show revision undeletion warnings and errors 01664 $status = $archive->getRevisionStatus(); 01665 if ( $status && !$status->isGood() ) { 01666 $out->addWikiText( '<div class="error">' . 01667 $status->getWikiText( 01668 'cannotundelete', 01669 'cannotundelete' 01670 ) . '</div>' 01671 ); 01672 } 01673 01674 // Show file undeletion warnings and errors 01675 $status = $archive->getFileStatus(); 01676 if ( $status && !$status->isGood() ) { 01677 $out->addWikiText( '<div class="error">' . 01678 $status->getWikiText( 01679 'undelete-error-short', 01680 'undelete-error-long' 01681 ) . '</div>' 01682 ); 01683 } 01684 } 01685 01686 protected function getGroupName() { 01687 return 'pagetools'; 01688 } 01689 }