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