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