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