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