MediaWiki  REL1_23
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 
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>&#160;</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>&#160;</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 }