]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/specials/SpecialUndelete.php
MediaWiki 1.16.0
[autoinstallsdev/mediawiki.git] / includes / specials / SpecialUndelete.php
1 <?php
2
3 /**
4  * Special page allowing users with the appropriate permissions to view
5  * and restore deleted content
6  *
7  * @file
8  * @ingroup SpecialPage
9  */
10
11 /**
12  * Constructor
13  */
14 function wfSpecialUndelete( $par ) {
15         global $wgRequest;
16
17         $form = new UndeleteForm( $wgRequest, $par );
18         $form->execute();
19 }
20
21 /**
22  * Used to show archived pages and eventually restore them.
23  * @ingroup SpecialPage
24  */
25 class PageArchive {
26         protected $title;
27         var $fileStatus;
28
29         function __construct( $title ) {
30                 if( is_null( $title ) ) {
31                         throw new MWException( 'Archiver() given a null title.');
32                 }
33                 $this->title = $title;
34         }
35
36         /**
37          * List all deleted pages recorded in the archive table. Returns result
38          * wrapper with (ar_namespace, ar_title, count) fields, ordered by page
39          * namespace/title.
40          *
41          * @return ResultWrapper
42          */
43         public static function listAllPages() {
44                 $dbr = wfGetDB( DB_SLAVE );
45                 return self::listPages( $dbr, '' );
46         }
47
48         /**
49          * List deleted pages recorded in the archive table matching the
50          * given title prefix.
51          * Returns result wrapper with (ar_namespace, ar_title, count) fields.
52          *
53          * @return ResultWrapper
54          */
55         public static function listPagesByPrefix( $prefix ) {
56                 $dbr = wfGetDB( DB_SLAVE );
57
58                 $title = Title::newFromText( $prefix );
59                 if( $title ) {
60                         $ns = $title->getNamespace();
61                         $prefix = $title->getDBkey();
62                 } else {
63                         // Prolly won't work too good
64                         // @todo handle bare namespace names cleanly?
65                         $ns = 0;
66                 }
67                 $conds = array(
68                         'ar_namespace' => $ns,
69                         'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
70                 );
71                 return self::listPages( $dbr, $conds );
72         }
73
74         protected static function listPages( $dbr, $condition ) {
75                 return $dbr->resultObject(
76                         $dbr->select(
77                                 array( 'archive' ),
78                                 array(
79                                         'ar_namespace',
80                                         'ar_title',
81                                         'COUNT(*) AS count'
82                                 ),
83                                 $condition,
84                                 __METHOD__,
85                                 array(
86                                         'GROUP BY' => 'ar_namespace,ar_title',
87                                         'ORDER BY' => 'ar_namespace,ar_title',
88                                         'LIMIT' => 100,
89                                 )
90                         )
91                 );
92         }
93
94         /**
95          * List the revisions of the given page. Returns result wrapper with
96          * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields.
97          *
98          * @return ResultWrapper
99          */
100         function listRevisions() {
101                 $dbr = wfGetDB( DB_SLAVE );
102                 $res = $dbr->select( 'archive',
103                         array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment', 'ar_len', 'ar_deleted' ),
104                         array( 'ar_namespace' => $this->title->getNamespace(),
105                                'ar_title' => $this->title->getDBkey() ),
106                         'PageArchive::listRevisions',
107                         array( 'ORDER BY' => 'ar_timestamp DESC' ) );
108                 $ret = $dbr->resultObject( $res );
109                 return $ret;
110         }
111
112         /**
113          * List the deleted file revisions for this page, if it's a file page.
114          * Returns a result wrapper with various filearchive fields, or null
115          * if not a file page.
116          *
117          * @return ResultWrapper
118          * @todo Does this belong in Image for fuller encapsulation?
119          */
120         function listFiles() {
121                 if( $this->title->getNamespace() == NS_FILE ) {
122                         $dbr = wfGetDB( DB_SLAVE );
123                         $res = $dbr->select( 'filearchive',
124                                 array(
125                                         'fa_id',
126                                         'fa_name',
127                                         'fa_archive_name',
128                                         'fa_storage_key',
129                                         'fa_storage_group',
130                                         'fa_size',
131                                         'fa_width',
132                                         'fa_height',
133                                         'fa_bits',
134                                         'fa_metadata',
135                                         'fa_media_type',
136                                         'fa_major_mime',
137                                         'fa_minor_mime',
138                                         'fa_description',
139                                         'fa_user',
140                                         'fa_user_text',
141                                         'fa_timestamp',
142                                         'fa_deleted' ),
143                                 array( 'fa_name' => $this->title->getDBkey() ),
144                                 __METHOD__,
145                                 array( 'ORDER BY' => 'fa_timestamp DESC' ) );
146                         $ret = $dbr->resultObject( $res );
147                         return $ret;
148                 }
149                 return null;
150         }
151
152         /**
153          * Fetch (and decompress if necessary) the stored text for the deleted
154          * revision of the page with the given timestamp.
155          *
156          * @return string
157          * @deprecated Use getRevision() for more flexible information
158          */
159         function getRevisionText( $timestamp ) {
160                 $rev = $this->getRevision( $timestamp );
161                 return $rev ? $rev->getText() : null;
162         }
163
164         /**
165          * Return a Revision object containing data for the deleted revision.
166          * Note that the result *may* or *may not* have a null page ID.
167          * @param string $timestamp
168          * @return Revision
169          */
170         function getRevision( $timestamp ) {
171                 $dbr = wfGetDB( DB_SLAVE );
172                 $row = $dbr->selectRow( 'archive',
173                         array(
174                                 'ar_rev_id',
175                                 'ar_text',
176                                 'ar_comment',
177                                 'ar_user',
178                                 'ar_user_text',
179                                 'ar_timestamp',
180                                 'ar_minor_edit',
181                                 'ar_flags',
182                                 'ar_text_id',
183                                 'ar_deleted',
184                                 'ar_len' ),
185                         array( 'ar_namespace' => $this->title->getNamespace(),
186                                'ar_title' => $this->title->getDBkey(),
187                                'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
188                         __METHOD__ );
189                 if( $row ) {
190                         return Revision::newFromArchiveRow( $row, array( 'page' => $this->title->getArticleId() ) );
191                 } else {
192                         return null;
193                 }
194         }
195
196         /**
197          * Return the most-previous revision, either live or deleted, against
198          * the deleted revision given by timestamp.
199          *
200          * May produce unexpected results in case of history merges or other
201          * unusual time issues.
202          *
203          * @param string $timestamp
204          * @return Revision or null
205          */
206         function getPreviousRevision( $timestamp ) {
207                 $dbr = wfGetDB( DB_SLAVE );
208
209                 // Check the previous deleted revision...
210                 $row = $dbr->selectRow( 'archive',
211                         'ar_timestamp',
212                         array( 'ar_namespace' => $this->title->getNamespace(),
213                                'ar_title' => $this->title->getDBkey(),
214                                'ar_timestamp < ' .
215                                                 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
216                         __METHOD__,
217                         array(
218                                 'ORDER BY' => 'ar_timestamp DESC',
219                                 'LIMIT' => 1 ) );
220                 $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
221
222                 $row = $dbr->selectRow( array( 'page', 'revision' ),
223                         array( 'rev_id', 'rev_timestamp' ),
224                         array(
225                                 'page_namespace' => $this->title->getNamespace(),
226                                 'page_title' => $this->title->getDBkey(),
227                                 'page_id = rev_page',
228                                 'rev_timestamp < ' .
229                                                 $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
230                         __METHOD__,
231                         array(
232                                 'ORDER BY' => 'rev_timestamp DESC',
233                                 'LIMIT' => 1 ) );
234                 $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
235                 $prevLiveId = $row ? intval( $row->rev_id ) : null;
236
237                 if( $prevLive && $prevLive > $prevDeleted ) {
238                         // Most prior revision was live
239                         return Revision::newFromId( $prevLiveId );
240                 } elseif( $prevDeleted ) {
241                         // Most prior revision was deleted
242                         return $this->getRevision( $prevDeleted );
243                 } else {
244                         // No prior revision on this page.
245                         return null;
246                 }
247         }
248
249         /**
250          * Get the text from an archive row containing ar_text, ar_flags and ar_text_id
251          */
252         function getTextFromRow( $row ) {
253                 if( is_null( $row->ar_text_id ) ) {
254                         // An old row from MediaWiki 1.4 or previous.
255                         // Text is embedded in this row in classic compression format.
256                         return Revision::getRevisionText( $row, "ar_" );
257                 } else {
258                         // New-style: keyed to the text storage backend.
259                         $dbr = wfGetDB( DB_SLAVE );
260                         $text = $dbr->selectRow( 'text',
261                                 array( 'old_text', 'old_flags' ),
262                                 array( 'old_id' => $row->ar_text_id ),
263                                 __METHOD__ );
264                         return Revision::getRevisionText( $text );
265                 }
266         }
267
268
269         /**
270          * Fetch (and decompress if necessary) the stored text of the most
271          * recently edited deleted revision of the page.
272          *
273          * If there are no archived revisions for the page, returns NULL.
274          *
275          * @return string
276          */
277         function getLastRevisionText() {
278                 $dbr = wfGetDB( DB_SLAVE );
279                 $row = $dbr->selectRow( 'archive',
280                         array( 'ar_text', 'ar_flags', 'ar_text_id' ),
281                         array( 'ar_namespace' => $this->title->getNamespace(),
282                                'ar_title' => $this->title->getDBkey() ),
283                         'PageArchive::getLastRevisionText',
284                         array( 'ORDER BY' => 'ar_timestamp DESC' ) );
285                 if( $row ) {
286                         return $this->getTextFromRow( $row );
287                 } else {
288                         return null;
289                 }
290         }
291
292         /**
293          * Quick check if any archived revisions are present for the page.
294          * @return bool
295          */
296         function isDeleted() {
297                 $dbr = wfGetDB( DB_SLAVE );
298                 $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
299                         array( 'ar_namespace' => $this->title->getNamespace(),
300                                'ar_title' => $this->title->getDBkey() ) );
301                 return ($n > 0);
302         }
303
304         /**
305          * Restore the given (or all) text and file revisions for the page.
306          * Once restored, the items will be removed from the archive tables.
307          * The deletion log will be updated with an undeletion notice.
308          *
309          * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
310          * @param string $comment
311          * @param array $fileVersions
312          * @param bool $unsuppress
313          *
314          * @return array(number of file revisions restored, number of image revisions restored, log message)
315          * on success, false on failure
316          */
317         function undelete( $timestamps, $comment = '', $fileVersions = array(), $unsuppress = false ) {
318                 // If both the set of text revisions and file revisions are empty,
319                 // restore everything. Otherwise, just restore the requested items.
320                 $restoreAll = empty( $timestamps ) && empty( $fileVersions );
321
322                 $restoreText = $restoreAll || !empty( $timestamps );
323                 $restoreFiles = $restoreAll || !empty( $fileVersions );
324
325                 if( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
326                         $img = wfLocalFile( $this->title );
327                         $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
328                         $filesRestored = $this->fileStatus->successCount;
329                 } else {
330                         $filesRestored = 0;
331                 }
332
333                 if( $restoreText ) {
334                         $textRestored = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
335                         if($textRestored === false) // It must be one of UNDELETE_*
336                                 return false;
337                 } else {
338                         $textRestored = 0;
339                 }
340
341                 // Touch the log!
342                 global $wgContLang;
343                 $log = new LogPage( 'delete' );
344
345                 if( $textRestored && $filesRestored ) {
346                         $reason = wfMsgExt( 'undeletedrevisions-files', array( 'content', 'parsemag' ),
347                                 $wgContLang->formatNum( $textRestored ),
348                                 $wgContLang->formatNum( $filesRestored ) );
349                 } elseif( $textRestored ) {
350                         $reason = wfMsgExt( 'undeletedrevisions', array( 'content', 'parsemag' ),
351                                 $wgContLang->formatNum( $textRestored ) );
352                 } elseif( $filesRestored ) {
353                         $reason = wfMsgExt( 'undeletedfiles', array( 'content', 'parsemag' ),
354                                 $wgContLang->formatNum( $filesRestored ) );
355                 } else {
356                         wfDebug( "Undelete: nothing undeleted...\n" );
357                         return false;
358                 }
359
360                 if( trim( $comment ) != '' )
361                         $reason .= wfMsgForContent( 'colon-separator' ) . $comment;
362                 $log->addEntry( 'restore', $this->title, $reason );
363
364                 return array($textRestored, $filesRestored, $reason);
365         }
366
367         /**
368          * This is the meaty bit -- restores archived revisions of the given page
369          * to the cur/old tables. If the page currently exists, all revisions will
370          * be stuffed into old, otherwise the most recent will go into cur.
371          *
372          * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
373          * @param string $comment
374          * @param array $fileVersions
375          * @param bool $unsuppress, remove all ar_deleted/fa_deleted restrictions of seletected revs
376          *
377          * @return mixed number of revisions restored or false on failure
378          */
379         private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
380                 if ( wfReadOnly() )
381                         return false;
382                 $restoreAll = empty( $timestamps );
383
384                 $dbw = wfGetDB( DB_MASTER );
385
386                 # Does this page already exist? We'll have to update it...
387                 $article = new Article( $this->title );
388                 $options = 'FOR UPDATE'; // lock page
389                 $page = $dbw->selectRow( 'page',
390                         array( 'page_id', 'page_latest' ),
391                         array( 'page_namespace' => $this->title->getNamespace(),
392                                'page_title'     => $this->title->getDBkey() ),
393                         __METHOD__,
394                         $options
395                 );
396                 if( $page ) {
397                         $makepage = false;
398                         # Page already exists. Import the history, and if necessary
399                         # we'll update the latest revision field in the record.
400                         $newid             = 0;
401                         $pageId            = $page->page_id;
402                         $previousRevId    = $page->page_latest;
403                         # Get the time span of this page
404                         $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
405                                 array( 'rev_id' => $previousRevId ),
406                                 __METHOD__ );
407                         if( $previousTimestamp === false ) {
408                                 wfDebug( __METHOD__.": existing page refers to a page_latest that does not exist\n" );
409                                 return 0;
410                         }
411                 } else {
412                         # Have to create a new article...
413                         $makepage = true;
414                         $previousRevId = 0;
415                         $previousTimestamp = 0;
416                 }
417
418                 if( $restoreAll ) {
419                         $oldones = '1 = 1'; # All revisions...
420                 } else {
421                         $oldts = implode( ',',
422                                 array_map( array( &$dbw, 'addQuotes' ),
423                                         array_map( array( &$dbw, 'timestamp' ),
424                                                 $timestamps ) ) );
425
426                         $oldones = "ar_timestamp IN ( {$oldts} )";
427                 }
428
429                 /**
430                  * Select each archived revision...
431                  */
432                 $result = $dbw->select( 'archive',
433                         /* fields */ array(
434                                 'ar_rev_id',
435                                 'ar_text',
436                                 'ar_comment',
437                                 'ar_user',
438                                 'ar_user_text',
439                                 'ar_timestamp',
440                                 'ar_minor_edit',
441                                 'ar_flags',
442                                 'ar_text_id',
443                                 'ar_deleted',
444                                 'ar_page_id',
445                                 'ar_len' ),
446                         /* WHERE */ array(
447                                 'ar_namespace' => $this->title->getNamespace(),
448                                 'ar_title'     => $this->title->getDBkey(),
449                                 $oldones ),
450                         __METHOD__,
451                         /* options */ array( 'ORDER BY' => 'ar_timestamp' )
452                 );
453                 $ret = $dbw->resultObject( $result );
454                 $rev_count = $dbw->numRows( $result );
455                 if( !$rev_count ) {
456                         wfDebug( __METHOD__.": no revisions to restore\n" );
457                         return false; // ???
458                 }
459
460                 $ret->seek( $rev_count - 1 ); // move to last
461                 $row = $ret->fetchObject(); // get newest archived rev
462                 $ret->seek( 0 ); // move back
463
464                 if( $makepage ) {
465                         // Check the state of the newest to-be version...
466                         if( !$unsuppress && ($row->ar_deleted & Revision::DELETED_TEXT) ) {
467                                 return false; // we can't leave the current revision like this!
468                         }
469                         // Safe to insert now...
470                         $newid  = $article->insertOn( $dbw );
471                         $pageId = $newid;
472                 } else {
473                         // Check if a deleted revision will become the current revision...
474                         if( $row->ar_timestamp > $previousTimestamp ) {
475                                 // Check the state of the newest to-be version...
476                                 if( !$unsuppress && ($row->ar_deleted & Revision::DELETED_TEXT) ) {
477                                         return false; // we can't leave the current revision like this!
478                                 }
479                         }
480                 }
481
482                 $revision = null;
483                 $restored = 0;
484
485                 while( $row = $ret->fetchObject() ) {
486                         // Check for key dupes due to shitty archive integrity.
487                         if( $row->ar_rev_id ) {
488                                 $exists = $dbw->selectField( 'revision', '1', array('rev_id' => $row->ar_rev_id), __METHOD__ );
489                                 if( $exists ) continue; // don't throw DB errors
490                         }
491                         // Insert one revision at a time...maintaining deletion status
492                         // unless we are specifically removing all restrictions...
493                         $revision = Revision::newFromArchiveRow( $row, 
494                                 array( 
495                                         'page' => $pageId, 
496                                         'deleted' => $unsuppress ? 0 : $row->ar_deleted
497                                 ) );
498                         
499                         $revision->insertOn( $dbw );
500                         $restored++;
501
502                         wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) );
503                 }
504                 # Now that it's safely stored, take it out of the archive
505                 $dbw->delete( 'archive',
506                         /* WHERE */ array(
507                                 'ar_namespace' => $this->title->getNamespace(),
508                                 'ar_title' => $this->title->getDBkey(),
509                                 $oldones ),
510                         __METHOD__ );
511                 
512                 // Was anything restored at all?
513                 if( $restored == 0 )
514                         return 0;
515
516                 if( $revision ) {
517                         // Attach the latest revision to the page...
518                         $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
519                         if( $newid || $wasnew ) {
520                                 // Update site stats, link tables, etc
521                                 $article->createUpdates( $revision );
522                         }
523
524                         if( $newid ) {
525                                 wfRunHooks( 'ArticleUndelete', array( &$this->title, true, $comment ) );
526                                 Article::onArticleCreate( $this->title );
527                         } else {
528                                 wfRunHooks( 'ArticleUndelete', array( &$this->title, false, $comment ) );
529                                 Article::onArticleEdit( $this->title );
530                         }
531
532                         if( $this->title->getNamespace() == NS_FILE ) {
533                                 $update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
534                                 $update->doUpdate();
535                         }
536                 } else {
537                         // Revision couldn't be created. This is very weird
538                         return self::UNDELETE_UNKNOWNERR;
539                 }
540
541                 return $restored;
542         }
543
544         function getFileStatus() { return $this->fileStatus; }
545 }
546
547 /**
548  * The HTML form for Special:Undelete, which allows users with the appropriate
549  * permissions to view and restore deleted content.
550  * @ingroup SpecialPage
551  */
552 class UndeleteForm {
553         var $mAction, $mTarget, $mTimestamp, $mRestore, $mInvert, $mTargetObj;
554         var $mTargetTimestamp, $mAllowed, $mCanView, $mComment, $mToken;
555
556         function UndeleteForm( $request, $par = "" ) {
557                 global $wgUser;
558                 $this->mAction = $request->getVal( 'action' );
559                 $this->mTarget = $request->getVal( 'target' );
560                 $this->mSearchPrefix = $request->getText( 'prefix' );
561                 $time = $request->getVal( 'timestamp' );
562                 $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
563                 $this->mFile = $request->getVal( 'file' );
564
565                 $posted = $request->wasPosted() &&
566                         $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
567                 $this->mRestore = $request->getCheck( 'restore' ) && $posted;
568                 $this->mInvert = $request->getCheck( 'invert' ) && $posted;
569                 $this->mPreview = $request->getCheck( 'preview' ) && $posted;
570                 $this->mDiff = $request->getCheck( 'diff' );
571                 $this->mComment = $request->getText( 'wpComment' );
572                 $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $wgUser->isAllowed( 'suppressrevision' );
573                 $this->mToken = $request->getVal( 'token' );
574
575                 if( $par != "" ) {
576                         $this->mTarget = $par;
577                 }
578                 if ( $wgUser->isAllowed( 'undelete' ) && !$wgUser->isBlocked() ) {
579                         $this->mAllowed = true; // user can restore
580                         $this->mCanView = true; // user can view content
581                 } elseif ( $wgUser->isAllowed( 'deletedtext' ) ) {
582                         $this->mAllowed = false; // user cannot restore
583                         $this->mCanView = true; // user can view content
584                 }  else { // user can only view the list of revisions
585                         $this->mAllowed = false;
586                         $this->mCanView = false;
587                         $this->mTimestamp = '';
588                         $this->mRestore = false;
589                 }
590                 if ( $this->mTarget !== "" ) {
591                         $this->mTargetObj = Title::newFromURL( $this->mTarget );
592                 } else {
593                         $this->mTargetObj = null;
594                 }
595                 if( $this->mRestore || $this->mInvert ) {
596                         $timestamps = array();
597                         $this->mFileVersions = array();
598                         foreach( $_REQUEST as $key => $val ) {
599                                 $matches = array();
600                                 if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
601                                         array_push( $timestamps, $matches[1] );
602                                 }
603
604                                 if( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
605                                         $this->mFileVersions[] = intval( $matches[1] );
606                                 }
607                         }
608                         rsort( $timestamps );
609                         $this->mTargetTimestamp = $timestamps;
610                 }
611         }
612
613         function execute() {
614                 global $wgOut, $wgUser;
615                 if ( $this->mAllowed ) {
616                         $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
617                 } else {
618                         $wgOut->setPagetitle( wfMsg( "viewdeletedpage" ) );
619                 }
620
621                 if( is_null( $this->mTargetObj ) ) {
622                 # Not all users can just browse every deleted page from the list
623                         if( $wgUser->isAllowed( 'browsearchive' ) ) {
624                                 $this->showSearchForm();
625
626                                 # List undeletable articles
627                                 if( $this->mSearchPrefix ) {
628                                         $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
629                                         $this->showList( $result );
630                                 }
631                         } else {
632                                 $wgOut->addWikiMsg( 'undelete-header' );
633                         }
634                         return;
635                 }
636                 if( $this->mTimestamp !== '' ) {
637                         return $this->showRevision( $this->mTimestamp );
638                 }
639                 if( $this->mFile !== null ) {
640                         $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
641                         // Check if user is allowed to see this file
642                         if ( !$file->exists() ) {
643                                 $wgOut->addWikiMsg( 'filedelete-nofile', $this->mFile );
644                                 return;
645                         } else if( !$file->userCan( File::DELETED_FILE ) ) {
646                                 if( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
647                                         $wgOut->permissionRequired( 'suppressrevision' );
648                                 } else {
649                                         $wgOut->permissionRequired( 'deletedtext' );
650                                 }
651                                 return false;
652                         } elseif ( !$wgUser->matchEditToken( $this->mToken, $this->mFile ) ) {
653                                 $this->showFileConfirmationForm( $this->mFile );
654                                 return false;
655                         } else {
656                                 return $this->showFile( $this->mFile );
657                         }
658                 }
659                 if( $this->mRestore && $this->mAction == "submit" ) {
660                         global $wgUploadMaintenance;
661                         if( $wgUploadMaintenance && $this->mTargetObj && $this->mTargetObj->getNamespace() == NS_FILE ) {
662                                 $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n", array( 'filedelete-maintenance' ) );
663                                 return;
664                         }
665                         return $this->undelete();
666                 }
667                 if( $this->mInvert && $this->mAction == "submit" ) {
668                         return $this->showHistory( );
669                 }
670                 return $this->showHistory();
671         }
672
673         function showSearchForm() {
674                 global $wgOut, $wgScript;
675                 $wgOut->addWikiMsg( 'undelete-header' );
676
677                 $wgOut->addHTML(
678                         Xml::openElement( 'form', array(
679                                 'method' => 'get',
680                                 'action' => $wgScript ) ) .
681                         Xml::fieldset( wfMsg( 'undelete-search-box' ) ) .
682                         Xml::hidden( 'title',
683                                 SpecialPage::getTitleFor( 'Undelete' )->getPrefixedDbKey() ) .
684                         Xml::inputLabel( wfMsg( 'undelete-search-prefix' ),
685                                 'prefix', 'prefix', 20,
686                                 $this->mSearchPrefix ) . ' ' .
687                         Xml::submitButton( wfMsg( 'undelete-search-submit' ) ) .
688                         Xml::closeElement( 'fieldset' ) .
689                         Xml::closeElement( 'form' )
690                 );
691         }
692
693         // Generic list of deleted pages
694         private function showList( $result ) {
695                 global $wgLang, $wgContLang, $wgUser, $wgOut;
696
697                 if( $result->numRows() == 0 ) {
698                         $wgOut->addWikiMsg( 'undelete-no-results' );
699                         return;
700                 }
701
702                 $wgOut->addWikiMsg( 'undeletepagetext', $wgLang->formatNum( $result->numRows() ) );
703
704                 $sk = $wgUser->getSkin();
705                 $undelete = SpecialPage::getTitleFor( 'Undelete' );
706                 $wgOut->addHTML( "<ul>\n" );
707                 while( $row = $result->fetchObject() ) {
708                         $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
709                         $link = $sk->linkKnown(
710                                 $undelete,
711                                 htmlspecialchars( $title->getPrefixedText() ),
712                                 array(),
713                                 array( 'target' => $title->getPrefixedText() )
714                         );
715                         $revs = wfMsgExt( 'undeleterevisions',
716                                 array( 'parseinline' ),
717                                 $wgLang->formatNum( $row->count ) );
718                         $wgOut->addHTML( "<li>{$link} ({$revs})</li>\n" );
719                 }
720                 $result->free();
721                 $wgOut->addHTML( "</ul>\n" );
722
723                 return true;
724         }
725
726         private function showRevision( $timestamp ) {
727                 global $wgLang, $wgUser, $wgOut;
728                 $self = SpecialPage::getTitleFor( 'Undelete' );
729                 $skin = $wgUser->getSkin();
730
731                 if(!preg_match("/[0-9]{14}/",$timestamp)) return 0;
732
733                 $archive = new PageArchive( $this->mTargetObj );
734                 $rev = $archive->getRevision( $timestamp );
735
736                 if( !$rev ) {
737                         $wgOut->addWikiMsg( 'undeleterevision-missing' );
738                         return;
739                 }
740
741                 if( $rev->isDeleted(Revision::DELETED_TEXT) ) {
742                         if( !$rev->userCan(Revision::DELETED_TEXT) ) {
743                                 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-permission' );
744                                 return;
745                         } else {
746                                 $wgOut->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1</div>\n", 'rev-deleted-text-view' );
747                                 $wgOut->addHTML( '<br />' );
748                                 // and we are allowed to see...
749                         }
750                 }
751
752                 $wgOut->setPageTitle( wfMsg( 'undeletepage' ) );
753
754                 $link = $skin->linkKnown(
755                         SpecialPage::getTitleFor( 'Undelete', $this->mTargetObj->getPrefixedDBkey() ),
756                         htmlspecialchars( $this->mTargetObj->getPrefixedText() )
757                 );
758
759                 if( $this->mDiff ) {
760                         $previousRev = $archive->getPreviousRevision( $timestamp );
761                         if( $previousRev ) {
762                                 $this->showDiff( $previousRev, $rev );
763                                 if( $wgUser->getOption( 'diffonly' ) ) {
764                                         return;
765                                 } else {
766                                         $wgOut->addHTML( '<hr />' );
767                                 }
768                         } else {
769                                 $wgOut->addWikiMsg( 'undelete-nodiff' );
770                         }
771                 }
772
773                 // date and time are separate parameters to facilitate localisation.
774                 // $time is kept for backward compat reasons.
775                 $time = htmlspecialchars( $wgLang->timeAndDate( $timestamp, true ) );
776                 $d = htmlspecialchars( $wgLang->date( $timestamp, true ) );
777                 $t = htmlspecialchars( $wgLang->time( $timestamp, true ) );
778                 $user = $skin->revUserTools( $rev );
779
780                 if( $this->mPreview ) {
781                         $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
782                 } else {
783                         $openDiv = '<div id="mw-undelete-revision">';
784                 }
785
786                 // Revision delete links
787                 $canHide = $wgUser->isAllowed( 'deleterevision' );
788                 if( $this->mDiff ) {
789                         $revdlink = ''; // diffs already have revision delete links
790                 } else if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
791                         if( !$rev->userCan(Revision::DELETED_RESTRICTED ) ) {
792                                 $revdlink = $skin->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
793                         } else {
794                                 $query = array(
795                                         'type'   => 'archive',
796                                         'target' => $this->mTargetObj->getPrefixedDBkey(),
797                                         'ids'    => $rev->getTimestamp()
798                                 );
799                                 $revdlink = $skin->revDeleteLink( $query,
800                                         $rev->isDeleted( File::DELETED_RESTRICTED ), $canHide );
801                         }
802                 } else {
803                         $revdlink = '';
804                 }
805
806                 $wgOut->addHTML( $openDiv . $revdlink . wfMsgWikiHtml( 'undelete-revision', $link, $time, $user, $d, $t ) . '</div>' );
807                 wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) );
808
809                 if( $this->mPreview ) {
810                         //Hide [edit]s
811                         $popts = $wgOut->parserOptions();
812                         $popts->setEditSection( false );
813                         $wgOut->parserOptions( $popts );
814                         $wgOut->addWikiTextTitleTidy( $rev->getText( Revision::FOR_THIS_USER ), $this->mTargetObj, true );
815                 }
816
817                 $wgOut->addHTML(
818                         Xml::element( 'textarea', array(
819                                         'readonly' => 'readonly',
820                                         'cols' => intval( $wgUser->getOption( 'cols' ) ),
821                                         'rows' => intval( $wgUser->getOption( 'rows' ) ) ),
822                                 $rev->getText( Revision::FOR_THIS_USER ) . "\n" ) .
823                         Xml::openElement( 'div' ) .
824                         Xml::openElement( 'form', array(
825                                 'method' => 'post',
826                                 'action' => $self->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
827                         Xml::element( 'input', array(
828                                 'type' => 'hidden',
829                                 'name' => 'target',
830                                 'value' => $this->mTargetObj->getPrefixedDbKey() ) ) .
831                         Xml::element( 'input', array(
832                                 'type' => 'hidden',
833                                 'name' => 'timestamp',
834                                 'value' => $timestamp ) ) .
835                         Xml::element( 'input', array(
836                                 'type' => 'hidden',
837                                 'name' => 'wpEditToken',
838                                 'value' => $wgUser->editToken() ) ) .
839                         Xml::element( 'input', array(
840                                 'type' => 'submit',
841                                 'name' => 'preview',
842                                 'value' => wfMsg( 'showpreview' ) ) ) .
843                         Xml::element( 'input', array(
844                                 'name' => 'diff',
845                                 'type' => 'submit',
846                                 'value' => wfMsg( 'showdiff' ) ) ) .
847                         Xml::closeElement( 'form' ) .
848                         Xml::closeElement( 'div' ) );
849         }
850
851         /**
852          * Build a diff display between this and the previous either deleted
853          * or non-deleted edit.
854          * @param Revision $previousRev
855          * @param Revision $currentRev
856          * @return string HTML
857          */
858         function showDiff( $previousRev, $currentRev ) {
859                 global $wgOut;
860
861                 $diffEngine = new DifferenceEngine();
862                 $diffEngine->showDiffStyle();
863                 $wgOut->addHTML(
864                         "<div>" .
865                         "<table border='0' width='98%' cellpadding='0' cellspacing='4' class='diff'>" .
866                         "<col class='diff-marker' />" .
867                         "<col class='diff-content' />" .
868                         "<col class='diff-marker' />" .
869                         "<col class='diff-content' />" .
870                         "<tr>" .
871                                 "<td colspan='2' width='50%' align='center' class='diff-otitle'>" .
872                                 $this->diffHeader( $previousRev, 'o' ) .
873                                 "</td>\n" .
874                                 "<td colspan='2' width='50%' align='center' class='diff-ntitle'>" .
875                                 $this->diffHeader( $currentRev, 'n' ) .
876                                 "</td>\n" .
877                         "</tr>" .
878                         $diffEngine->generateDiffBody(
879                                 $previousRev->getText(), $currentRev->getText() ) .
880                         "</table>" .
881                         "</div>\n"
882                 );
883         }
884
885         private function diffHeader( $rev, $prefix ) {
886                 global $wgUser, $wgLang;
887                 $sk = $wgUser->getSkin();
888                 $isDeleted = !( $rev->getId() && $rev->getTitle() );
889                 if( $isDeleted ) {
890                         /// @todo Fixme: $rev->getTitle() is null for deleted revs...?
891                         $targetPage = SpecialPage::getTitleFor( 'Undelete' );
892                         $targetQuery = array(
893                                 'target' => $this->mTargetObj->getPrefixedText(),
894                                 'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
895                         );
896                 } else {
897                         /// @todo Fixme getId() may return non-zero for deleted revs...
898                         $targetPage = $rev->getTitle();
899                         $targetQuery = array( 'oldid' => $rev->getId() );
900                 }
901                 // Add show/hide deletion links if available
902                 $canHide = $wgUser->isAllowed( 'deleterevision' );
903                 if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
904                         $del = ' ';
905                         if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
906                                 $del .= $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
907                         } else {
908                                 $query = array( 
909                                         'type'   => 'archive',
910                                         'target' => $this->mTargetObj->getPrefixedDbkey(),
911                                         'ids'    => $rev->getTimestamp()
912                                 );
913                                 $del .= $sk->revDeleteLink( $query,
914                                         $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
915                         }
916                 } else {
917                         $del = '';
918                 }
919                 return
920                         '<div id="mw-diff-'.$prefix.'title1"><strong>' .
921                                 $sk->link(
922                                         $targetPage,
923                                         wfMsgHtml(
924                                                 'revisionasof',
925                                                 htmlspecialchars( $wgLang->timeanddate( $rev->getTimestamp(), true ) ),
926                                                 htmlspecialchars( $wgLang->date( $rev->getTimestamp(), true ) ),
927                                                 htmlspecialchars( $wgLang->time( $rev->getTimestamp(), true ) )
928                                         ),
929                                         array(),
930                                         $targetQuery
931                                 ) .
932                         '</strong></div>' .
933                         '<div id="mw-diff-'.$prefix.'title2">' .
934                                 $sk->revUserTools( $rev ) . '<br />' .
935                         '</div>' .
936                         '<div id="mw-diff-'.$prefix.'title3">' .
937                                 $sk->revComment( $rev ) . $del . '<br />' .
938                         '</div>';
939         }
940
941         /**
942          * Show a form confirming whether a tokenless user really wants to see a file
943          */
944         private function showFileConfirmationForm( $key ) {
945                 global $wgOut, $wgUser, $wgLang;
946                 $file = new ArchivedFile( $this->mTargetObj, '', $this->mFile );
947                 $wgOut->addWikiMsg( 'undelete-show-file-confirm',
948                         $this->mTargetObj->getText(),
949                         $wgLang->date( $file->getTimestamp() ),
950                         $wgLang->time( $file->getTimestamp() ) );
951                 $wgOut->addHTML( 
952                         Xml::openElement( 'form', array( 
953                                 'method' => 'POST',
954                                 'action' => SpecialPage::getTitleFor( 'Undelete' )->getLocalUrl(
955                                         'target=' . urlencode( $this->mTarget ) .
956                                         '&file=' . urlencode( $key ) .
957                                         '&token=' . urlencode( $wgUser->editToken( $key ) ) )
958                                 )
959                         ) .
960                         Xml::submitButton( wfMsg( 'undelete-show-file-submit' ) ) .
961                         '</form>'
962                 );
963         }
964
965         /**
966          * Show a deleted file version requested by the visitor.
967          */
968         private function showFile( $key ) {
969                 global $wgOut, $wgRequest;
970                 $wgOut->disable();
971
972                 # We mustn't allow the output to be Squid cached, otherwise
973                 # if an admin previews a deleted image, and it's cached, then
974                 # a user without appropriate permissions can toddle off and
975                 # nab the image, and Squid will serve it
976                 $wgRequest->response()->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
977                 $wgRequest->response()->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
978                 $wgRequest->response()->header( 'Pragma: no-cache' );
979
980                 global $IP;
981                 require_once( "$IP/includes/StreamFile.php" );
982                 $repo = RepoGroup::singleton()->getLocalRepo();         
983                 $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
984                 wfStreamFile( $path );
985         }
986
987         private function showHistory( ) {
988                 global $wgLang, $wgUser, $wgOut;
989
990                 $sk = $wgUser->getSkin();
991                 if( $this->mAllowed ) {
992                         $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
993                 } else {
994                         $wgOut->setPagetitle( wfMsg( 'viewdeletedpage' ) );
995                 }
996
997                 $wgOut->wrapWikiMsg(  "<div class='mw-undelete-pagetitle'>\n$1</div>\n", array ( 'undeletepagetitle', $this->mTargetObj->getPrefixedText() ) );
998
999                 $archive = new PageArchive( $this->mTargetObj );
1000                 /*
1001                 $text = $archive->getLastRevisionText();
1002                 if( is_null( $text ) ) {
1003                         $wgOut->addWikiMsg( "nohistory" );
1004                         return;
1005                 }
1006                 */
1007                 $wgOut->addHTML( '<div class="mw-undelete-history">' );
1008                 if ( $this->mAllowed ) {
1009                         $wgOut->addWikiMsg( "undeletehistory" );
1010                         $wgOut->addWikiMsg( "undeleterevdel" );
1011                 } else {
1012                         $wgOut->addWikiMsg( "undeletehistorynoadmin" );
1013                 }
1014                 $wgOut->addHTML( '</div>' );
1015
1016                 # List all stored revisions
1017                 $revisions = $archive->listRevisions();
1018                 $files = $archive->listFiles();
1019
1020                 $haveRevisions = $revisions && $revisions->numRows() > 0;
1021                 $haveFiles = $files && $files->numRows() > 0;
1022
1023                 # Batch existence check on user and talk pages
1024                 if( $haveRevisions ) {
1025                         $batch = new LinkBatch();
1026                         while( $row = $revisions->fetchObject() ) {
1027                                 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
1028                                 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
1029                         }
1030                         $batch->execute();
1031                         $revisions->seek( 0 );
1032                 }
1033                 if( $haveFiles ) {
1034                         $batch = new LinkBatch();
1035                         while( $row = $files->fetchObject() ) {
1036                                 $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
1037                                 $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) );
1038                         }
1039                         $batch->execute();
1040                         $files->seek( 0 );
1041                 }
1042
1043                 if ( $this->mAllowed ) {
1044                         $titleObj = SpecialPage::getTitleFor( "Undelete" );
1045                         $action = $titleObj->getLocalURL( array( 'action' => 'submit' ) );
1046                         # Start the form here
1047                         $top = Xml::openElement( 'form', array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' ) );
1048                         $wgOut->addHTML( $top );
1049                 }
1050
1051                 # Show relevant lines from the deletion log:
1052                 $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'delete' ) ) . "\n" );
1053                 LogEventsList::showLogExtract( $wgOut, 'delete', $this->mTargetObj->getPrefixedText() );
1054                 # Show relevant lines from the suppression log:
1055                 if( $wgUser->isAllowed( 'suppressionlog' ) ) {
1056                         $wgOut->addHTML( Xml::element( 'h2', null, LogPage::logName( 'suppress' ) ) . "\n" );
1057                         LogEventsList::showLogExtract( $wgOut, 'suppress', $this->mTargetObj->getPrefixedText() );
1058                 }
1059
1060                 if( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
1061                         # Format the user-visible controls (comment field, submission button)
1062                         # in a nice little table
1063                         if( $wgUser->isAllowed( 'suppressrevision' ) ) {
1064                                 $unsuppressBox =
1065                                         "<tr>
1066                                                 <td>&nbsp;</td>
1067                                                 <td class='mw-input'>" .
1068                                                         Xml::checkLabel( wfMsg('revdelete-unsuppress'), 'wpUnsuppress',
1069                                                                 'mw-undelete-unsuppress', $this->mUnsuppress ).
1070                                                 "</td>
1071                                         </tr>";
1072                         } else {
1073                                 $unsuppressBox = "";
1074                         }
1075                         $table =
1076                                 Xml::fieldset( wfMsg( 'undelete-fieldset-title' ) ) .
1077                                 Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
1078                                         "<tr>
1079                                                 <td colspan='2' class='mw-undelete-extrahelp'>" .
1080                                                         wfMsgWikiHtml( 'undeleteextrahelp' ) .
1081                                                 "</td>
1082                                         </tr>
1083                                         <tr>
1084                                                 <td class='mw-label'>" .
1085                                                         Xml::label( wfMsg( 'undeletecomment' ), 'wpComment' ) .
1086                                                 "</td>
1087                                                 <td class='mw-input'>" .
1088                                                         Xml::input( 'wpComment', 50, $this->mComment, array( 'id' =>  'wpComment' ) ) .
1089                                                 "</td>
1090                                         </tr>
1091                                         <tr>
1092                                                 <td>&nbsp;</td>
1093                                                 <td class='mw-submit'>" .
1094                                                         Xml::submitButton( wfMsg( 'undeletebtn' ), array( 'name' => 'restore', 'id' => 'mw-undelete-submit' ) ) . ' ' .
1095                                                         Xml::element( 'input', array( 'type' => 'reset', 'value' => wfMsg( 'undeletereset' ), 'id' => 'mw-undelete-reset' ) ) . ' ' .
1096                                                         Xml::submitButton( wfMsg( 'undeleteinvert' ), array( 'name' => 'invert', 'id' => 'mw-undelete-invert' ) ) .
1097                                                 "</td>
1098                                         </tr>" .
1099                                         $unsuppressBox .
1100                                 Xml::closeElement( 'table' ) .
1101                                 Xml::closeElement( 'fieldset' );
1102
1103                         $wgOut->addHTML( $table );
1104                 }
1105
1106                 $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'history' ) ) . "\n" );
1107
1108                 if( $haveRevisions ) {
1109                         # The page's stored (deleted) history:
1110                         $wgOut->addHTML("<ul>");
1111                         $target = urlencode( $this->mTarget );
1112                         $remaining = $revisions->numRows();
1113                         $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
1114
1115                         while( $row = $revisions->fetchObject() ) {
1116                                 $remaining--;
1117                                 $wgOut->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) );
1118                         }
1119                         $revisions->free();
1120                         $wgOut->addHTML("</ul>");
1121                 } else {
1122                         $wgOut->addWikiMsg( "nohistory" );
1123                 }
1124
1125                 if( $haveFiles ) {
1126                         $wgOut->addHTML( Xml::element( 'h2', null, wfMsg( 'filehist' ) ) . "\n" );
1127                         $wgOut->addHTML( "<ul>" );
1128                         while( $row = $files->fetchObject() ) {
1129                                 $wgOut->addHTML( $this->formatFileRow( $row, $sk ) );
1130                         }
1131                         $files->free();
1132                         $wgOut->addHTML( "</ul>" );
1133                 }
1134
1135                 if ( $this->mAllowed ) {
1136                         # Slip in the hidden controls here
1137                         $misc  = Xml::hidden( 'target', $this->mTarget );
1138                         $misc .= Xml::hidden( 'wpEditToken', $wgUser->editToken() );
1139                         $misc .= Xml::closeElement( 'form' );
1140                         $wgOut->addHTML( $misc );
1141                 }
1142
1143                 return true;
1144         }
1145
1146         private function formatRevisionRow( $row, $earliestLiveTime, $remaining, $sk ) {
1147                 global $wgUser, $wgLang;
1148
1149                 $rev = Revision::newFromArchiveRow( $row, 
1150                         array( 'page' => $this->mTargetObj->getArticleId() ) );
1151                 $stxt = '';
1152                 $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
1153                 // Build checkboxen...
1154                 if( $this->mAllowed ) {
1155                         if( $this->mInvert ) {
1156                                 if( in_array( $ts, $this->mTargetTimestamp ) ) {
1157                                         $checkBox = Xml::check( "ts$ts");
1158                                 } else {
1159                                         $checkBox = Xml::check( "ts$ts", true );
1160                                 }
1161                         } else {
1162                                 $checkBox = Xml::check( "ts$ts" );
1163                         }
1164                 } else {
1165                         $checkBox = '';
1166                 }
1167                 // Build page & diff links...
1168                 if( $this->mCanView ) {
1169                         $titleObj = SpecialPage::getTitleFor( "Undelete" );
1170                         # Last link
1171                         if( !$rev->userCan( Revision::DELETED_TEXT ) ) {
1172                                 $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
1173                                 $last = wfMsgHtml('diff');
1174                         } else if( $remaining > 0 || ($earliestLiveTime && $ts > $earliestLiveTime) ) {
1175                                 $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk );
1176                                 $last = $sk->linkKnown(
1177                                         $titleObj,
1178                                         wfMsgHtml('diff'),
1179                                         array(),
1180                                         array(
1181                                                 'target' => $this->mTargetObj->getPrefixedText(),
1182                                                 'timestamp' => $ts,
1183                                                 'diff' => 'prev'
1184                                         )
1185                                 );
1186                         } else {
1187                                 $pageLink = $this->getPageLink( $rev, $titleObj, $ts, $sk );
1188                                 $last = wfMsgHtml('diff');
1189                         }
1190                 } else {
1191                         $pageLink = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
1192                         $last = wfMsgHtml('diff');
1193                 }
1194                 // User links
1195                 $userLink = $sk->revUserTools( $rev );
1196                 // Revision text size
1197                 if( !is_null($size = $row->ar_len) ) {
1198                         $stxt = $sk->formatRevisionSize( $size );
1199                 }
1200                 // Edit summary
1201                 $comment = $sk->revComment( $rev );
1202                 // Revision delete links
1203                 $canHide = $wgUser->isAllowed( 'deleterevision' );
1204                 if( $canHide || ($rev->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
1205                         if( !$rev->userCan( Revision::DELETED_RESTRICTED ) ) {
1206                                 $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
1207                         } else {
1208                                 $query = array(
1209                                         'type'   => 'archive',
1210                                         'target' => $this->mTargetObj->getPrefixedDBkey(),
1211                                         'ids'    => $ts
1212                                 );
1213                                 $revdlink = $sk->revDeleteLink( $query,
1214                                         $rev->isDeleted( Revision::DELETED_RESTRICTED ), $canHide );
1215                         }
1216                 } else {
1217                         $revdlink = '';
1218                 }
1219                 return "<li>$checkBox $revdlink ($last) $pageLink . . $userLink $stxt $comment</li>";
1220         }
1221
1222         private function formatFileRow( $row, $sk ) {
1223                 global $wgUser, $wgLang;
1224
1225                 $file = ArchivedFile::newFromRow( $row );
1226
1227                 $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1228                 if( $this->mAllowed && $row->fa_storage_key ) {
1229                         $checkBox = Xml::check( "fileid" . $row->fa_id );
1230                         $key = urlencode( $row->fa_storage_key );
1231                         $target = urlencode( $this->mTarget );
1232                         $titleObj = SpecialPage::getTitleFor( "Undelete" );
1233                         $pageLink = $this->getFileLink( $file, $titleObj, $ts, $key, $sk );
1234                 } else {
1235                         $checkBox = '';
1236                         $pageLink = $wgLang->timeanddate( $ts, true );
1237                 }
1238                 $userLink = $this->getFileUser( $file, $sk );
1239                 $data =
1240                         wfMsg( 'widthheight',
1241                                 $wgLang->formatNum( $row->fa_width ),
1242                                 $wgLang->formatNum( $row->fa_height ) ) .
1243                         ' (' .
1244                         wfMsg( 'nbytes', $wgLang->formatNum( $row->fa_size ) ) .
1245                         ')';
1246                 $data = htmlspecialchars( $data );
1247                 $comment = $this->getFileComment( $file, $sk );
1248                 // Add show/hide deletion links if available
1249                 $canHide = $wgUser->isAllowed( 'deleterevision' );
1250                 if( $canHide || ($file->getVisibility() && $wgUser->isAllowed('deletedhistory')) ) {
1251                         if( !$file->userCan(File::DELETED_RESTRICTED ) ) {
1252                                 $revdlink = $sk->revDeleteLinkDisabled( $canHide ); // revision was hidden from sysops
1253                         } else {
1254                                 $query = array(
1255                                         'type' => 'filearchive',
1256                                         'target' => $this->mTargetObj->getPrefixedDBkey(),
1257                                         'ids' => $row->fa_id
1258                                 );
1259                                 $revdlink = $sk->revDeleteLink( $query,
1260                                         $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1261                         }
1262                 } else {
1263                         $revdlink = '';
1264                 }
1265                 return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1266         }
1267
1268         /**
1269          * Fetch revision text link if it's available to all users
1270          * @return string
1271          */
1272         function getPageLink( $rev, $titleObj, $ts, $sk ) {
1273                 global $wgLang;
1274
1275                 $time = htmlspecialchars( $wgLang->timeanddate( $ts, true ) );
1276
1277                 if( !$rev->userCan(Revision::DELETED_TEXT) ) {
1278                         return '<span class="history-deleted">' . $time . '</span>';
1279                 } else {
1280                         $link = $sk->linkKnown(
1281                                 $titleObj,
1282                                 $time,
1283                                 array(),
1284                                 array(
1285                                         'target' => $this->mTargetObj->getPrefixedText(),
1286                                         'timestamp' => $ts
1287                                 )
1288                         );
1289                         if( $rev->isDeleted(Revision::DELETED_TEXT) )
1290                                 $link = '<span class="history-deleted">' . $link . '</span>';
1291                         return $link;
1292                 }
1293         }
1294
1295         /**
1296          * Fetch image view link if it's available to all users
1297          * @return string
1298          */
1299         function getFileLink( $file, $titleObj, $ts, $key, $sk ) {
1300                 global $wgLang, $wgUser;
1301
1302                 if( !$file->userCan(File::DELETED_FILE) ) {
1303                         return '<span class="history-deleted">' . $wgLang->timeanddate( $ts, true ) . '</span>';
1304                 } else {
1305                         $link = $sk->linkKnown(
1306                                 $titleObj,
1307                                 $wgLang->timeanddate( $ts, true ),
1308                                 array(),
1309                                 array(
1310                                         'target' => $this->mTargetObj->getPrefixedText(),
1311                                         'file' => $key,
1312                                         'token' => $wgUser->editToken( $key )
1313                                 )
1314                         );
1315                         if( $file->isDeleted(File::DELETED_FILE) )
1316                                 $link = '<span class="history-deleted">' . $link . '</span>';
1317                         return $link;
1318                 }
1319         }
1320
1321         /**
1322          * Fetch file's user id if it's available to this user
1323          * @return string
1324          */
1325         function getFileUser( $file, $sk ) {
1326                 if( !$file->userCan(File::DELETED_USER) ) {
1327                         return '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
1328                 } else {
1329                         $link = $sk->userLink( $file->getRawUser(), $file->getRawUserText() ) .
1330                                 $sk->userToolLinks( $file->getRawUser(), $file->getRawUserText() );
1331                         if( $file->isDeleted(File::DELETED_USER) )
1332                                 $link = '<span class="history-deleted">' . $link . '</span>';
1333                         return $link;
1334                 }
1335         }
1336
1337         /**
1338          * Fetch file upload comment if it's available to this user
1339          * @return string
1340          */
1341         function getFileComment( $file, $sk ) {
1342                 if( !$file->userCan(File::DELETED_COMMENT) ) {
1343                         return '<span class="history-deleted"><span class="comment">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></span>';
1344                 } else {
1345                         $link = $sk->commentBlock( $file->getRawDescription() );
1346                         if( $file->isDeleted(File::DELETED_COMMENT) )
1347                                 $link = '<span class="history-deleted">' . $link . '</span>';
1348                         return $link;
1349                 }
1350         }
1351
1352         function undelete() {
1353                 global $wgOut, $wgUser;
1354                 if ( wfReadOnly() ) {
1355                         $wgOut->readOnlyPage();
1356                         return;
1357                 }
1358                 if( !is_null( $this->mTargetObj ) ) {
1359                         $archive = new PageArchive( $this->mTargetObj );
1360                         $ok = $archive->undelete(
1361                                 $this->mTargetTimestamp,
1362                                 $this->mComment,
1363                                 $this->mFileVersions,
1364                                 $this->mUnsuppress );
1365
1366                         if( is_array($ok) ) {
1367                                 if ( $ok[1] ) // Undeleted file count
1368                                         wfRunHooks( 'FileUndeleteComplete', array(
1369                                                 $this->mTargetObj, $this->mFileVersions,
1370                                                 $wgUser, $this->mComment) );
1371
1372                                 $skin = $wgUser->getSkin();
1373                                 $link = $skin->linkKnown( $this->mTargetObj );
1374                                 $wgOut->addHTML( wfMsgWikiHtml( 'undeletedpage', $link ) );
1375                         } else {
1376                                 $wgOut->showFatalError( wfMsg( "cannotundelete" ) );
1377                                 $wgOut->addHTML( '<p>' . wfMsgHtml( "undeleterevdel" ) . '</p>' );
1378                         }
1379
1380                         // Show file deletion warnings and errors
1381                         $status = $archive->getFileStatus();
1382                         if( $status && !$status->isGood() ) {
1383                                 $wgOut->addWikiText( $status->getWikiText( 'undelete-error-short', 'undelete-error-long' ) );
1384                         }
1385                 } else {
1386                         $wgOut->showFatalError( wfMsg( "cannotundelete" ) );
1387                 }
1388                 return false;
1389         }
1390 }