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