MediaWiki  REL1_19
SpecialUndelete.php
Go to the documentation of this file.
00001 <?php
00029 class PageArchive {
00030 
00034         protected $title;
00035         var $fileStatus;
00036 
00037         function __construct( $title ) {
00038                 if( is_null( $title ) ) {
00039                         throw new MWException( __METHOD__ . ' given a null title.' );
00040                 }
00041                 $this->title = $title;
00042         }
00043 
00051         public static function listAllPages() {
00052                 $dbr = wfGetDB( DB_SLAVE );
00053                 return self::listPages( $dbr, '' );
00054         }
00055 
00064         public static function listPagesByPrefix( $prefix ) {
00065                 $dbr = wfGetDB( DB_SLAVE );
00066 
00067                 $title = Title::newFromText( $prefix );
00068                 if( $title ) {
00069                         $ns = $title->getNamespace();
00070                         $prefix = $title->getDBkey();
00071                 } else {
00072                         // Prolly won't work too good
00073                         // @todo handle bare namespace names cleanly?
00074                         $ns = 0;
00075                 }
00076                 $conds = array(
00077                         'ar_namespace' => $ns,
00078                         'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
00079                 );
00080                 return self::listPages( $dbr, $conds );
00081         }
00082 
00088         protected static function listPages( $dbr, $condition ) {
00089                 return $dbr->resultObject(
00090                         $dbr->select(
00091                                 array( 'archive' ),
00092                                 array(
00093                                         'ar_namespace',
00094                                         'ar_title',
00095                                         'COUNT(*) AS count'
00096                                 ),
00097                                 $condition,
00098                                 __METHOD__,
00099                                 array(
00100                                         'GROUP BY' => 'ar_namespace,ar_title',
00101                                         'ORDER BY' => 'ar_namespace,ar_title',
00102                                         'LIMIT' => 100,
00103                                 )
00104                         )
00105                 );
00106         }
00107 
00114         function listRevisions() {
00115                 $dbr = wfGetDB( DB_SLAVE );
00116                 $res = $dbr->select( 'archive',
00117                         array(
00118                                 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
00119                                 'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1'
00120                         ),
00121                         array( 'ar_namespace' => $this->title->getNamespace(),
00122                                    'ar_title' => $this->title->getDBkey() ),
00123                         'PageArchive::listRevisions',
00124                         array( 'ORDER BY' => 'ar_timestamp DESC' ) );
00125                 $ret = $dbr->resultObject( $res );
00126                 return $ret;
00127         }
00128 
00137         function listFiles() {
00138                 if( $this->title->getNamespace() == NS_FILE ) {
00139                         $dbr = wfGetDB( DB_SLAVE );
00140                         $res = $dbr->select( 'filearchive',
00141                                 array(
00142                                         'fa_id',
00143                                         'fa_name',
00144                                         'fa_archive_name',
00145                                         'fa_storage_key',
00146                                         'fa_storage_group',
00147                                         'fa_size',
00148                                         'fa_width',
00149                                         'fa_height',
00150                                         'fa_bits',
00151                                         'fa_metadata',
00152                                         'fa_media_type',
00153                                         'fa_major_mime',
00154                                         'fa_minor_mime',
00155                                         'fa_description',
00156                                         'fa_user',
00157                                         'fa_user_text',
00158                                         'fa_timestamp',
00159                                         'fa_deleted' ),
00160                                 array( 'fa_name' => $this->title->getDBkey() ),
00161                                 __METHOD__,
00162                                 array( 'ORDER BY' => 'fa_timestamp DESC' ) );
00163                         $ret = $dbr->resultObject( $res );
00164                         return $ret;
00165                 }
00166                 return null;
00167         }
00168 
00176         function getRevision( $timestamp ) {
00177                 $dbr = wfGetDB( DB_SLAVE );
00178                 $row = $dbr->selectRow( 'archive',
00179                         array(
00180                                 'ar_rev_id',
00181                                 'ar_text',
00182                                 'ar_comment',
00183                                 'ar_user',
00184                                 'ar_user_text',
00185                                 'ar_timestamp',
00186                                 'ar_minor_edit',
00187                                 'ar_flags',
00188                                 'ar_text_id',
00189                                 'ar_deleted',
00190                                 'ar_len',
00191                                 'ar_sha1',
00192                         ),
00193                         array( 'ar_namespace' => $this->title->getNamespace(),
00194                                         'ar_title' => $this->title->getDBkey(),
00195                                         'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
00196                         __METHOD__ );
00197                 if( $row ) {
00198                         return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleId() ) );
00199                 } else {
00200                         return null;
00201                 }
00202         }
00203 
00214         function getPreviousRevision( $timestamp ) {
00215                 $dbr = wfGetDB( DB_SLAVE );
00216 
00217                 // Check the previous deleted revision...
00218                 $row = $dbr->selectRow( 'archive',
00219                         'ar_timestamp',
00220                         array( 'ar_namespace' => $this->title->getNamespace(),
00221                                    'ar_title' => $this->title->getDBkey(),
00222                                    'ar_timestamp < ' .
00223                                                 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
00224                         __METHOD__,
00225                         array(
00226                                 'ORDER BY' => 'ar_timestamp DESC',
00227                                 'LIMIT' => 1 ) );
00228                 $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
00229 
00230                 $row = $dbr->selectRow( array( 'page', 'revision' ),
00231                         array( 'rev_id', 'rev_timestamp' ),
00232                         array(
00233                                 'page_namespace' => $this->title->getNamespace(),
00234                                 'page_title' => $this->title->getDBkey(),
00235                                 'page_id = rev_page',
00236                                 'rev_timestamp < ' .
00237                                                 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
00238                         __METHOD__,
00239                         array(
00240                                 'ORDER BY' => 'rev_timestamp DESC',
00241                                 'LIMIT' => 1 ) );
00242                 $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
00243                 $prevLiveId = $row ? intval( $row->rev_id ) : null;
00244 
00245                 if( $prevLive && $prevLive > $prevDeleted ) {
00246                         // Most prior revision was live
00247                         return Revision::newFromId( $prevLiveId );
00248                 } elseif( $prevDeleted ) {
00249                         // Most prior revision was deleted
00250                         return $this->getRevision( $prevDeleted );
00251                 } else {
00252                         // No prior revision on this page.
00253                         return null;
00254                 }
00255         }
00256 
00263         function getTextFromRow( $row ) {
00264                 if( is_null( $row->ar_text_id ) ) {
00265                         // An old row from MediaWiki 1.4 or previous.
00266                         // Text is embedded in this row in classic compression format.
00267                         return Revision::getRevisionText( $row, 'ar_' );
00268                 } else {
00269                         // New-style: keyed to the text storage backend.
00270                         $dbr = wfGetDB( DB_SLAVE );
00271                         $text = $dbr->selectRow( 'text',
00272                                 array( 'old_text', 'old_flags' ),
00273                                 array( 'old_id' => $row->ar_text_id ),
00274                                 __METHOD__ );
00275                         return Revision::getRevisionText( $text );
00276                 }
00277         }
00278 
00287         function getLastRevisionText() {
00288                 $dbr = wfGetDB( DB_SLAVE );
00289                 $row = $dbr->selectRow( 'archive',
00290                         array( 'ar_text', 'ar_flags', 'ar_text_id' ),
00291                         array( 'ar_namespace' => $this->title->getNamespace(),
00292                                    'ar_title' => $this->title->getDBkey() ),
00293                         __METHOD__,
00294                         array( 'ORDER BY' => 'ar_timestamp DESC' ) );
00295                 if( $row ) {
00296                         return $this->getTextFromRow( $row );
00297                 } else {
00298                         return null;
00299                 }
00300         }
00301 
00307         function isDeleted() {
00308                 $dbr = wfGetDB( DB_SLAVE );
00309                 $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
00310                         array( 'ar_namespace' => $this->title->getNamespace(),
00311                                    'ar_title' => $this->title->getDBkey() ) );
00312                 return ( $n > 0 );
00313         }
00314 
00328         function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false ) {
00329                 // If both the set of text revisions and file revisions are empty,
00330                 // restore everything. Otherwise, just restore the requested items.
00331                 $restoreAll = empty( $timestamps ) && empty( $fileVersions );
00332 
00333                 $restoreText = $restoreAll || !empty( $timestamps );
00334                 $restoreFiles = $restoreAll || !empty( $fileVersions );
00335 
00336                 if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
00337                         $img = wfLocalFile( $this->title );
00338                         $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
00339                         if ( !$this->fileStatus->isOk() ) {
00340                                 return false;
00341                         }
00342                         $filesRestored = $this->fileStatus->successCount;
00343                 } else {
00344                         $filesRestored = 0;
00345                 }
00346 
00347                 if( $restoreText ) {
00348                         $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
00349                         if( $textRestored === false ) { // It must be one of UNDELETE_*
00350                                 return false;
00351                         }
00352                 } else {
00353                         $textRestored = 0;
00354                 }
00355 
00356                 // Touch the log!
00357                 global $wgContLang;
00358                 $log = new LogPage( 'delete' );
00359 
00360                 if( $textRestored && $filesRestored ) {
00361                         $reason = wfMsgExt( 'undeletedrevisions-files', array( 'content', 'parsemag' ),
00362                                 $wgContLang->formatNum( $textRestored ),
00363                                 $wgContLang->formatNum( $filesRestored ) );
00364                 } elseif( $textRestored ) {
00365                         $reason = wfMsgExt( 'undeletedrevisions', array( 'content', 'parsemag' ),
00366                                 $wgContLang->formatNum( $textRestored ) );
00367                 } elseif( $filesRestored ) {
00368                         $reason = wfMsgExt( 'undeletedfiles', array( 'content', 'parsemag' ),
00369                                 $wgContLang->formatNum( $filesRestored ) );
00370                 } else {
00371                         wfDebug( "Undelete: nothing undeleted...\n" );
00372                         return false;
00373                 }
00374 
00375                 if( trim( $comment ) != '' ) {
00376                         $reason .= wfMsgForContent( 'colon-separator' ) . $comment;
00377                 }
00378                 $log->addEntry( 'restore', $this->title, $reason );
00379 
00380                 return array( $textRestored, $filesRestored, $reason );
00381         }
00382 
00394         private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
00395                 if ( wfReadOnly() ) {
00396                         return false;
00397                 }
00398                 $restoreAll = empty( $timestamps );
00399 
00400                 $dbw = wfGetDB( DB_MASTER );
00401 
00402                 # Does this page already exist? We'll have to update it...
00403                 $article = WikiPage::factory( $this->title );
00404                 # Load latest data for the current page (bug 31179)
00405                 $article->loadPageData( 'fromdbmaster' );
00406                 $oldcountable = $article->isCountable();
00407 
00408                 $page = $dbw->selectRow( 'page',
00409                         array( 'page_id', 'page_latest' ),
00410                         array( 'page_namespace' => $this->title->getNamespace(),
00411                                    'page_title'     => $this->title->getDBkey() ),
00412                         __METHOD__,
00413                         array( 'FOR UPDATE' ) // lock page
00414                 );
00415                 if( $page ) {
00416                         $makepage = false;
00417                         # Page already exists. Import the history, and if necessary
00418                         # we'll update the latest revision field in the record.
00419                         $newid             = 0;
00420                         $pageId            = $page->page_id;
00421                         $previousRevId    = $page->page_latest;
00422                         # Get the time span of this page
00423                         $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
00424                                 array( 'rev_id' => $previousRevId ),
00425                                 __METHOD__ );
00426                         if( $previousTimestamp === false ) {
00427                                 wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
00428                                 return 0;
00429                         }
00430                 } else {
00431                         # Have to create a new article...
00432                         $makepage = true;
00433                         $previousRevId = 0;
00434                         $previousTimestamp = 0;
00435                 }
00436 
00437                 if( $restoreAll ) {
00438                         $oldones = '1 = 1'; # All revisions...
00439                 } else {
00440                         $oldts = implode( ',',
00441                                 array_map( array( &$dbw, 'addQuotes' ),
00442                                         array_map( array( &$dbw, 'timestamp' ),
00443                                                 $timestamps ) ) );
00444 
00445                         $oldones = "ar_timestamp IN ( {$oldts} )";
00446                 }
00447 
00451                 $result = $dbw->select( 'archive',
00452                         /* fields */ array(
00453                                 'ar_rev_id',
00454                                 'ar_text',
00455                                 'ar_comment',
00456                                 'ar_user',
00457                                 'ar_user_text',
00458                                 'ar_timestamp',
00459                                 'ar_minor_edit',
00460                                 'ar_flags',
00461                                 'ar_text_id',
00462                                 'ar_deleted',
00463                                 'ar_page_id',
00464                                 'ar_len',
00465                                 'ar_sha1' ),
00466                         /* WHERE */ array(
00467                                 'ar_namespace' => $this->title->getNamespace(),
00468                                 'ar_title'     => $this->title->getDBkey(),
00469                                 $oldones ),
00470                         __METHOD__,
00471                         /* options */ array( 'ORDER BY' => 'ar_timestamp' )
00472                 );
00473                 $ret = $dbw->resultObject( $result );
00474                 $rev_count = $dbw->numRows( $result );
00475                 if( !$rev_count ) {
00476                         wfDebug( __METHOD__ . ": no revisions to restore\n" );
00477                         return false; // ???
00478                 }
00479 
00480                 $ret->seek( $rev_count - 1 ); // move to last
00481                 $row = $ret->fetchObject(); // get newest archived rev
00482                 $ret->seek( 0 ); // move back
00483 
00484                 if( $makepage ) {
00485                         // Check the state of the newest to-be version...
00486                         if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
00487                                 return false; // we can't leave the current revision like this!
00488                         }
00489                         // Safe to insert now...
00490                         $newid  = $article->insertOn( $dbw );
00491                         $pageId = $newid;
00492                 } else {
00493                         // Check if a deleted revision will become the current revision...
00494                         if( $row->ar_timestamp > $previousTimestamp ) {
00495                                 // Check the state of the newest to-be version...
00496                                 if( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
00497                                         return false; // we can't leave the current revision like this!
00498                                 }
00499                         }
00500                 }
00501 
00502                 $revision = null;
00503                 $restored = 0;
00504 
00505                 foreach ( $ret as $row ) {
00506                         // Check for key dupes due to shitty archive integrity.
00507                         if( $row->ar_rev_id ) {
00508                                 $exists = $dbw->selectField( 'revision', '1',
00509                                         array( 'rev_id' => $row->ar_rev_id ), __METHOD__ );
00510                                 if( $exists ) {
00511                                         continue; // don't throw DB errors
00512                                 }
00513                         }
00514                         // Insert one revision at a time...maintaining deletion status
00515                         // unless we are specifically removing all restrictions...
00516                         $revision = Revision::newFromArchiveRow( $row,
00517                                 array(
00518                                         'page' => $pageId,
00519                                         'deleted' => $unsuppress ? 0 : $row->ar_deleted
00520                                 ) );
00521 
00522                         $revision->insertOn( $dbw );
00523                         $restored++;
00524 
00525                         wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) );
00526                 }
00527                 # Now that it's safely stored, take it out of the archive
00528                 $dbw->delete( 'archive',
00529                         /* WHERE */ array(
00530                                 'ar_namespace' => $this->title->getNamespace(),
00531                                 'ar_title' => $this->title->getDBkey(),
00532                                 $oldones ),
00533                         __METHOD__ );
00534 
00535                 // Was anything restored at all?
00536                 if ( $restored == 0 ) {
00537                         return 0;
00538                 }
00539 
00540                 $created = (bool)$newid;
00541 
00542                 // Attach the latest revision to the page...
00543                 $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
00544                 if ( $created || $wasnew ) {
00545                         // Update site stats, link tables, etc
00546                         $user = User::newFromName( $revision->getRawUserText(), false );
00547                         $article->doEditUpdates( $revision, $user, array( 'created' => $created, 'oldcountable' => $oldcountable ) );
00548                 }
00549 
00550                 wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment ) );
00551 
00552                 if( $this->title->getNamespace() == NS_FILE ) {
00553                         $update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
00554                         $update->doUpdate();
00555                 }
00556 
00557                 return $restored;
00558         }
00559 
00563         function getFileStatus() { return $this->fileStatus; }
00564 }
00565 
00572 class SpecialUndelete extends SpecialPage {
00573         var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mFilename;
00574         var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken;
00575 
00579         var $mTargetObj;
00580 
00581         function __construct() {
00582                 parent::__construct( 'Undelete', 'deletedhistory' );
00583         }
00584 
00585         function loadRequest( $par ) {
00586                 $request = $this->getRequest();
00587                 $user = $this->getUser();
00588 
00589                 $this->mAction = $request->getVal( 'action' );
00590                 if ( $par !== null && $par !== '' ) {
00591                         $this->mTarget = $par;
00592                 } else {
00593                         $this->mTarget = $request->getVal( 'target' );
00594                 }
00595                 $this->mTargetObj = null;
00596                 if ( $this->mTarget !== null && $this->mTarget !== '' ) {
00597                         $this->mTargetObj = Title::newFromURL( $this->mTarget );
00598                 }
00599                 $this->mSearchPrefix = $request->getText( 'prefix' );
00600                 $time = $request->getVal( 'timestamp' );
00601                 $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
00602                 $this->mFilename = $request->getVal( 'file' );
00603 
00604                 $posted = $request->wasPosted() &&
00605                         $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
00606                 $this->mRestore = $request->getCheck( 'restore' ) && $posted;
00607                 $this->mInvert = $request->getCheck( 'invert' ) && $posted;
00608                 $this->mPreview = $request->getCheck( 'preview' ) && $posted;
00609                 $this->mDiff = $request->getCheck( 'diff' );
00610                 $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) );
00611                 $this->mComment = $request->getText( 'wpComment' );
00612                 $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' );
00613                 $this->mToken = $request->getVal( 'token' );
00614 
00615                 if ( $user->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
00616                         $this->mAllowed = true; // user can restore
00617                         $this->mCanView = true; // user can view content
00618                 } elseif ( $user->isAllowed( 'deletedtext' ) ) {
00619                         $this->mAllowed = false; // user cannot restore
00620                         $this->mCanView = true; // user can view content
00621                         $this->mRestore = false;
00622                 } else { // user can only view the list of revisions
00623                         $this->mAllowed = false;
00624                         $this->mCanView = false;
00625                         $this->mTimestamp = '';
00626                         $this->mRestore = false;
00627                 }
00628 
00629                 if( $this->mRestore || $this->mInvert ) {
00630                         $timestamps = array();
00631                         $this->mFileVersions = array();
00632                         foreach( $request->getValues() as $key => $val ) {
00633                                 $matches = array();
00634                                 if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
00635                                         array_push( $timestamps, $matches[1] );
00636                                 }
00637 
00638                                 if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
00639                                         $this->mFileVersions[] = intval( $matches[1] );
00640                                 }
00641                         }
00642                         rsort( $timestamps );
00643                         $this->mTargetTimestamp = $timestamps;
00644                 }
00645         }
00646 
00647         function execute( $par ) {
00648                 $this->checkPermissions();
00649                 $user = $this->getUser();
00650 
00651                 $this->setHeaders();
00652                 $this->outputHeader();
00653 
00654                 $this->loadRequest( $par );
00655 
00656                 $out = $this->getOutput();
00657 
00658                 if ( is_null( $this->mTargetObj ) ) {
00659                         $out->addWikiMsg( 'undelete-header' );
00660 
00661                         # Not all users can just browse every deleted page from the list
00662                         if ( $user->isAllowed( 'browsearchive' ) ) {
00663                                 $this->showSearchForm();
00664                         }
00665                         return;
00666                 }
00667 
00668                 if ( $this->mAllowed ) {
00669                         $out->setPageTitle( $this->msg( 'undeletepage' ) );
00670                 } else {
00671                         $out->setPageTitle( $this->msg( 'viewdeletedpage' ) );
00672                 }
00673 
00674                 $this->getSkin()->setRelevantTitle( $this->mTargetObj );
00675 
00676                 if ( $this->mTimestamp !== '' ) {
00677                         $this->showRevision( $this->mTimestamp );
00678                 } elseif ( $this->mFilename !== null ) {
00679                         $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
00680                         // Check if user is allowed to see this file
00681                         if ( !$file->exists() ) {
00682                                 $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
00683                         } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
00684                                 if( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
00685                                         throw new PermissionsError( 'suppressrevision' );
00686                                 } else {
00687                                         throw new PermissionsError( 'deletedtext' );
00688                                 }
00689                         } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
00690                                 $this->showFileConfirmationForm( $this->mFilename );
00691                         } else {
00692                                 $this->showFile( $this->mFilename );
00693                         }
00694                 } elseif ( $this->mRestore && $this->mAction == 'submit' ) {
00695                         $this->undelete();
00696                 } else {
00697                         $this->showHistory();
00698                 }
00699         }
00700 
00701         function showSearchForm() {
00702                 global $wgScript;
00703 
00704                 $out = $this->getOutput();
00705                 $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
00706                 $out->addHTML(
00707                         Xml::openElement( 'form', array(
00708                                 'method' => 'get',
00709                                 'action' => $wgScript ) ) .
00710                         Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
00711                         Html::hidden( 'title',
00712                                 $this->getTitle()->getPrefixedDbKey() ) .
00713                         Xml::inputLabel( $this->msg( 'undelete-search-prefix' )->text(),
00714                                 'prefix', 'prefix', 20,
00715                                 $this->mSearchPrefix ) . ' ' .
00716                         Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
00717                         Xml::closeElement( 'fieldset' ) .
00718                         Xml::closeElement( 'form' )
00719                 );
00720 
00721                 # List undeletable articles
00722                 if( $this->mSearchPrefix ) {
00723                         $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
00724                         $this->showList( $result );
00725                 }
00726         }
00727 
00734         private function showList( $result ) {
00735                 $out = $this->getOutput();
00736 
00737                 if( $result->numRows() == 0 ) {
00738                         $out->addWikiMsg( 'undelete-no-results' );
00739                         return;
00740                 }
00741 
00742                 $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
00743 
00744                 $undelete = $this->getTitle();
00745                 $out->addHTML( "<ul>\n" );
00746                 foreach ( $result as $row ) {
00747                         $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
00748                         $link = Linker::linkKnown(
00749                                 $undelete,
00750                                 htmlspecialchars( $title->getPrefixedText() ),
00751                                 array(),
00752                                 array( 'target' => $title->getPrefixedText() )
00753                         );
00754                         $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
00755                         $out->addHTML( "<li>{$link} ({$revs})</li>\n" );
00756                 }
00757                 $result->free();
00758                 $out->addHTML( "</ul>\n" );
00759 
00760                 return true;
00761         }
00762 
00763         private function showRevision( $timestamp ) {
00764                 if( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
00765                         return 0;
00766                 }
00767 
00768                 $archive = new PageArchive( $this->mTargetObj );
00769                 wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) );
00770                 $rev = $archive->getRevision( $timestamp );
00771 
00772                 $out = $this->getOutput();
00773                 $user = $this->getUser();
00774 
00775                 if( !$rev ) {
00776                         $out->addWikiMsg( 'undeleterevision-missing' );
00777                         return;
00778                 }
00779 
00780                 if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
00781                         if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
00782                                 $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-permission' );
00783                                 return;
00784                         } else {
00785                                 $out->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", 'rev-deleted-text-view' );
00786                                 $out->addHTML( '<br />' );
00787                                 // and we are allowed to see...
00788                         }
00789                 }
00790 
00791                 if( $this->mDiff ) {
00792                         $previousRev = $archive->getPreviousRevision( $timestamp );
00793                         if( $previousRev ) {
00794                                 $this->showDiff( $previousRev, $rev );
00795                                 if( $this->mDiffOnly ) {
00796                                         return;
00797                                 } else {
00798                                         $out->addHTML( '<hr />' );
00799                                 }
00800                         } else {
00801                                 $out->addWikiMsg( 'undelete-nodiff' );
00802                         }
00803                 }
00804 
00805                 $link = Linker::linkKnown(
00806                         $this->getTitle( $this->mTargetObj->getPrefixedDBkey() ),
00807                         htmlspecialchars( $this->mTargetObj->getPrefixedText() )
00808                 );
00809 
00810                 $lang = $this->getLanguage();
00811 
00812                 // date and time are separate parameters to facilitate localisation.
00813                 // $time is kept for backward compat reasons.
00814                 $time = $lang->userTimeAndDate( $timestamp, $user );
00815                 $d = $lang->userDate( $timestamp, $user );
00816                 $t = $lang->userTime( $timestamp, $user );
00817                 $userLink = Linker::revUserTools( $rev );
00818 
00819                 if( $this->mPreview ) {
00820                         $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
00821                 } else {
00822                         $openDiv = '<div id="mw-undelete-revision">';
00823                 }
00824                 $out->addHTML( $openDiv );
00825 
00826                 // Revision delete links
00827                 if ( !$this->mDiff ) {
00828                         $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
00829                         if ( $revdel ) {
00830                                 $out->addHTML( "$revdel " );
00831                         }
00832                 }
00833 
00834                 $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
00835                         $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
00836                 wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
00837 
00838                 if( $this->mPreview ) {
00839                         // Hide [edit]s
00840                         $popts = $out->parserOptions();
00841                         $popts->setEditSection( false );
00842                         $out->parserOptions( $popts );
00843                         $out->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER, $user ), $this->mTargetObj, true );
00844                 }
00845 
00846                 $out->addHTML(
00847                         Xml::element( 'textarea', array(
00848                                         'readonly' => 'readonly',
00849                                         'cols' => intval( $user->getOption( 'cols' ) ),
00850                                         'rows' => intval( $user->getOption( 'rows' ) ) ),
00851                                 $rev->getText( Revision::FOR_THIS_USER, $user ) . "\n" ) .
00852                         Xml::openElement( 'div' ) .
00853                         Xml::openElement( 'form', array(
00854                                 'method' => 'post',
00855                                 'action' => $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
00856                         Xml::element( 'input', array(
00857                                 'type' => 'hidden',
00858                                 'name' => 'target',
00859                                 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) .
00860                         Xml::element( 'input', array(
00861                                 'type' => 'hidden',
00862                                 'name' => 'timestamp',
00863                                 'value' => $timestamp ) ) .
00864                         Xml::element( 'input', array(
00865                                 'type' => 'hidden',
00866                                 'name' => 'wpEditToken',
00867                                 'value' => $user->getEditToken() ) ) .
00868                         Xml::element( 'input', array(
00869                                 'type' => 'submit',
00870                                 'name' => 'preview',
00871                                 'value' => $this->msg( 'showpreview' )->text() ) ) .
00872                         Xml::element( 'input', array(
00873                                 'name' => 'diff',
00874                                 'type' => 'submit',
00875                                 'value' => $this->msg( 'showdiff' )->text() ) ) .
00876                         Xml::closeElement( 'form' ) .
00877                         Xml::closeElement( 'div' ) );
00878         }
00879 
00888         function showDiff( $previousRev, $currentRev ) {
00889                 $diffEngine = new DifferenceEngine( $this->getContext() );
00890                 $diffEngine->showDiffStyle();
00891                 $this->getOutput()->addHTML(
00892                         "<div>" .
00893                         "<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>" .
00894                         "<col class='diff-marker' />" .
00895                         "<col class='diff-content' />" .
00896                         "<col class='diff-marker' />" .
00897                         "<col class='diff-content' />" .
00898                         "<tr>" .
00899                                 "<td colspan='2' width='50%' align='center' class='diff-otitle'>" .
00900                                 $this->diffHeader( $previousRev, 'o' ) .
00901                                 "</td>\n" .
00902                                 "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" .
00903                                 $this->diffHeader( $currentRev, 'n' ) .
00904                                 "</td>\n" .
00905                         "</tr>" .
00906                         $diffEngine->generateDiffBody(
00907                                 $previousRev->getText(), $currentRev->getText() ) .
00908                         "</table>" .
00909                         "</div>\n"
00910                 );
00911         }
00912 
00918         private function diffHeader( $rev, $prefix ) {
00919                 $isDeleted = !( $rev->getId() && $rev->getTitle() );
00920                 if( $isDeleted ) {
00922                         $targetPage = $this->getTitle();
00923                         $targetQuery = array(
00924                                 'target' => $this->mTargetObj->getPrefixedText(),
00925                                 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
00926                         );
00927                 } else {
00929                         $targetPage = $rev->getTitle();
00930                         $targetQuery = array( 'oldid' => $rev->getId() );
00931                 }
00932                 // Add show/hide deletion links if available
00933                 $user = $this->getUser();
00934                 $lang = $this->getLanguage();
00935                 $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
00936                 if ( $rdel ) $rdel = " $rdel";
00937                 return
00938                         '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
00939                                 Linker::link(
00940                                         $targetPage,
00941                                         $this->msg(
00942                                                 'revisionasof',
00943                                                 $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
00944                                                 $lang->userDate( $rev->getTimestamp(), $user ),
00945                                                 $lang->userTime( $rev->getTimestamp(), $user )
00946                                         )->escaped(),
00947                                         array(),
00948                                         $targetQuery
00949                                 ) .
00950                         '</strong></div>' .
00951                         '<div id="mw-diff-'.$prefix.'title2">' .
00952                                 Linker::revUserTools( $rev ) . '<br />' .
00953                         '</div>' .
00954                         '<div id="mw-diff-'.$prefix.'title3">' .
00955                                 Linker::revComment( $rev ) . $rdel . '<br />' .
00956                         '</div>';
00957         }
00958 
00962         private function showFileConfirmationForm( $key ) {
00963                 $out = $this->getOutput();
00964                 $lang = $this->getLanguage();
00965                 $user = $this->getUser();
00966                 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
00967                 $out->addWikiMsg( 'undelete-show-file-confirm',
00968                         $this->mTargetObj->getText(),
00969                         $lang->userDate( $file->getTimestamp(), $user ),
00970                         $lang->userTime( $file->getTimestamp(), $user ) );
00971                 $out->addHTML(
00972                         Xml::openElement( 'form', array(
00973                                 'method' => 'POST',
00974                                 'action' => $this->getTitle()->getLocalURL(
00975                                         'target=' . urlencode( $this->mTarget ) .
00976                                         '&file=' . urlencode( $key ) .
00977                                         '&token=' . urlencode( $user->getEditToken( $key ) ) )
00978                                 )
00979                         ) .
00980                         Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
00981                         '</form>'
00982                 );
00983         }
00984 
00988         private function showFile( $key ) {
00989                 $this->getOutput()->disable();
00990 
00991                 # We mustn't allow the output to be Squid cached, otherwise
00992                 # if an admin previews a deleted image, and it's cached, then
00993                 # a user without appropriate permissions can toddle off and
00994                 # nab the image, and Squid will serve it
00995                 $response = $this->getRequest()->response();
00996                 $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
00997                 $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
00998                 $response->header( 'Pragma: no-cache' );
00999 
01000                 $repo = RepoGroup::singleton()->getLocalRepo();
01001                 $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
01002                 $repo->streamFile( $path );
01003         }
01004 
01005         private function showHistory() {
01006                 $out = $this->getOutput();
01007                 if( $this->mAllowed ) {
01008                         $out->addModules( 'mediawiki.special.undelete' );
01009                 }
01010                 $out->wrapWikiMsg(
01011                         "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
01012                         array( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() )
01013                 );
01014 
01015                 $archive = new PageArchive( $this->mTargetObj );
01016                 wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
01017                 /*
01018                 $text = $archive->getLastRevisionText();
01019                 if( is_null( $text ) ) {
01020                         $out->addWikiMsg( 'nohistory' );
01021                         return;
01022                 }
01023                 */
01024                 $out->addHTML( '<div class="mw-undelete-history">' );
01025                 if ( $this->mAllowed ) {
01026                         $out->addWikiMsg( 'undeletehistory' );
01027                         $out->addWikiMsg( 'undeleterevdel' );
01028                 } else {
01029                         $out->addWikiMsg( 'undeletehistorynoadmin' );
01030                 }
01031                 $out->addHTML( '</div>' );
01032 
01033                 # List all stored revisions
01034                 $revisions = $archive->listRevisions();
01035                 $files = $archive->listFiles();
01036 
01037                 $haveRevisions = $revisions && $revisions->numRows() > 0;
01038                 $haveFiles = $files && $files->numRows() > 0;
01039 
01040                 # Batch existence check on user and talk pages
01041                 if( $haveRevisions ) {
01042                         $batch = new LinkBatch();
01043                         foreach ( $revisions as $row ) {
01044                                 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
01045                                 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
01046                         }
01047                         $batch->execute();
01048                         $revisions->seek( 0 );
01049                 }
01050                 if( $haveFiles ) {
01051                         $batch = new LinkBatch();
01052                         foreach ( $files as $row ) {
01053                                 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
01054                                 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) );
01055                         }
01056                         $batch->execute();
01057                         $files->seek( 0 );
01058                 }
01059 
01060                 if ( $this->mAllowed ) {
01061                         $action = $this->getTitle()->getLocalURL( array( 'action' => 'submit' ) );
01062                         # Start the form here
01063                         $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) );
01064                         $out->addHTML( $top );
01065                 }
01066 
01067                 # Show relevant lines from the deletion log:
01068                 $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" );
01069                 LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
01070                 # Show relevant lines from the suppression log:
01071                 if( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
01072                         $out->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" );
01073                         LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
01074                 }
01075 
01076                 if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
01077                         # Format the user-visible controls (comment field, submission button)
01078                         # in a nice little table
01079                         if( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
01080                                 $unsuppressBox =
01081                                         "<tr>
01082                                                 <td>&#160;</td>
01083                                                 <td class='mw-input'>" .
01084                                                         Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(),
01085                                                                 'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ).
01086                                                 "</td>
01087                                         </tr>";
01088                         } else {
01089                                 $unsuppressBox = '';
01090                         }
01091                         $table =
01092                                 Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
01093                                 Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
01094                                         "<tr>
01095                                                 <td colspan='2' class='mw-undelete-extrahelp'>" .
01096                                                         $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
01097                                                 "</td>
01098                                         </tr>
01099                                         <tr>
01100                                                 <td class='mw-label'>" .
01101                                                         Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
01102                                                 "</td>
01103                                                 <td class='mw-input'>" .
01104                                                         Xml::input( 'wpComment', 50, $this->mComment, array( 'id' =>  'wpComment' ) ) .
01105                                                 "</td>
01106                                         </tr>
01107                                         <tr>
01108                                                 <td>&#160;</td>
01109                                                 <td class='mw-submit'>" .
01110                                                         Xml::submitButton( $this->msg( 'undeletebtn' )->text(), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
01111                                                         Xml::submitButton( $this->msg( 'undeleteinvert' )->text(), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
01112                                                 "</td>
01113                                         </tr>" .
01114                                         $unsuppressBox .
01115                                 Xml::closeElement( 'table' ) .
01116                                 Xml::closeElement( 'fieldset' );
01117 
01118                         $out->addHTML( $table );
01119                 }
01120 
01121                 $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" );
01122 
01123                 if( $haveRevisions ) {
01124                         # The page's stored (deleted) history:
01125                         $out->addHTML( '<ul>' );
01126                         $remaining = $revisions->numRows();
01127                         $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
01128 
01129                         foreach ( $revisions as $row ) {
01130                                 $remaining--;
01131                                 $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) );
01132                         }
01133                         $revisions->free();
01134                         $out->addHTML( '</ul>' );
01135                 } else {
01136                         $out->addWikiMsg( 'nohistory' );
01137                 }
01138 
01139                 if( $haveFiles ) {
01140                         $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" );
01141                         $out->addHTML( '<ul>' );
01142                         foreach ( $files as $row ) {
01143                                 $out->addHTML( $this->formatFileRow( $row ) );
01144                         }
01145                         $files->free();
01146                         $out->addHTML( '</ul>' );
01147                 }
01148 
01149                 if ( $this->mAllowed ) {
01150                         # Slip in the hidden controls here
01151                         $misc  = Html::hidden( 'target', $this->mTarget );
01152                         $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
01153                         $misc .= Xml::closeElement( 'form' );
01154                         $out->addHTML( $misc );
01155                 }
01156 
01157                 return true;
01158         }
01159 
01160         private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
01161                 $rev = Revision::newFromArchiveRow( $row,
01162                         array( 'page' => $this->mTargetObj->getArticleId() ) );
01163                 $stxt = '';
01164                 $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
01165                 // Build checkboxen...
01166                 if( $this->mAllowed ) {
01167                         if( $this->mInvert ) {
01168                                 if( in_array( $ts, $this->mTargetTimestamp ) ) {
01169                                         $checkBox = Xml::check( "ts$ts" );
01170                                 } else {
01171                                         $checkBox = Xml::check( "ts$ts", true );
01172                                 }
01173                         } else {
01174                                 $checkBox = Xml::check( "ts$ts" );
01175                         }
01176                 } else {
01177                         $checkBox = '';
01178                 }
01179                 $user = $this->getUser();
01180                 // Build page & diff links...
01181                 if( $this->mCanView ) {
01182                         $titleObj = $this->getTitle();
01183                         # Last link
01184                         if( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
01185                                 $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
01186                                 $last = $this->msg( 'diff' )->escaped();
01187                         } elseif( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
01188                                 $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
01189                                 $last = Linker::linkKnown(
01190                                         $titleObj,
01191                                         $this->msg( 'diff' )->escaped(),
01192                                         array(),
01193                                         array(
01194                                                 'target' => $this->mTargetObj->getPrefixedText(),
01195                                                 'timestamp' => $ts,
01196                                                 'diff' => 'prev'
01197                                         )
01198                                 );
01199                         } else {
01200                                 $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
01201                                 $last = $this->msg( 'diff' )->escaped();
01202                         }
01203                 } else {
01204                         $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
01205                         $last = $this->msg( 'diff' )->escaped();
01206                 }
01207                 // User links
01208                 $userLink = Linker::revUserTools( $rev );
01209                 // Revision text size
01210                 $size = $row->ar_len;
01211                 if( !is_null( $size ) ) {
01212                         $stxt = Linker::formatRevisionSize( $size );
01213                 }
01214                 // Edit summary
01215                 $comment = Linker::revComment( $rev );
01216                 // Revision delete links
01217                 $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
01218                 return "<li>$checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment</li>";
01219         }
01220 
01221         private function formatFileRow( $row ) {
01222                 $file = ArchivedFile::newFromRow( $row );
01223 
01224                 $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
01225                 $user = $this->getUser();
01226                 if( $this->mAllowed && $row->fa_storage_key ) {
01227                         $checkBox = Xml::check( 'fileid' . $row->fa_id );
01228                         $key = urlencode( $row->fa_storage_key );
01229                         $pageLink = $this->getFileLink( $file, $this->getTitle(), $ts, $key );
01230                 } else {
01231                         $checkBox = '';
01232                         $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
01233                 }
01234                 $userLink = $this->getFileUser( $file );
01235                 $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text() .
01236                         ' (' . $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() . ')';
01237                 $data = htmlspecialchars( $data );
01238                 $comment = $this->getFileComment( $file );
01239 
01240                 // Add show/hide deletion links if available
01241                 $canHide = $user->isAllowed( 'deleterevision' );
01242                 if( $canHide || ( $file->getVisibility() && $user->isAllowed( 'deletedhistory' ) ) ) {
01243                         if( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
01244                                 $revdlink = Linker::revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
01245                         } else {
01246                                 $query = array(
01247                                         'type' => 'filearchive',
01248                                         'target' => $this->mTargetObj->getPrefixedDBkey(),
01249                                         'ids' => $row->fa_id
01250                                 );
01251                                 $revdlink = Linker::revDeleteLink( $query,
01252                                         $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
01253                         }
01254                 } else {
01255                         $revdlink = '';
01256                 }
01257 
01258                 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
01259         }
01260 
01269         function getPageLink( $rev, $titleObj, $ts ) {
01270                 $user = $this->getUser();
01271                 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
01272 
01273                 if( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
01274                         return '<span class="history-deleted">' . $time . '</span>';
01275                 } else {
01276                         $link = Linker::linkKnown(
01277                                 $titleObj,
01278                                 htmlspecialchars( $time ),
01279                                 array(),
01280                                 array(
01281                                         'target' => $this->mTargetObj->getPrefixedText(),
01282                                         'timestamp' => $ts
01283                                 )
01284                         );
01285                         if( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
01286                                 $link = '<span class="history-deleted">' . $link . '</span>';
01287                         }
01288                         return $link;
01289                 }
01290         }
01291 
01302         function getFileLink( $file, $titleObj, $ts, $key ) {
01303                 $user = $this->getUser();
01304                 $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
01305 
01306                 if( !$file->userCan( File::DELETED_FILE, $user ) ) {
01307                         return '<span class="history-deleted">' . $time . '</span>';
01308                 } else {
01309                         $link = Linker::linkKnown(
01310                                 $titleObj,
01311                                 htmlspecialchars( $time ),
01312                                 array(),
01313                                 array(
01314                                         'target' => $this->mTargetObj->getPrefixedText(),
01315                                         'file' => $key,
01316                                         'token' => $user->getEditToken( $key )
01317                                 )
01318                         );
01319                         if( $file->isDeleted( File::DELETED_FILE ) ) {
01320                                 $link = '<span class="history-deleted">' . $link . '</span>';
01321                         }
01322                         return $link;
01323                 }
01324         }
01325 
01332         function getFileUser( $file ) {
01333                 if( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
01334                         return '<span class="history-deleted">' . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
01335                 } else {
01336                         $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
01337                                 Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
01338                         if( $file->isDeleted( File::DELETED_USER ) ) {
01339                                 $link = '<span class="history-deleted">' . $link . '</span>';
01340                         }
01341                         return $link;
01342                 }
01343         }
01344 
01351         function getFileComment( $file ) {
01352                 if( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
01353                         return '<span class="history-deleted"><span class="comment">' .
01354                                 $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
01355                 } else {
01356                         $link = Linker::commentBlock( $file->getRawDescription() );
01357                         if( $file->isDeleted( File::DELETED_COMMENT ) ) {
01358                                 $link = '<span class="history-deleted">' . $link . '</span>';
01359                         }
01360                         return $link;
01361                 }
01362         }
01363 
01364         function undelete() {
01365                 global $wgUploadMaintenance;
01366 
01367                 if ( $wgUploadMaintenance && $this->mTargetObj->getNamespace() == NS_FILE ) {
01368                         throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
01369                 }
01370 
01371                 if ( wfReadOnly() ) {
01372                         throw new ReadOnlyError;
01373                 }
01374 
01375                 $out = $this->getOutput();
01376                 $archive = new PageArchive( $this->mTargetObj );
01377                 wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
01378                 $ok = $archive->undelete(
01379                         $this->mTargetTimestamp,
01380                         $this->mComment,
01381                         $this->mFileVersions,
01382                         $this->mUnsuppress );
01383 
01384                 if( is_array( $ok ) ) {
01385                         if ( $ok[1] ) { // Undeleted file count
01386                                 wfRunHooks( 'FileUndeleteComplete', array(
01387                                         $this->mTargetObj, $this->mFileVersions,
01388                                         $this->getUser(), $this->mComment ) );
01389                         }
01390 
01391                         $link = Linker::linkKnown( $this->mTargetObj );
01392                         $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
01393                 } else {
01394                         $out->setPageTitle( $this->msg( 'undelete-error' ) );
01395                         $out->addWikiMsg( 'cannotundelete' );
01396                         $out->addWikiMsg( 'undeleterevdel' );
01397                 }
01398 
01399                 // Show file deletion warnings and errors
01400                 $status = $archive->getFileStatus();
01401                 if( $status && !$status->isGood() ) {
01402                         $out->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
01403                 }
01404         }
01405 }