MediaWiki
REL1_23
|
00001 <?php 00029 class PageArchive { 00031 protected $title; 00032 00034 protected $fileStatus; 00035 00037 protected $revisionStatus; 00038 00039 function __construct( $title ) { 00040 if ( is_null( $title ) ) { 00041 throw new MWException( __METHOD__ . ' given a null title.' ); 00042 } 00043 $this->title = $title; 00044 } 00045 00053 public static function listAllPages() { 00054 $dbr = wfGetDB( DB_SLAVE ); 00055 00056 return self::listPages( $dbr, '' ); 00057 } 00058 00067 public static function listPagesByPrefix( $prefix ) { 00068 $dbr = wfGetDB( DB_SLAVE ); 00069 00070 $title = Title::newFromText( $prefix ); 00071 if ( $title ) { 00072 $ns = $title->getNamespace(); 00073 $prefix = $title->getDBkey(); 00074 } else { 00075 // Prolly won't work too good 00076 // @todo handle bare namespace names cleanly? 00077 $ns = 0; 00078 } 00079 00080 $conds = array( 00081 'ar_namespace' => $ns, 00082 'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ), 00083 ); 00084 00085 return self::listPages( $dbr, $conds ); 00086 } 00087 00093 protected static function listPages( $dbr, $condition ) { 00094 return $dbr->resultObject( $dbr->select( 00095 array( 'archive' ), 00096 array( 00097 'ar_namespace', 00098 'ar_title', 00099 'count' => 'COUNT(*)' 00100 ), 00101 $condition, 00102 __METHOD__, 00103 array( 00104 'GROUP BY' => array( 'ar_namespace', 'ar_title' ), 00105 'ORDER BY' => array( 'ar_namespace', 'ar_title' ), 00106 'LIMIT' => 100, 00107 ) 00108 ) ); 00109 } 00110 00117 function listRevisions() { 00118 global $wgContentHandlerUseDB; 00119 00120 $dbr = wfGetDB( DB_SLAVE ); 00121 00122 $tables = array( 'archive' ); 00123 00124 $fields = array( 00125 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 00126 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1', 00127 ); 00128 00129 if ( $wgContentHandlerUseDB ) { 00130 $fields[] = 'ar_content_format'; 00131 $fields[] = 'ar_content_model'; 00132 } 00133 00134 $conds = array( 'ar_namespace' => $this->title->getNamespace(), 00135 'ar_title' => $this->title->getDBkey() ); 00136 00137 $options = array( 'ORDER BY' => 'ar_timestamp DESC' ); 00138 00139 $join_conds = array(); 00140 00141 ChangeTags::modifyDisplayQuery( 00142 $tables, 00143 $fields, 00144 $conds, 00145 $join_conds, 00146 $options 00147 ); 00148 00149 $res = $dbr->select( $tables, 00150 $fields, 00151 $conds, 00152 __METHOD__, 00153 $options, 00154 $join_conds 00155 ); 00156 00157 return $dbr->resultObject( $res ); 00158 } 00159 00168 function listFiles() { 00169 if ( $this->title->getNamespace() != NS_FILE ) { 00170 return null; 00171 } 00172 00173 $dbr = wfGetDB( DB_SLAVE ); 00174 $res = $dbr->select( 00175 'filearchive', 00176 ArchivedFile::selectFields(), 00177 array( 'fa_name' => $this->title->getDBkey() ), 00178 __METHOD__, 00179 array( 'ORDER BY' => 'fa_timestamp DESC' ) 00180 ); 00181 00182 return $dbr->resultObject( $res ); 00183 } 00184 00192 function getRevision( $timestamp ) { 00193 global $wgContentHandlerUseDB; 00194 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 ( $wgContentHandlerUseDB ) { 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 global $wgContentHandlerUseDB; 00446 00447 if ( wfReadOnly() ) { 00448 throw new ReadOnlyError(); 00449 } 00450 00451 $restoreAll = empty( $timestamps ); 00452 $dbw = wfGetDB( DB_MASTER ); 00453 00454 # Does this page already exist? We'll have to update it... 00455 $article = WikiPage::factory( $this->title ); 00456 # Load latest data for the current page (bug 31179) 00457 $article->loadPageData( 'fromdbmaster' ); 00458 $oldcountable = $article->isCountable(); 00459 00460 $page = $dbw->selectRow( 'page', 00461 array( 'page_id', 'page_latest' ), 00462 array( 'page_namespace' => $this->title->getNamespace(), 00463 'page_title' => $this->title->getDBkey() ), 00464 __METHOD__, 00465 array( 'FOR UPDATE' ) // lock page 00466 ); 00467 00468 if ( $page ) { 00469 $makepage = false; 00470 # Page already exists. Import the history, and if necessary 00471 # we'll update the latest revision field in the record. 00472 00473 $previousRevId = $page->page_latest; 00474 00475 # Get the time span of this page 00476 $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp', 00477 array( 'rev_id' => $previousRevId ), 00478 __METHOD__ ); 00479 00480 if ( $previousTimestamp === false ) { 00481 wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" ); 00482 00483 $status = Status::newGood( 0 ); 00484 $status->warning( 'undeleterevision-missing' ); 00485 00486 return $status; 00487 } 00488 } else { 00489 # Have to create a new article... 00490 $makepage = true; 00491 $previousRevId = 0; 00492 $previousTimestamp = 0; 00493 } 00494 00495 if ( $restoreAll ) { 00496 $oldones = '1 = 1'; # All revisions... 00497 } else { 00498 $oldts = implode( ',', 00499 array_map( array( &$dbw, 'addQuotes' ), 00500 array_map( array( &$dbw, 'timestamp' ), 00501 $timestamps ) ) ); 00502 00503 $oldones = "ar_timestamp IN ( {$oldts} )"; 00504 } 00505 00506 $fields = array( 00507 'ar_rev_id', 00508 'ar_text', 00509 'ar_comment', 00510 'ar_user', 00511 'ar_user_text', 00512 'ar_timestamp', 00513 'ar_minor_edit', 00514 'ar_flags', 00515 'ar_text_id', 00516 'ar_deleted', 00517 'ar_page_id', 00518 'ar_len', 00519 'ar_sha1' 00520 ); 00521 00522 if ( $wgContentHandlerUseDB ) { 00523 $fields[] = 'ar_content_format'; 00524 $fields[] = 'ar_content_model'; 00525 } 00526 00530 $result = $dbw->select( 'archive', 00531 $fields, 00532 /* WHERE */ array( 00533 'ar_namespace' => $this->title->getNamespace(), 00534 'ar_title' => $this->title->getDBkey(), 00535 $oldones ), 00536 __METHOD__, 00537 /* options */ array( 'ORDER BY' => 'ar_timestamp' ) 00538 ); 00539 $ret = $dbw->resultObject( $result ); 00540 $rev_count = $dbw->numRows( $result ); 00541 00542 if ( !$rev_count ) { 00543 wfDebug( __METHOD__ . ": no revisions to restore\n" ); 00544 00545 $status = Status::newGood( 0 ); 00546 $status->warning( "undelete-no-results" ); 00547 00548 return $status; 00549 } 00550 00551 $ret->seek( $rev_count - 1 ); // move to last 00552 $row = $ret->fetchObject(); // get newest archived rev 00553 $ret->seek( 0 ); // move back 00554 00555 // grab the content to check consistency with global state before restoring the page. 00556 $revision = Revision::newFromArchiveRow( $row, 00557 array( 00558 'title' => $article->getTitle(), // used to derive default content model 00559 ) 00560 ); 00561 $user = User::newFromName( $revision->getRawUserText(), false ); 00562 $content = $revision->getContent( Revision::RAW ); 00563 00564 //NOTE: article ID may not be known yet. prepareSave() should not modify the database. 00565 $status = $content->prepareSave( $article, 0, -1, $user ); 00566 00567 if ( !$status->isOK() ) { 00568 return $status; 00569 } 00570 00571 if ( $makepage ) { 00572 // Check the state of the newest to-be version... 00573 if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { 00574 return Status::newFatal( "undeleterevdel" ); 00575 } 00576 // Safe to insert now... 00577 $newid = $article->insertOn( $dbw ); 00578 $pageId = $newid; 00579 } else { 00580 // Check if a deleted revision will become the current revision... 00581 if ( $row->ar_timestamp > $previousTimestamp ) { 00582 // Check the state of the newest to-be version... 00583 if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) { 00584 return Status::newFatal( "undeleterevdel" ); 00585 } 00586 } 00587 00588 $newid = false; 00589 $pageId = $article->getId(); 00590 } 00591 00592 $revision = null; 00593 $restored = 0; 00594 00595 foreach ( $ret as $row ) { 00596 // Check for key dupes due to shitty archive integrity. 00597 if ( $row->ar_rev_id ) { 00598 $exists = $dbw->selectField( 'revision', '1', 00599 array( 'rev_id' => $row->ar_rev_id ), __METHOD__ ); 00600 if ( $exists ) { 00601 continue; // don't throw DB errors 00602 } 00603 } 00604 // Insert one revision at a time...maintaining deletion status 00605 // unless we are specifically removing all restrictions... 00606 $revision = Revision::newFromArchiveRow( $row, 00607 array( 00608 'page' => $pageId, 00609 'title' => $this->title, 00610 'deleted' => $unsuppress ? 0 : $row->ar_deleted 00611 ) ); 00612 00613 $revision->insertOn( $dbw ); 00614 $restored++; 00615 00616 wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) ); 00617 } 00618 # Now that it's safely stored, take it out of the archive 00619 $dbw->delete( 'archive', 00620 /* WHERE */ array( 00621 'ar_namespace' => $this->title->getNamespace(), 00622 'ar_title' => $this->title->getDBkey(), 00623 $oldones ), 00624 __METHOD__ ); 00625 00626 // Was anything restored at all? 00627 if ( $restored == 0 ) { 00628 return Status::newGood( 0 ); 00629 } 00630 00631 $created = (bool)$newid; 00632 00633 // Attach the latest revision to the page... 00634 $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId ); 00635 if ( $created || $wasnew ) { 00636 // Update site stats, link tables, etc 00637 $user = User::newFromName( $revision->getRawUserText(), false ); 00638 $article->doEditUpdates( 00639 $revision, 00640 $user, 00641 array( 'created' => $created, 'oldcountable' => $oldcountable ) 00642 ); 00643 } 00644 00645 wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) ); 00646 00647 if ( $this->title->getNamespace() == NS_FILE ) { 00648 $update = new HTMLCacheUpdate( $this->title, 'imagelinks' ); 00649 $update->doUpdate(); 00650 } 00651 00652 return Status::newGood( $restored ); 00653 } 00654 00658 function getFileStatus() { 00659 return $this->fileStatus; 00660 } 00661 00665 function getRevisionStatus() { 00666 return $this->revisionStatus; 00667 } 00668 } 00669 00676 class SpecialUndelete extends SpecialPage { 00677 private $mAction; 00678 private $mTarget; 00679 private $mTimestamp; 00680 private $mRestore; 00681 private $mInvert; 00682 private $mFilename; 00683 private $mTargetTimestamp; 00684 private $mAllowed; 00685 private $mCanView; 00686 private $mComment; 00687 private $mToken; 00688 00690 private $mTargetObj; 00691 00692 function __construct() { 00693 parent::__construct( 'Undelete', 'deletedhistory' ); 00694 } 00695 00696 function loadRequest( $par ) { 00697 $request = $this->getRequest(); 00698 $user = $this->getUser(); 00699 00700 $this->mAction = $request->getVal( 'action' ); 00701 if ( $par !== null && $par !== '' ) { 00702 $this->mTarget = $par; 00703 } else { 00704 $this->mTarget = $request->getVal( 'target' ); 00705 } 00706 00707 $this->mTargetObj = null; 00708 00709 if ( $this->mTarget !== null && $this->mTarget !== '' ) { 00710 $this->mTargetObj = Title::newFromURL( $this->mTarget ); 00711 } 00712 00713 $this->mSearchPrefix = $request->getText( 'prefix' ); 00714 $time = $request->getVal( 'timestamp' ); 00715 $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : ''; 00716 $this->mFilename = $request->getVal( 'file' ); 00717 00718 $posted = $request->wasPosted() && 00719 $user->matchEditToken( $request->getVal( 'wpEditToken' ) ); 00720 $this->mRestore = $request->getCheck( 'restore' ) && $posted; 00721 $this->mInvert = $request->getCheck( 'invert' ) && $posted; 00722 $this->mPreview = $request->getCheck( 'preview' ) && $posted; 00723 $this->mDiff = $request->getCheck( 'diff' ); 00724 $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) ); 00725 $this->mComment = $request->getText( 'wpComment' ); 00726 $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' ); 00727 $this->mToken = $request->getVal( 'token' ); 00728 00729 if ( $user->isAllowed( 'undelete' ) && !$user->isBlocked() ) { 00730 $this->mAllowed = true; // user can restore 00731 $this->mCanView = true; // user can view content 00732 } elseif ( $user->isAllowed( 'deletedtext' ) ) { 00733 $this->mAllowed = false; // user cannot restore 00734 $this->mCanView = true; // user can view content 00735 $this->mRestore = false; 00736 } else { // user can only view the list of revisions 00737 $this->mAllowed = false; 00738 $this->mCanView = false; 00739 $this->mTimestamp = ''; 00740 $this->mRestore = false; 00741 } 00742 00743 if ( $this->mRestore || $this->mInvert ) { 00744 $timestamps = array(); 00745 $this->mFileVersions = array(); 00746 foreach ( $request->getValues() as $key => $val ) { 00747 $matches = array(); 00748 if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) { 00749 array_push( $timestamps, $matches[1] ); 00750 } 00751 00752 if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) { 00753 $this->mFileVersions[] = intval( $matches[1] ); 00754 } 00755 } 00756 rsort( $timestamps ); 00757 $this->mTargetTimestamp = $timestamps; 00758 } 00759 } 00760 00761 function execute( $par ) { 00762 $this->checkPermissions(); 00763 $user = $this->getUser(); 00764 00765 $this->setHeaders(); 00766 $this->outputHeader(); 00767 00768 $this->loadRequest( $par ); 00769 00770 $out = $this->getOutput(); 00771 00772 if ( is_null( $this->mTargetObj ) ) { 00773 $out->addWikiMsg( 'undelete-header' ); 00774 00775 # Not all users can just browse every deleted page from the list 00776 if ( $user->isAllowed( 'browsearchive' ) ) { 00777 $this->showSearchForm(); 00778 } 00779 00780 return; 00781 } 00782 00783 if ( $this->mAllowed ) { 00784 $out->setPageTitle( $this->msg( 'undeletepage' ) ); 00785 } else { 00786 $out->setPageTitle( $this->msg( 'viewdeletedpage' ) ); 00787 } 00788 00789 $this->getSkin()->setRelevantTitle( $this->mTargetObj ); 00790 00791 if ( $this->mTimestamp !== '' ) { 00792 $this->showRevision( $this->mTimestamp ); 00793 } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) { 00794 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); 00795 // Check if user is allowed to see this file 00796 if ( !$file->exists() ) { 00797 $out->addWikiMsg( 'filedelete-nofile', $this->mFilename ); 00798 } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) { 00799 if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) { 00800 throw new PermissionsError( 'suppressrevision' ); 00801 } else { 00802 throw new PermissionsError( 'deletedtext' ); 00803 } 00804 } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) { 00805 $this->showFileConfirmationForm( $this->mFilename ); 00806 } else { 00807 $this->showFile( $this->mFilename ); 00808 } 00809 } elseif ( $this->mRestore && $this->mAction == 'submit' ) { 00810 $this->undelete(); 00811 } else { 00812 $this->showHistory(); 00813 } 00814 } 00815 00816 function showSearchForm() { 00817 global $wgScript; 00818 00819 $out = $this->getOutput(); 00820 $out->setPageTitle( $this->msg( 'undelete-search-title' ) ); 00821 $out->addHTML( 00822 Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) . 00823 Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) . 00824 Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) . 00825 Html::rawElement( 00826 'label', 00827 array( 'for' => 'prefix' ), 00828 $this->msg( 'undelete-search-prefix' )->parse() 00829 ) . 00830 Xml::input( 00831 'prefix', 00832 20, 00833 $this->mSearchPrefix, 00834 array( 'id' => 'prefix', 'autofocus' => true ) 00835 ) . ' ' . 00836 Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) . 00837 Xml::closeElement( 'fieldset' ) . 00838 Xml::closeElement( 'form' ) 00839 ); 00840 00841 # List undeletable articles 00842 if ( $this->mSearchPrefix ) { 00843 $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix ); 00844 $this->showList( $result ); 00845 } 00846 } 00847 00854 private function showList( $result ) { 00855 $out = $this->getOutput(); 00856 00857 if ( $result->numRows() == 0 ) { 00858 $out->addWikiMsg( 'undelete-no-results' ); 00859 00860 return false; 00861 } 00862 00863 $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) ); 00864 00865 $undelete = $this->getPageTitle(); 00866 $out->addHTML( "<ul>\n" ); 00867 foreach ( $result as $row ) { 00868 $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title ); 00869 if ( $title !== null ) { 00870 $item = Linker::linkKnown( 00871 $undelete, 00872 htmlspecialchars( $title->getPrefixedText() ), 00873 array(), 00874 array( 'target' => $title->getPrefixedText() ) 00875 ); 00876 } else { 00877 // The title is no longer valid, show as text 00878 $item = Html::element( 00879 'span', 00880 array( 'class' => 'mw-invalidtitle' ), 00881 Linker::getInvalidTitleDescription( 00882 $this->getContext(), 00883 $row->ar_namespace, 00884 $row->ar_title 00885 ) 00886 ); 00887 } 00888 $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse(); 00889 $out->addHTML( "<li>{$item} ({$revs})</li>\n" ); 00890 } 00891 $result->free(); 00892 $out->addHTML( "</ul>\n" ); 00893 00894 return true; 00895 } 00896 00897 private function showRevision( $timestamp ) { 00898 if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) { 00899 return; 00900 } 00901 00902 $archive = new PageArchive( $this->mTargetObj ); 00903 if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) { 00904 return; 00905 } 00906 $rev = $archive->getRevision( $timestamp ); 00907 00908 $out = $this->getOutput(); 00909 $user = $this->getUser(); 00910 00911 if ( !$rev ) { 00912 $out->addWikiMsg( 'undeleterevision-missing' ); 00913 00914 return; 00915 } 00916 00917 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { 00918 if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { 00919 $out->wrapWikiMsg( 00920 "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 00921 'rev-deleted-text-permission' 00922 ); 00923 00924 return; 00925 } 00926 00927 $out->wrapWikiMsg( 00928 "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 00929 'rev-deleted-text-view' 00930 ); 00931 $out->addHTML( '<br />' ); 00932 // and we are allowed to see... 00933 } 00934 00935 if ( $this->mDiff ) { 00936 $previousRev = $archive->getPreviousRevision( $timestamp ); 00937 if ( $previousRev ) { 00938 $this->showDiff( $previousRev, $rev ); 00939 if ( $this->mDiffOnly ) { 00940 return; 00941 } 00942 00943 $out->addHTML( '<hr />' ); 00944 } else { 00945 $out->addWikiMsg( 'undelete-nodiff' ); 00946 } 00947 } 00948 00949 $link = Linker::linkKnown( 00950 $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ), 00951 htmlspecialchars( $this->mTargetObj->getPrefixedText() ) 00952 ); 00953 00954 $lang = $this->getLanguage(); 00955 00956 // date and time are separate parameters to facilitate localisation. 00957 // $time is kept for backward compat reasons. 00958 $time = $lang->userTimeAndDate( $timestamp, $user ); 00959 $d = $lang->userDate( $timestamp, $user ); 00960 $t = $lang->userTime( $timestamp, $user ); 00961 $userLink = Linker::revUserTools( $rev ); 00962 00963 $content = $rev->getContent( Revision::FOR_THIS_USER, $user ); 00964 00965 $isText = ( $content instanceof TextContent ); 00966 00967 if ( $this->mPreview || $isText ) { 00968 $openDiv = '<div id="mw-undelete-revision" class="mw-warning">'; 00969 } else { 00970 $openDiv = '<div id="mw-undelete-revision">'; 00971 } 00972 $out->addHTML( $openDiv ); 00973 00974 // Revision delete links 00975 if ( !$this->mDiff ) { 00976 $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); 00977 if ( $revdel ) { 00978 $out->addHTML( "$revdel " ); 00979 } 00980 } 00981 00982 $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params( 00983 $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' ); 00984 00985 if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) { 00986 return; 00987 } 00988 00989 if ( $this->mPreview || !$isText ) { 00990 // NOTE: non-text content has no source view, so always use rendered preview 00991 00992 // Hide [edit]s 00993 $popts = $out->parserOptions(); 00994 $popts->setEditSection( false ); 00995 00996 $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true ); 00997 $out->addParserOutput( $pout ); 00998 } 00999 01000 if ( $isText ) { 01001 // source view for textual content 01002 $sourceView = Xml::element( 01003 'textarea', 01004 array( 01005 'readonly' => 'readonly', 01006 'cols' => $user->getIntOption( 'cols' ), 01007 'rows' => $user->getIntOption( 'rows' ) 01008 ), 01009 $content->getNativeData() . "\n" 01010 ); 01011 01012 $previewButton = Xml::element( 'input', array( 01013 'type' => 'submit', 01014 'name' => 'preview', 01015 'value' => $this->msg( 'showpreview' )->text() 01016 ) ); 01017 } else { 01018 $sourceView = ''; 01019 $previewButton = ''; 01020 } 01021 01022 $diffButton = Xml::element( 'input', array( 01023 'name' => 'diff', 01024 'type' => 'submit', 01025 'value' => $this->msg( 'showdiff' )->text() ) ); 01026 01027 $out->addHTML( 01028 $sourceView . 01029 Xml::openElement( 'div', array( 01030 'style' => 'clear: both' ) ) . 01031 Xml::openElement( 'form', array( 01032 'method' => 'post', 01033 'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) . 01034 Xml::element( 'input', array( 01035 'type' => 'hidden', 01036 'name' => 'target', 01037 'value' => $this->mTargetObj->getPrefixedDBkey() ) ) . 01038 Xml::element( 'input', array( 01039 'type' => 'hidden', 01040 'name' => 'timestamp', 01041 'value' => $timestamp ) ) . 01042 Xml::element( 'input', array( 01043 'type' => 'hidden', 01044 'name' => 'wpEditToken', 01045 'value' => $user->getEditToken() ) ) . 01046 $previewButton . 01047 $diffButton . 01048 Xml::closeElement( 'form' ) . 01049 Xml::closeElement( 'div' ) 01050 ); 01051 } 01052 01061 function showDiff( $previousRev, $currentRev ) { 01062 $diffContext = clone $this->getContext(); 01063 $diffContext->setTitle( $currentRev->getTitle() ); 01064 $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) ); 01065 01066 $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext ); 01067 $diffEngine->showDiffStyle(); 01068 01069 $formattedDiff = $diffEngine->generateContentDiffBody( 01070 $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ), 01071 $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ) 01072 ); 01073 01074 $formattedDiff = $diffEngine->addHeader( 01075 $formattedDiff, 01076 $this->diffHeader( $previousRev, 'o' ), 01077 $this->diffHeader( $currentRev, 'n' ) 01078 ); 01079 01080 $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" ); 01081 } 01082 01088 private function diffHeader( $rev, $prefix ) { 01089 $isDeleted = !( $rev->getId() && $rev->getTitle() ); 01090 if ( $isDeleted ) { 01092 $targetPage = $this->getPageTitle(); 01093 $targetQuery = array( 01094 'target' => $this->mTargetObj->getPrefixedText(), 01095 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() ) 01096 ); 01097 } else { 01099 $targetPage = $rev->getTitle(); 01100 $targetQuery = array( 'oldid' => $rev->getId() ); 01101 } 01102 01103 // Add show/hide deletion links if available 01104 $user = $this->getUser(); 01105 $lang = $this->getLanguage(); 01106 $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); 01107 01108 if ( $rdel ) { 01109 $rdel = " $rdel"; 01110 } 01111 01112 $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : ''; 01113 01114 $tags = wfGetDB( DB_SLAVE )->selectField( 01115 'tag_summary', 01116 'ts_tags', 01117 array( 'ts_rev_id' => $rev->getId() ), 01118 __METHOD__ 01119 ); 01120 $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff' ); 01121 01122 // FIXME This is reimplementing DifferenceEngine#getRevisionHeader 01123 // and partially #showDiffPage, but worse 01124 return '<div id="mw-diff-' . $prefix . 'title1"><strong>' . 01125 Linker::link( 01126 $targetPage, 01127 $this->msg( 01128 'revisionasof', 01129 $lang->userTimeAndDate( $rev->getTimestamp(), $user ), 01130 $lang->userDate( $rev->getTimestamp(), $user ), 01131 $lang->userTime( $rev->getTimestamp(), $user ) 01132 )->escaped(), 01133 array(), 01134 $targetQuery 01135 ) . 01136 '</strong></div>' . 01137 '<div id="mw-diff-' . $prefix . 'title2">' . 01138 Linker::revUserTools( $rev ) . '<br />' . 01139 '</div>' . 01140 '<div id="mw-diff-' . $prefix . 'title3">' . 01141 $minor . Linker::revComment( $rev ) . $rdel . '<br />' . 01142 '</div>' . 01143 '<div id="mw-diff-' . $prefix . 'title5">' . 01144 $tagSummary[0] . '<br />' . 01145 '</div>'; 01146 } 01147 01151 private function showFileConfirmationForm( $key ) { 01152 $out = $this->getOutput(); 01153 $lang = $this->getLanguage(); 01154 $user = $this->getUser(); 01155 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename ); 01156 $out->addWikiMsg( 'undelete-show-file-confirm', 01157 $this->mTargetObj->getText(), 01158 $lang->userDate( $file->getTimestamp(), $user ), 01159 $lang->userTime( $file->getTimestamp(), $user ) ); 01160 $out->addHTML( 01161 Xml::openElement( 'form', array( 01162 'method' => 'POST', 01163 'action' => $this->getPageTitle()->getLocalURL( array( 01164 'target' => $this->mTarget, 01165 'file' => $key, 01166 'token' => $user->getEditToken( $key ), 01167 ) ), 01168 ) 01169 ) . 01170 Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) . 01171 '</form>' 01172 ); 01173 } 01174 01178 private function showFile( $key ) { 01179 $this->getOutput()->disable(); 01180 01181 # We mustn't allow the output to be Squid cached, otherwise 01182 # if an admin previews a deleted image, and it's cached, then 01183 # a user without appropriate permissions can toddle off and 01184 # nab the image, and Squid will serve it 01185 $response = $this->getRequest()->response(); 01186 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' ); 01187 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' ); 01188 $response->header( 'Pragma: no-cache' ); 01189 01190 $repo = RepoGroup::singleton()->getLocalRepo(); 01191 $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key; 01192 $repo->streamFile( $path ); 01193 } 01194 01195 private function showHistory() { 01196 $out = $this->getOutput(); 01197 if ( $this->mAllowed ) { 01198 $out->addModules( 'mediawiki.special.undelete' ); 01199 } 01200 $out->wrapWikiMsg( 01201 "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n", 01202 array( 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) ) 01203 ); 01204 01205 $archive = new PageArchive( $this->mTargetObj ); 01206 wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) ); 01207 /* 01208 $text = $archive->getLastRevisionText(); 01209 if( is_null( $text ) ) { 01210 $out->addWikiMsg( 'nohistory' ); 01211 return; 01212 } 01213 */ 01214 $out->addHTML( '<div class="mw-undelete-history">' ); 01215 if ( $this->mAllowed ) { 01216 $out->addWikiMsg( 'undeletehistory' ); 01217 $out->addWikiMsg( 'undeleterevdel' ); 01218 } else { 01219 $out->addWikiMsg( 'undeletehistorynoadmin' ); 01220 } 01221 $out->addHTML( '</div>' ); 01222 01223 # List all stored revisions 01224 $revisions = $archive->listRevisions(); 01225 $files = $archive->listFiles(); 01226 01227 $haveRevisions = $revisions && $revisions->numRows() > 0; 01228 $haveFiles = $files && $files->numRows() > 0; 01229 01230 # Batch existence check on user and talk pages 01231 if ( $haveRevisions ) { 01232 $batch = new LinkBatch(); 01233 foreach ( $revisions as $row ) { 01234 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) ); 01235 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) ); 01236 } 01237 $batch->execute(); 01238 $revisions->seek( 0 ); 01239 } 01240 if ( $haveFiles ) { 01241 $batch = new LinkBatch(); 01242 foreach ( $files as $row ) { 01243 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) ); 01244 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) ); 01245 } 01246 $batch->execute(); 01247 $files->seek( 0 ); 01248 } 01249 01250 if ( $this->mAllowed ) { 01251 $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ); 01252 # Start the form here 01253 $top = Xml::openElement( 01254 'form', 01255 array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) 01256 ); 01257 $out->addHTML( $top ); 01258 } 01259 01260 # Show relevant lines from the deletion log: 01261 $deleteLogPage = new LogPage( 'delete' ); 01262 $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" ); 01263 LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj ); 01264 # Show relevant lines from the suppression log: 01265 $suppressLogPage = new LogPage( 'suppress' ); 01266 if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) { 01267 $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" ); 01268 LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj ); 01269 } 01270 01271 if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) { 01272 # Format the user-visible controls (comment field, submission button) 01273 # in a nice little table 01274 if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) { 01275 $unsuppressBox = 01276 "<tr> 01277 <td> </td> 01278 <td class='mw-input'>" . 01279 Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(), 01280 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ) . 01281 "</td> 01282 </tr>"; 01283 } else { 01284 $unsuppressBox = ''; 01285 } 01286 01287 $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) . 01288 Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) . 01289 "<tr> 01290 <td colspan='2' class='mw-undelete-extrahelp'>" . 01291 $this->msg( 'undeleteextrahelp' )->parseAsBlock() . 01292 "</td> 01293 </tr> 01294 <tr> 01295 <td class='mw-label'>" . 01296 Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) . 01297 "</td> 01298 <td class='mw-input'>" . 01299 Xml::input( 01300 'wpComment', 01301 50, 01302 $this->mComment, 01303 array( 'id' => 'wpComment', 'autofocus' => true ) 01304 ) . 01305 "</td> 01306 </tr> 01307 <tr> 01308 <td> </td> 01309 <td class='mw-submit'>" . 01310 Xml::submitButton( 01311 $this->msg( 'undeletebtn' )->text(), 01312 array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) 01313 ) . ' ' . 01314 Xml::submitButton( 01315 $this->msg( 'undeleteinvert' )->text(), 01316 array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) 01317 ) . 01318 "</td> 01319 </tr>" . 01320 $unsuppressBox . 01321 Xml::closeElement( 'table' ) . 01322 Xml::closeElement( 'fieldset' ); 01323 01324 $out->addHTML( $table ); 01325 } 01326 01327 $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" ); 01328 01329 if ( $haveRevisions ) { 01330 # The page's stored (deleted) history: 01331 $out->addHTML( '<ul>' ); 01332 $remaining = $revisions->numRows(); 01333 $earliestLiveTime = $this->mTargetObj->getEarliestRevTime(); 01334 01335 foreach ( $revisions as $row ) { 01336 $remaining--; 01337 $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) ); 01338 } 01339 $revisions->free(); 01340 $out->addHTML( '</ul>' ); 01341 } else { 01342 $out->addWikiMsg( 'nohistory' ); 01343 } 01344 01345 if ( $haveFiles ) { 01346 $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" ); 01347 $out->addHTML( '<ul>' ); 01348 foreach ( $files as $row ) { 01349 $out->addHTML( $this->formatFileRow( $row ) ); 01350 } 01351 $files->free(); 01352 $out->addHTML( '</ul>' ); 01353 } 01354 01355 if ( $this->mAllowed ) { 01356 # Slip in the hidden controls here 01357 $misc = Html::hidden( 'target', $this->mTarget ); 01358 $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() ); 01359 $misc .= Xml::closeElement( 'form' ); 01360 $out->addHTML( $misc ); 01361 } 01362 01363 return true; 01364 } 01365 01366 private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) { 01367 $rev = Revision::newFromArchiveRow( $row, 01368 array( 01369 'title' => $this->mTargetObj 01370 ) ); 01371 01372 $revTextSize = ''; 01373 $ts = wfTimestamp( TS_MW, $row->ar_timestamp ); 01374 // Build checkboxen... 01375 if ( $this->mAllowed ) { 01376 if ( $this->mInvert ) { 01377 if ( in_array( $ts, $this->mTargetTimestamp ) ) { 01378 $checkBox = Xml::check( "ts$ts" ); 01379 } else { 01380 $checkBox = Xml::check( "ts$ts", true ); 01381 } 01382 } else { 01383 $checkBox = Xml::check( "ts$ts" ); 01384 } 01385 } else { 01386 $checkBox = ''; 01387 } 01388 01389 // Build page & diff links... 01390 $user = $this->getUser(); 01391 if ( $this->mCanView ) { 01392 $titleObj = $this->getPageTitle(); 01393 # Last link 01394 if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) { 01395 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); 01396 $last = $this->msg( 'diff' )->escaped(); 01397 } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) { 01398 $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); 01399 $last = Linker::linkKnown( 01400 $titleObj, 01401 $this->msg( 'diff' )->escaped(), 01402 array(), 01403 array( 01404 'target' => $this->mTargetObj->getPrefixedText(), 01405 'timestamp' => $ts, 01406 'diff' => 'prev' 01407 ) 01408 ); 01409 } else { 01410 $pageLink = $this->getPageLink( $rev, $titleObj, $ts ); 01411 $last = $this->msg( 'diff' )->escaped(); 01412 } 01413 } else { 01414 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) ); 01415 $last = $this->msg( 'diff' )->escaped(); 01416 } 01417 01418 // User links 01419 $userLink = Linker::revUserTools( $rev ); 01420 01421 // Minor edit 01422 $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : ''; 01423 01424 // Revision text size 01425 $size = $row->ar_len; 01426 if ( !is_null( $size ) ) { 01427 $revTextSize = Linker::formatRevisionSize( $size ); 01428 } 01429 01430 // Edit summary 01431 $comment = Linker::revComment( $rev ); 01432 01433 // Tags 01434 $attribs = array(); 01435 list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'deletedhistory' ); 01436 if ( $classes ) { 01437 $attribs['class'] = implode( ' ', $classes ); 01438 } 01439 01440 // Revision delete links 01441 $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj ); 01442 01443 $revisionRow = $this->msg( 'undelete-revision-row' ) 01444 ->rawParams( 01445 $checkBox, 01446 $revdlink, 01447 $last, 01448 $pageLink, 01449 $userLink, 01450 $minor, 01451 $revTextSize, 01452 $comment, 01453 $tagSummary 01454 ) 01455 ->escaped(); 01456 01457 return Xml::tags( 'li', $attribs, $revisionRow ) . "\n"; 01458 } 01459 01460 private function formatFileRow( $row ) { 01461 $file = ArchivedFile::newFromRow( $row ); 01462 $ts = wfTimestamp( TS_MW, $row->fa_timestamp ); 01463 $user = $this->getUser(); 01464 01465 if ( $this->mAllowed && $row->fa_storage_key ) { 01466 $checkBox = Xml::check( 'fileid' . $row->fa_id ); 01467 $key = urlencode( $row->fa_storage_key ); 01468 $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key ); 01469 } else { 01470 $checkBox = ''; 01471 $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user ); 01472 } 01473 $userLink = $this->getFileUser( $file ); 01474 $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text(); 01475 $bytes = $this->msg( 'parentheses' ) 01476 ->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() ) 01477 ->plain(); 01478 $data = htmlspecialchars( $data . ' ' . $bytes ); 01479 $comment = $this->getFileComment( $file ); 01480 01481 // Add show/hide deletion links if available 01482 $canHide = $user->isAllowed( 'deleterevision' ); 01483 if ( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) { 01484 if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) { 01485 // Revision was hidden from sysops 01486 $revdlink = Linker::revDeleteLinkDisabled( $canHide ); 01487 } else { 01488 $query = array( 01489 'type' => 'filearchive', 01490 'target' => $this->mTargetObj->getPrefixedDBkey(), 01491 'ids' => $row->fa_id 01492 ); 01493 $revdlink = Linker::revDeleteLink( $query, 01494 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide ); 01495 } 01496 } else { 01497 $revdlink = ''; 01498 } 01499 01500 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n"; 01501 } 01502 01511 function getPageLink( $rev, $titleObj, $ts ) { 01512 $user = $this->getUser(); 01513 $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); 01514 01515 if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) { 01516 return '<span class="history-deleted">' . $time . '</span>'; 01517 } 01518 01519 $link = Linker::linkKnown( 01520 $titleObj, 01521 htmlspecialchars( $time ), 01522 array(), 01523 array( 01524 'target' => $this->mTargetObj->getPrefixedText(), 01525 'timestamp' => $ts 01526 ) 01527 ); 01528 01529 if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) { 01530 $link = '<span class="history-deleted">' . $link . '</span>'; 01531 } 01532 01533 return $link; 01534 } 01535 01546 function getFileLink( $file, $titleObj, $ts, $key ) { 01547 $user = $this->getUser(); 01548 $time = $this->getLanguage()->userTimeAndDate( $ts, $user ); 01549 01550 if ( !$file->userCan( File::DELETED_FILE, $user ) ) { 01551 return '<span class="history-deleted">' . $time . '</span>'; 01552 } 01553 01554 $link = Linker::linkKnown( 01555 $titleObj, 01556 htmlspecialchars( $time ), 01557 array(), 01558 array( 01559 'target' => $this->mTargetObj->getPrefixedText(), 01560 'file' => $key, 01561 'token' => $user->getEditToken( $key ) 01562 ) 01563 ); 01564 01565 if ( $file->isDeleted( File::DELETED_FILE ) ) { 01566 $link = '<span class="history-deleted">' . $link . '</span>'; 01567 } 01568 01569 return $link; 01570 } 01571 01578 function getFileUser( $file ) { 01579 if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) { 01580 return '<span class="history-deleted">' . 01581 $this->msg( 'rev-deleted-user' )->escaped() . 01582 '</span>'; 01583 } 01584 01585 $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) . 01586 Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() ); 01587 01588 if ( $file->isDeleted( File::DELETED_USER ) ) { 01589 $link = '<span class="history-deleted">' . $link . '</span>'; 01590 } 01591 01592 return $link; 01593 } 01594 01601 function getFileComment( $file ) { 01602 if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) { 01603 return '<span class="history-deleted"><span class="comment">' . 01604 $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>'; 01605 } 01606 01607 $link = Linker::commentBlock( $file->getRawDescription() ); 01608 01609 if ( $file->isDeleted( File::DELETED_COMMENT ) ) { 01610 $link = '<span class="history-deleted">' . $link . '</span>'; 01611 } 01612 01613 return $link; 01614 } 01615 01616 function undelete() { 01617 global $wgUploadMaintenance; 01618 01619 if ( $wgUploadMaintenance && $this->mTargetObj->getNamespace() == NS_FILE ) { 01620 throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' ); 01621 } 01622 01623 if ( wfReadOnly() ) { 01624 throw new ReadOnlyError; 01625 } 01626 01627 $out = $this->getOutput(); 01628 $archive = new PageArchive( $this->mTargetObj ); 01629 wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) ); 01630 $ok = $archive->undelete( 01631 $this->mTargetTimestamp, 01632 $this->mComment, 01633 $this->mFileVersions, 01634 $this->mUnsuppress, 01635 $this->getUser() 01636 ); 01637 01638 if ( is_array( $ok ) ) { 01639 if ( $ok[1] ) { // Undeleted file count 01640 wfRunHooks( 'FileUndeleteComplete', array( 01641 $this->mTargetObj, $this->mFileVersions, 01642 $this->getUser(), $this->mComment ) ); 01643 } 01644 01645 $link = Linker::linkKnown( $this->mTargetObj ); 01646 $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() ); 01647 } else { 01648 $out->setPageTitle( $this->msg( 'undelete-error' ) ); 01649 } 01650 01651 // Show revision undeletion warnings and errors 01652 $status = $archive->getRevisionStatus(); 01653 if ( $status && !$status->isGood() ) { 01654 $out->addWikiText( '<div class="error">' . 01655 $status->getWikiText( 01656 'cannotundelete', 01657 'cannotundelete' 01658 ) . '</div>' 01659 ); 01660 } 01661 01662 // Show file undeletion warnings and errors 01663 $status = $archive->getFileStatus(); 01664 if ( $status && !$status->isGood() ) { 01665 $out->addWikiText( '<div class="error">' . 01666 $status->getWikiText( 01667 'undelete-error-short', 01668 'undelete-error-long' 01669 ) . '</div>' 01670 ); 01671 } 01672 } 01673 01674 protected function getGroupName() { 01675 return 'pagetools'; 01676 } 01677 }