]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/SpecialUndelete.php
MediaWiki 1.5.8 (initial commit)
[autoinstallsdev/mediawiki.git] / includes / SpecialUndelete.php
1 <?php
2 /**
3  * @todo document
4  * @package MediaWiki
5  * @subpackage SpecialPage
6  */
7
8 /** */
9 require_once( 'Revision.php' );
10
11 /**
12  *
13  */
14 function wfSpecialUndelete( $par ) {
15     global $wgRequest;
16
17         $form = new UndeleteForm( $wgRequest, $par );
18         $form->execute();
19 }
20
21 /**
22  *
23  * @package MediaWiki
24  * @subpackage SpecialPage
25  */
26 class PageArchive {
27         var $title;
28         
29         function PageArchive( &$title ) {
30                 if( is_null( $title ) ) {
31                         wfDebugDieBacktrace( '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. Can be called staticaly.
40          *
41          * @return ResultWrapper
42          */
43         /* static */ function listAllPages() {
44                 $dbr =& wfGetDB( DB_SLAVE );
45                 $archive = $dbr->tableName( 'archive' );
46
47                 $sql = "SELECT ar_namespace,ar_title, COUNT(*) AS count FROM $archive " . 
48                   "GROUP BY ar_namespace,ar_title ORDER BY ar_namespace,ar_title";
49
50                 return $dbr->resultObject( $dbr->query( $sql, 'PageArchive::listAllPages' ) );
51         }
52         
53         /**
54          * List the revisions of the given page. Returns result wrapper with
55          * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields.
56          *
57          * @return ResultWrapper
58          */
59         function listRevisions() {
60                 $dbr =& wfGetDB( DB_SLAVE );
61                 return $dbr->resultObject( $dbr->select( 'archive',
62                         array( 'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text', 'ar_comment' ),
63                         array( 'ar_namespace' => $this->title->getNamespace(),
64                                'ar_title' => $this->title->getDBkey() ),
65                         'PageArchive::listRevisions',
66                         array( 'ORDER BY' => 'ar_timestamp DESC' ) ) );
67         }
68         
69         /**
70          * Fetch (and decompress if necessary) the stored text for the deleted
71          * revision of the page with the given timestamp.
72          *
73          * @return string
74          */
75         function getRevisionText( $timestamp ) {
76                 $fname = 'PageArchive::getRevisionText';
77                 $dbr =& wfGetDB( DB_SLAVE );
78                 $row = $dbr->selectRow( 'archive',
79                         array( 'ar_text', 'ar_flags', 'ar_text_id' ),
80                         array( 'ar_namespace' => $this->title->getNamespace(),
81                                'ar_title' => $this->title->getDbkey(),
82                                'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
83                         $fname );
84                 if( is_null( $row->ar_text_id ) ) {
85                         // An old row from MediaWiki 1.4 or previous.
86                         // Text is embedded in this row in classic compression format.
87                         return Revision::getRevisionText( $row, "ar_" );
88                 } else {
89                         // New-style: keyed to the text storage backend.
90                         $text = $dbr->selectRow( 'text',
91                                 array( 'old_text', 'old_flags' ),
92                                 array( 'old_id' => $row->ar_text_id ),
93                                 $fname );
94                         return Revision::getRevisionText( $text );
95                 }
96         }
97         
98         /**
99          * Fetch (and decompress if necessary) the stored text of the most
100          * recently edited deleted revision of the page.
101          *
102          * If there are no archived revisions for the page, returns NULL.
103          *
104          * @return string
105          */
106         function getLastRevisionText() {
107                 $dbr =& wfGetDB( DB_SLAVE );
108                 $row = $dbr->selectRow( 'archive',
109                         array( 'ar_text', 'ar_flags' ),
110                         array( 'ar_namespace' => $this->title->getNamespace(),
111                                'ar_title' => $this->title->getDBkey() ),
112                         'PageArchive::getLastRevisionText',
113                         array( 'ORDER BY' => 'ar_timestamp DESC' ) );
114                 if( $row ) {
115                         return Revision::getRevisionText( $row, "ar_" );
116                 } else {
117                         return NULL;
118                 }
119         }
120         
121         /**
122          * Quick check if any archived revisions are present for the page.
123          * @return bool
124          */
125         function isDeleted() {
126                 $dbr =& wfGetDB( DB_SLAVE );
127                 $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
128                         array( 'ar_namespace' => $this->title->getNamespace(),
129                                'ar_title' => $this->title->getDBkey() ) );
130                 return ($n > 0);
131         }
132         
133         /**
134          * This is the meaty bit -- restores archived revisions of the given page
135          * to the cur/old tables. If the page currently exists, all revisions will
136          * be stuffed into old, otherwise the most recent will go into cur.
137          * The deletion log will be updated with an undeletion notice.
138          *
139          * Returns true on success.
140          *
141          * @param array $timestamps Pass an empty array to restore all revisions, otherwise list the ones to undelete.
142          * @return bool
143          */
144         function undelete( $timestamps ) {
145                 global $wgUser, $wgOut, $wgLang, $wgDeferredUpdateList;
146                 global $wgUseSquid, $wgInternalServer, $wgLinkCache;
147                 global $wgDBtype;
148
149                 $fname = "doUndeleteArticle";
150                 $restoreAll = empty( $timestamps );
151                 $restoreRevisions = count( $timestamps );
152
153                 $dbw =& wfGetDB( DB_MASTER );
154                 extract( $dbw->tableNames( 'page', 'archive' ) );
155
156                 # Does this page already exist? We'll have to update it...
157                 $article = new Article( $this->title );
158                 $options = ( $wgDBtype == 'PostgreSQL' )
159                         ? '' // pg doesn't support this?
160                         : 'FOR UPDATE';
161                 $page = $dbw->selectRow( 'page',
162                         array( 'page_id', 'page_latest' ),
163                         array( 'page_namespace' => $this->title->getNamespace(),
164                                'page_title'     => $this->title->getDBkey() ),
165                         $fname,
166                         $options );
167                 if( $page ) {
168                         # Page already exists. Import the history, and if necessary
169                         # we'll update the latest revision field in the record.
170                         $newid             = 0;
171                         $pageId            = $page->page_id;
172                         $previousRevId     = $page->page_latest;
173                         $previousTimestamp = $page->rev_timestamp;
174                 } else {
175                         # Have to create a new article...
176                         $newid  = $article->insertOn( $dbw );
177                         $pageId = $newid;
178                         $previousRevId = 0;
179                         $previousTimestamp = 0;
180                 }
181                 
182                 if( $restoreAll ) {
183                         $oldones = '1'; # All revisions...
184                 } else {
185                         $oldts = implode( ',',
186                                 array_map( array( &$dbw, 'addQuotes' ),
187                                         array_map( array( &$dbw, 'timestamp' ),
188                                                 $timestamps ) ) );
189                         
190                         $oldones = "ar_timestamp IN ( {$oldts} )";
191                 }
192                 
193                 /**
194                  * Restore each revision...
195                  */
196                 $result = $dbw->select( 'archive',
197                         /* fields */ array(
198                                 'ar_rev_id',
199                                 'ar_text',
200                                 'ar_comment',
201                                 'ar_user',
202                                 'ar_user_text',
203                                 'ar_timestamp',
204                                 'ar_minor_edit',
205                                 'ar_flags',
206                                 'ar_text_id' ),
207                         /* WHERE */ array(
208                                 'ar_namespace' => $this->title->getNamespace(),
209                                 'ar_title'     => $this->title->getDBkey(),
210                                 $oldones ),
211                         $fname,
212                         /* options */ array(
213                                 'ORDER BY' => 'ar_timestamp' )
214                         );
215                 $revision = null;
216                 while( $row = $dbw->fetchObject( $result ) ) {
217                         if( $row->ar_text_id ) {
218                                 // Revision was deleted in 1.5+; text is in
219                                 // the regular text table, use the reference.
220                                 // Specify null here so the so the text is
221                                 // dereferenced for page length info if needed.
222                                 $revText = null;
223                         } else {
224                                 // Revision was deleted in 1.4 or earlier.
225                                 // Text is squashed into the archive row, and
226                                 // a new text table entry will be created for it.
227                                 $revText = Revision::getRevisionText( $row, 'ar_' );
228                         }
229                         $revision = new Revision( array(
230                                 'page'       => $pageId,
231                                 'id'         => $row->ar_rev_id,
232                                 'text'       => $revText,
233                                 'comment'    => $row->ar_comment,
234                                 'user'       => $row->ar_user,
235                                 'user_text'  => $row->ar_user_text,
236                                 'timestamp'  => $row->ar_timestamp,
237                                 'minor_edit' => $row->ar_minor_edit,
238                                 'text_id'    => $row->ar_text_id,
239                                 ) );
240                         $revision->insertOn( $dbw );
241                 }
242                 
243                 if( $revision ) {
244                         # FIXME: Update latest if newer as well...
245                         if( $newid ) {
246                                 # FIXME: update article count if changed...
247                                 $article->updateRevisionOn( $dbw, $revision, $previousRevId );
248                                 
249                                 # Finally, clean up the link tables
250                                 $wgLinkCache = new LinkCache();
251                                 # Select for update
252                                 $wgLinkCache->forUpdate( true );
253                                 
254                                 # Create a dummy OutputPage to update the outgoing links
255                                 $dummyOut = new OutputPage();
256                                 $dummyOut->addWikiText( $revision->getText() );
257
258                                 $u = new LinksUpdate( $newid, $this->title->getPrefixedDBkey() );
259                                 array_push( $wgDeferredUpdateList, $u );
260                                 
261                                 #TODO: SearchUpdate, etc.
262                         }
263                                 
264                         if( $newid ) {
265                                 Article::onArticleCreate( $this->title );
266                         } else {
267                                 Article::onArticleEdit( $this->title );
268                         }
269                 } else {
270                         # Something went terribly worong!
271                 }
272
273                 # Now that it's safely stored, take it out of the archive
274                 $dbw->delete( 'archive',
275                         /* WHERE */ array(
276                                 'ar_namespace' => $this->title->getNamespace(),
277                                 'ar_title' => $this->title->getDBkey(),
278                                 $oldones ),
279                         $fname );
280                 
281                 # Touch the log!
282                 $log = new LogPage( 'delete' );
283                 if( $restoreAll ) {
284                         $reason = '';
285                 } else {
286                         $reason = wfMsgForContent( 'undeletedrevisions', $restoreRevisions );
287                 }
288                 $log->addEntry( 'restore', $this->title, $reason );
289
290                 return true;
291         }
292 }
293
294 /**
295  *
296  * @package MediaWiki
297  * @subpackage SpecialPage
298  */
299 class UndeleteForm {
300         var $mAction, $mTarget, $mTimestamp, $mRestore, $mTargetObj;
301         var $mTargetTimestamp;
302
303         function UndeleteForm( &$request, $par = "" ) {
304                 global $wgUser;
305                 $this->mAction = $request->getText( 'action' );
306                 $this->mTarget = $request->getText( 'target' );
307                 $this->mTimestamp = $request->getText( 'timestamp' );
308                 $this->mRestore = $request->getCheck( 'restore' ) &&
309                         $request->wasPosted() &&
310                         $wgUser->matchEditToken( $request->getVal( 'wpEditToken' ) );
311                 if( $par != "" ) {
312                         $this->mTarget = $par;
313                 }
314                 if ( $this->mTarget !== "" ) {
315                         $this->mTargetObj = Title::newFromURL( $this->mTarget );
316                 } else {
317                         $this->mTargetObj = NULL;
318                 }
319                 if( $this->mRestore ) {
320                         $timestamps = array();
321                         foreach( $_REQUEST as $key => $val ) {
322                                 if( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
323                                         array_push( $timestamps, $matches[1] );
324                                 }
325                         }
326                         rsort( $timestamps );
327                         $this->mTargetTimestamp = $timestamps;
328                 }
329         }
330
331         function execute() {
332                 if( is_null( $this->mTargetObj ) ) {
333                         return $this->showList();
334                 }
335                 if( $this->mTimestamp !== "" ) {
336                         return $this->showRevision( $this->mTimestamp );
337                 }
338                 if( $this->mRestore && $this->mAction == "submit" ) {
339                         return $this->undelete();
340                 }
341                 return $this->showHistory();
342         }
343
344         /* private */ function showList() {
345                 global $wgLang, $wgContLang, $wgUser, $wgOut;
346                 $fname = "UndeleteForm::showList";
347                 
348                 # List undeletable articles    
349                 $result = PageArchive::listAllPages();
350                 
351                 $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
352                 $wgOut->addWikiText( wfMsg( "undeletepagetext" ) );
353
354                 $sk = $wgUser->getSkin();
355                 $undelete =& Title::makeTitle( NS_SPECIAL, 'Undelete' );
356                 $wgOut->addHTML( "<ul>\n" );
357                 while( $row = $result->fetchObject() ) {
358                         $n = ($row->ar_namespace ? 
359                                 ($wgContLang->getNsText( $row->ar_namespace ) . ":") : "").
360                                 $row->ar_title;
361                         $link = $sk->makeKnownLinkObj( $undelete,
362                                 htmlspecialchars( $n ), "target=" . urlencode( $n ) );
363                         $revisions = htmlspecialchars( wfMsg( "undeleterevisions",
364                                 $wgLang->formatNum( $row->count ) ) );
365                         $wgOut->addHTML( "<li>$link $revisions</li>\n" );
366                 }
367                 $result->free();
368                 $wgOut->addHTML( "</ul>\n" );
369                 
370                 return true;
371         }
372         
373         /* private */ function showRevision( $timestamp ) {
374                 global $wgLang, $wgUser, $wgOut;
375                 $fname = "UndeleteForm::showRevision";
376
377                 if(!preg_match("/[0-9]{14}/",$timestamp)) return 0;
378
379                 $archive =& new PageArchive( $this->mTargetObj );
380                 $text = $archive->getRevisionText( $timestamp );
381                 
382                 $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
383                 $wgOut->addWikiText( "(" . wfMsg( "undeleterevision",
384                         $wgLang->date( $timestamp ) ) . ")\n<hr />\n" . $text );
385         }
386
387         /* private */ function showHistory() {
388                 global $wgLang, $wgUser, $wgOut;
389                 
390                 $sk = $wgUser->getSkin();
391                 $wgOut->setPagetitle( wfMsg( "undeletepage" ) );
392
393                 $archive = new PageArchive( $this->mTargetObj );
394                 $text = $archive->getLastRevisionText();
395                 if( is_null( $text ) ) {
396                         $wgOut->addWikiText( wfMsg( "nohistory" ) );
397                         return;
398                 }
399                 $wgOut->addWikiText( wfMsg( "undeletehistory" ) . "\n----\n" . $text );
400
401                 # List all stored revisions
402                 $revisions = $archive->listRevisions();
403                 
404                 $titleObj = Title::makeTitle( NS_SPECIAL, "Undelete" );
405                 $action = $titleObj->escapeLocalURL( "action=submit" );
406                 $encTarget = htmlspecialchars( $this->mTarget );
407                 $button = htmlspecialchars( wfMsg("undeletebtn") );
408                 $token = htmlspecialchars( $wgUser->editToken() );
409                 
410                 $wgOut->addHTML("
411         <form id=\"undelete\" method=\"post\" action=\"{$action}\">
412         <input type=\"hidden\" name=\"target\" value=\"{$encTarget}\" />
413         <input type=\"submit\" name=\"restore\" value=\"{$button}\" />
414         <input type='hidden' name='wpEditToken' value=\"{$token}\" />
415         ");
416
417                 # Show relevant lines from the deletion log:
418                 $wgOut->addHTML( "<h2>" . htmlspecialchars( LogPage::logName( 'delete' ) ) . "</h2>\n" );
419                 require_once( 'SpecialLog.php' );
420                 $logViewer =& new LogViewer(
421                         new LogReader(
422                                 new FauxRequest(
423                                         array( 'page' => $this->mTargetObj->getPrefixedText(),
424                                                'type' => 'delete' ) ) ) );
425                 $logViewer->showList( $wgOut );
426                 
427                 # The page's stored (deleted) history:
428                 $wgOut->addHTML( "<h2>" . htmlspecialchars( wfMsg( "history" ) ) . "</h2>\n" );
429                 $wgOut->addHTML("<ul>");
430                 $target = urlencode( $this->mTarget );
431                 while( $row = $revisions->fetchObject() ) {
432                         $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
433                         $checkBox = "<input type=\"checkbox\" name=\"ts$ts\" value=\"1\" />";
434                         $pageLink = $sk->makeKnownLinkObj( $titleObj,
435                                 $wgLang->timeanddate( $row->ar_timestamp, true ),
436                                 "target=$target&timestamp=$ts" );
437                         $userLink = htmlspecialchars( $row->ar_user_text );
438                         if( $row->ar_user ) {
439                                 $userLink = $sk->makeKnownLinkObj(
440                                         Title::makeTitle( NS_USER, $row->ar_user_text ),
441                                         $userLink );
442                         } else {
443                                 $userLink = $sk->makeKnownLinkObj(
444                                         Title::makeTitle( NS_SPECIAL, 'Contributions' ),
445                                         $userLink, 'target=' . $row->ar_user_text );
446                         }
447                         $comment = $sk->commentBlock( $row->ar_comment );
448                         $wgOut->addHTML( "<li>$checkBox $pageLink . . $userLink $comment</li>\n" );
449
450                 }
451                 $revisions->free();
452                 $wgOut->addHTML("</ul>\n</form>");
453                 
454                 return true;
455         }
456
457         function undelete() {
458                 global $wgOut;
459                 if( !is_null( $this->mTargetObj ) ) {
460                         $archive = new PageArchive( $this->mTargetObj );
461                         if( $archive->undelete( $this->mTargetTimestamp ) ) {
462                                 $wgOut->addWikiText( wfMsg( "undeletedtext", $this->mTarget ) );
463
464                                 if (NS_IMAGE == $this->mTargetObj->getNamespace()) {
465                                         /* refresh image metadata cache */
466                                         new Image( $this->mTargetObj );
467                                 }
468
469                                 return true;
470                         }
471                 }
472                 $wgOut->fatalError( wfMsg( "cannotundelete" ) );
473                 return false;
474         }
475 }
476
477 ?>