[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/specials/ -> SpecialUndelete.php (source)

   1  <?php
   2  /**
   3   * Implements Special:Undelete
   4   *
   5   * This program is free software; you can redistribute it and/or modify
   6   * it under the terms of the GNU General Public License as published by
   7   * the Free Software Foundation; either version 2 of the License, or
   8   * (at your option) any later version.
   9   *
  10   * This program is distributed in the hope that it will be useful,
  11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13   * GNU General Public License for more details.
  14   *
  15   * You should have received a copy of the GNU General Public License along
  16   * with this program; if not, write to the Free Software Foundation, Inc.,
  17   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  18   * http://www.gnu.org/copyleft/gpl.html
  19   *
  20   * @file
  21   * @ingroup SpecialPage
  22   */
  23  
  24  /**
  25   * Used to show archived pages and eventually restore them.
  26   *
  27   * @ingroup SpecialPage
  28   */
  29  class PageArchive {
  30      /** @var Title */
  31      protected $title;
  32  
  33      /** @var Status */
  34      protected $fileStatus;
  35  
  36      /** @var Status */
  37      protected $revisionStatus;
  38  
  39      /** @var Config */
  40      protected $config;
  41  
  42  	function __construct( $title, Config $config = null ) {
  43          if ( is_null( $title ) ) {
  44              throw new MWException( __METHOD__ . ' given a null title.' );
  45          }
  46          $this->title = $title;
  47          if ( $config === null ) {
  48              wfDebug( __METHOD__ . ' did not have a Config object passed to it' );
  49              $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
  50          }
  51          $this->config = $config;
  52      }
  53  
  54      /**
  55       * List all deleted pages recorded in the archive table. Returns result
  56       * wrapper with (ar_namespace, ar_title, count) fields, ordered by page
  57       * namespace/title.
  58       *
  59       * @return ResultWrapper
  60       */
  61  	public static function listAllPages() {
  62          $dbr = wfGetDB( DB_SLAVE );
  63  
  64          return self::listPages( $dbr, '' );
  65      }
  66  
  67      /**
  68       * List deleted pages recorded in the archive table matching the
  69       * given title prefix.
  70       * Returns result wrapper with (ar_namespace, ar_title, count) fields.
  71       *
  72       * @param string $prefix Title prefix
  73       * @return ResultWrapper
  74       */
  75  	public static function listPagesByPrefix( $prefix ) {
  76          $dbr = wfGetDB( DB_SLAVE );
  77  
  78          $title = Title::newFromText( $prefix );
  79          if ( $title ) {
  80              $ns = $title->getNamespace();
  81              $prefix = $title->getDBkey();
  82          } else {
  83              // Prolly won't work too good
  84              // @todo handle bare namespace names cleanly?
  85              $ns = 0;
  86          }
  87  
  88          $conds = array(
  89              'ar_namespace' => $ns,
  90              'ar_title' . $dbr->buildLike( $prefix, $dbr->anyString() ),
  91          );
  92  
  93          return self::listPages( $dbr, $conds );
  94      }
  95  
  96      /**
  97       * @param DatabaseBase $dbr
  98       * @param string|array $condition
  99       * @return bool|ResultWrapper
 100       */
 101  	protected static function listPages( $dbr, $condition ) {
 102          return $dbr->select(
 103              array( 'archive' ),
 104              array(
 105                  'ar_namespace',
 106                  'ar_title',
 107                  'count' => 'COUNT(*)'
 108              ),
 109              $condition,
 110              __METHOD__,
 111              array(
 112                  'GROUP BY' => array( 'ar_namespace', 'ar_title' ),
 113                  'ORDER BY' => array( 'ar_namespace', 'ar_title' ),
 114                  'LIMIT' => 100,
 115              )
 116          );
 117      }
 118  
 119      /**
 120       * List the revisions of the given page. Returns result wrapper with
 121       * (ar_minor_edit, ar_timestamp, ar_user, ar_user_text, ar_comment) fields.
 122       *
 123       * @return ResultWrapper
 124       */
 125  	function listRevisions() {
 126          $dbr = wfGetDB( DB_SLAVE );
 127  
 128          $tables = array( 'archive' );
 129  
 130          $fields = array(
 131              'ar_minor_edit', 'ar_timestamp', 'ar_user', 'ar_user_text',
 132              'ar_comment', 'ar_len', 'ar_deleted', 'ar_rev_id', 'ar_sha1',
 133          );
 134  
 135          if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
 136              $fields[] = 'ar_content_format';
 137              $fields[] = 'ar_content_model';
 138          }
 139  
 140          $conds = array( 'ar_namespace' => $this->title->getNamespace(),
 141              'ar_title' => $this->title->getDBkey() );
 142  
 143          $options = array( 'ORDER BY' => 'ar_timestamp DESC' );
 144  
 145          $join_conds = array();
 146  
 147          ChangeTags::modifyDisplayQuery(
 148              $tables,
 149              $fields,
 150              $conds,
 151              $join_conds,
 152              $options
 153          );
 154  
 155          return $dbr->select( $tables,
 156              $fields,
 157              $conds,
 158              __METHOD__,
 159              $options,
 160              $join_conds
 161          );
 162      }
 163  
 164      /**
 165       * List the deleted file revisions for this page, if it's a file page.
 166       * Returns a result wrapper with various filearchive fields, or null
 167       * if not a file page.
 168       *
 169       * @return ResultWrapper
 170       * @todo Does this belong in Image for fuller encapsulation?
 171       */
 172  	function listFiles() {
 173          if ( $this->title->getNamespace() != NS_FILE ) {
 174              return null;
 175          }
 176  
 177          $dbr = wfGetDB( DB_SLAVE );
 178          return $dbr->select(
 179              'filearchive',
 180              ArchivedFile::selectFields(),
 181              array( 'fa_name' => $this->title->getDBkey() ),
 182              __METHOD__,
 183              array( 'ORDER BY' => 'fa_timestamp DESC' )
 184          );
 185      }
 186  
 187      /**
 188       * Return a Revision object containing data for the deleted revision.
 189       * Note that the result *may* or *may not* have a null page ID.
 190       *
 191       * @param string $timestamp
 192       * @return Revision|null
 193       */
 194  	function getRevision( $timestamp ) {
 195          $dbr = wfGetDB( DB_SLAVE );
 196  
 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              'ar_deleted',
 208              'ar_len',
 209              'ar_sha1',
 210          );
 211  
 212          if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
 213              $fields[] = 'ar_content_format';
 214              $fields[] = 'ar_content_model';
 215          }
 216  
 217          $row = $dbr->selectRow( 'archive',
 218              $fields,
 219              array( 'ar_namespace' => $this->title->getNamespace(),
 220                  'ar_title' => $this->title->getDBkey(),
 221                  'ar_timestamp' => $dbr->timestamp( $timestamp ) ),
 222              __METHOD__ );
 223  
 224          if ( $row ) {
 225              return Revision::newFromArchiveRow( $row, array( 'title' => $this->title ) );
 226          }
 227  
 228          return null;
 229      }
 230  
 231      /**
 232       * Return the most-previous revision, either live or deleted, against
 233       * the deleted revision given by timestamp.
 234       *
 235       * May produce unexpected results in case of history merges or other
 236       * unusual time issues.
 237       *
 238       * @param string $timestamp
 239       * @return Revision|null Null when there is no previous revision
 240       */
 241  	function getPreviousRevision( $timestamp ) {
 242          $dbr = wfGetDB( DB_SLAVE );
 243  
 244          // Check the previous deleted revision...
 245          $row = $dbr->selectRow( 'archive',
 246              'ar_timestamp',
 247              array( 'ar_namespace' => $this->title->getNamespace(),
 248                  'ar_title' => $this->title->getDBkey(),
 249                  'ar_timestamp < ' .
 250                      $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
 251              __METHOD__,
 252              array(
 253                  'ORDER BY' => 'ar_timestamp DESC',
 254                  'LIMIT' => 1 ) );
 255          $prevDeleted = $row ? wfTimestamp( TS_MW, $row->ar_timestamp ) : false;
 256  
 257          $row = $dbr->selectRow( array( 'page', 'revision' ),
 258              array( 'rev_id', 'rev_timestamp' ),
 259              array(
 260                  'page_namespace' => $this->title->getNamespace(),
 261                  'page_title' => $this->title->getDBkey(),
 262                  'page_id = rev_page',
 263                  'rev_timestamp < ' .
 264                      $dbr->addQuotes( $dbr->timestamp( $timestamp ) ) ),
 265              __METHOD__,
 266              array(
 267                  'ORDER BY' => 'rev_timestamp DESC',
 268                  'LIMIT' => 1 ) );
 269          $prevLive = $row ? wfTimestamp( TS_MW, $row->rev_timestamp ) : false;
 270          $prevLiveId = $row ? intval( $row->rev_id ) : null;
 271  
 272          if ( $prevLive && $prevLive > $prevDeleted ) {
 273              // Most prior revision was live
 274              return Revision::newFromId( $prevLiveId );
 275          } elseif ( $prevDeleted ) {
 276              // Most prior revision was deleted
 277              return $this->getRevision( $prevDeleted );
 278          }
 279  
 280          // No prior revision on this page.
 281          return null;
 282      }
 283  
 284      /**
 285       * Get the text from an archive row containing ar_text, ar_flags and ar_text_id
 286       *
 287       * @param object $row Database row
 288       * @return string
 289       */
 290  	function getTextFromRow( $row ) {
 291          if ( is_null( $row->ar_text_id ) ) {
 292              // An old row from MediaWiki 1.4 or previous.
 293              // Text is embedded in this row in classic compression format.
 294              return Revision::getRevisionText( $row, 'ar_' );
 295          }
 296  
 297          // New-style: keyed to the text storage backend.
 298          $dbr = wfGetDB( DB_SLAVE );
 299          $text = $dbr->selectRow( 'text',
 300              array( 'old_text', 'old_flags' ),
 301              array( 'old_id' => $row->ar_text_id ),
 302              __METHOD__ );
 303  
 304          return Revision::getRevisionText( $text );
 305      }
 306  
 307      /**
 308       * Fetch (and decompress if necessary) the stored text of the most
 309       * recently edited deleted revision of the page.
 310       *
 311       * If there are no archived revisions for the page, returns NULL.
 312       *
 313       * @return string|null
 314       */
 315  	function getLastRevisionText() {
 316          $dbr = wfGetDB( DB_SLAVE );
 317          $row = $dbr->selectRow( 'archive',
 318              array( 'ar_text', 'ar_flags', 'ar_text_id' ),
 319              array( 'ar_namespace' => $this->title->getNamespace(),
 320                  'ar_title' => $this->title->getDBkey() ),
 321              __METHOD__,
 322              array( 'ORDER BY' => 'ar_timestamp DESC' ) );
 323  
 324          if ( $row ) {
 325              return $this->getTextFromRow( $row );
 326          }
 327  
 328          return null;
 329      }
 330  
 331      /**
 332       * Quick check if any archived revisions are present for the page.
 333       *
 334       * @return bool
 335       */
 336  	function isDeleted() {
 337          $dbr = wfGetDB( DB_SLAVE );
 338          $n = $dbr->selectField( 'archive', 'COUNT(ar_title)',
 339              array( 'ar_namespace' => $this->title->getNamespace(),
 340                  'ar_title' => $this->title->getDBkey() ),
 341              __METHOD__
 342          );
 343  
 344          return ( $n > 0 );
 345      }
 346  
 347      /**
 348       * Restore the given (or all) text and file revisions for the page.
 349       * Once restored, the items will be removed from the archive tables.
 350       * The deletion log will be updated with an undeletion notice.
 351       *
 352       * @param array $timestamps Pass an empty array to restore all revisions,
 353       *   otherwise list the ones to undelete.
 354       * @param string $comment
 355       * @param array $fileVersions
 356       * @param bool $unsuppress
 357       * @param User $user User performing the action, or null to use $wgUser
 358       * @return array(number of file revisions restored, number of image revisions
 359       *   restored, log message) on success, false on failure.
 360       */
 361  	function undelete( $timestamps, $comment = '', $fileVersions = array(),
 362          $unsuppress = false, User $user = null
 363      ) {
 364          // If both the set of text revisions and file revisions are empty,
 365          // restore everything. Otherwise, just restore the requested items.
 366          $restoreAll = empty( $timestamps ) && empty( $fileVersions );
 367  
 368          $restoreText = $restoreAll || !empty( $timestamps );
 369          $restoreFiles = $restoreAll || !empty( $fileVersions );
 370  
 371          if ( $restoreFiles && $this->title->getNamespace() == NS_FILE ) {
 372              $img = wfLocalFile( $this->title );
 373              $this->fileStatus = $img->restore( $fileVersions, $unsuppress );
 374              if ( !$this->fileStatus->isOK() ) {
 375                  return false;
 376              }
 377              $filesRestored = $this->fileStatus->successCount;
 378          } else {
 379              $filesRestored = 0;
 380          }
 381  
 382          if ( $restoreText ) {
 383              $this->revisionStatus = $this->undeleteRevisions( $timestamps, $unsuppress, $comment );
 384              if ( !$this->revisionStatus->isOK() ) {
 385                  return false;
 386              }
 387  
 388              $textRestored = $this->revisionStatus->getValue();
 389          } else {
 390              $textRestored = 0;
 391          }
 392  
 393          // Touch the log!
 394  
 395          if ( $textRestored && $filesRestored ) {
 396              $reason = wfMessage( 'undeletedrevisions-files' )
 397                  ->numParams( $textRestored, $filesRestored )->inContentLanguage()->text();
 398          } elseif ( $textRestored ) {
 399              $reason = wfMessage( 'undeletedrevisions' )->numParams( $textRestored )
 400                  ->inContentLanguage()->text();
 401          } elseif ( $filesRestored ) {
 402              $reason = wfMessage( 'undeletedfiles' )->numParams( $filesRestored )
 403                  ->inContentLanguage()->text();
 404          } else {
 405              wfDebug( "Undelete: nothing undeleted...\n" );
 406  
 407              return false;
 408          }
 409  
 410          if ( trim( $comment ) != '' ) {
 411              $reason .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $comment;
 412          }
 413  
 414          if ( $user === null ) {
 415              global $wgUser;
 416              $user = $wgUser;
 417          }
 418  
 419          $logEntry = new ManualLogEntry( 'delete', 'restore' );
 420          $logEntry->setPerformer( $user );
 421          $logEntry->setTarget( $this->title );
 422          $logEntry->setComment( $reason );
 423  
 424          wfRunHooks( 'ArticleUndeleteLogEntry', array( $this, &$logEntry, $user ) );
 425  
 426          $logid = $logEntry->insert();
 427          $logEntry->publish( $logid );
 428  
 429          return array( $textRestored, $filesRestored, $reason );
 430      }
 431  
 432      /**
 433       * This is the meaty bit -- restores archived revisions of the given page
 434       * to the cur/old tables. If the page currently exists, all revisions will
 435       * be stuffed into old, otherwise the most recent will go into cur.
 436       *
 437       * @param array $timestamps Pass an empty array to restore all revisions,
 438       *   otherwise list the ones to undelete.
 439       * @param bool $unsuppress Remove all ar_deleted/fa_deleted restrictions of seletected revs
 440       * @param string $comment
 441       * @throws ReadOnlyError
 442       * @return Status Status object containing the number of revisions restored on success
 443       */
 444  	private function undeleteRevisions( $timestamps, $unsuppress = false, $comment = '' ) {
 445          if ( wfReadOnly() ) {
 446              throw new ReadOnlyError();
 447          }
 448  
 449          $restoreAll = empty( $timestamps );
 450          $dbw = wfGetDB( DB_MASTER );
 451  
 452          # Does this page already exist? We'll have to update it...
 453          $article = WikiPage::factory( $this->title );
 454          # Load latest data for the current page (bug 31179)
 455          $article->loadPageData( 'fromdbmaster' );
 456          $oldcountable = $article->isCountable();
 457  
 458          $page = $dbw->selectRow( 'page',
 459              array( 'page_id', 'page_latest' ),
 460              array( 'page_namespace' => $this->title->getNamespace(),
 461                  'page_title' => $this->title->getDBkey() ),
 462              __METHOD__,
 463              array( 'FOR UPDATE' ) // lock page
 464          );
 465  
 466          if ( $page ) {
 467              $makepage = false;
 468              # Page already exists. Import the history, and if necessary
 469              # we'll update the latest revision field in the record.
 470  
 471              $previousRevId = $page->page_latest;
 472  
 473              # Get the time span of this page
 474              $previousTimestamp = $dbw->selectField( 'revision', 'rev_timestamp',
 475                  array( 'rev_id' => $previousRevId ),
 476                  __METHOD__ );
 477  
 478              if ( $previousTimestamp === false ) {
 479                  wfDebug( __METHOD__ . ": existing page refers to a page_latest that does not exist\n" );
 480  
 481                  $status = Status::newGood( 0 );
 482                  $status->warning( 'undeleterevision-missing' );
 483  
 484                  return $status;
 485              }
 486          } else {
 487              # Have to create a new article...
 488              $makepage = true;
 489              $previousRevId = 0;
 490              $previousTimestamp = 0;
 491          }
 492  
 493          $oldWhere = array(
 494              'ar_namespace' => $this->title->getNamespace(),
 495              'ar_title' => $this->title->getDBkey(),
 496          );
 497          if ( !$restoreAll ) {
 498              $oldWhere['ar_timestamp'] = array_map( array( &$dbw, 'timestamp' ), $timestamps );
 499          }
 500  
 501          $fields = array(
 502              'ar_rev_id',
 503              'ar_text',
 504              'ar_comment',
 505              'ar_user',
 506              'ar_user_text',
 507              'ar_timestamp',
 508              'ar_minor_edit',
 509              'ar_flags',
 510              'ar_text_id',
 511              'ar_deleted',
 512              'ar_page_id',
 513              'ar_len',
 514              'ar_sha1'
 515          );
 516  
 517          if ( $this->config->get( 'ContentHandlerUseDB' ) ) {
 518              $fields[] = 'ar_content_format';
 519              $fields[] = 'ar_content_model';
 520          }
 521  
 522          /**
 523           * Select each archived revision...
 524           */
 525          $result = $dbw->select( 'archive',
 526              $fields,
 527              $oldWhere,
 528              __METHOD__,
 529              /* options */ array( 'ORDER BY' => 'ar_timestamp' )
 530          );
 531  
 532          $rev_count = $result->numRows();
 533          if ( !$rev_count ) {
 534              wfDebug( __METHOD__ . ": no revisions to restore\n" );
 535  
 536              $status = Status::newGood( 0 );
 537              $status->warning( "undelete-no-results" );
 538  
 539              return $status;
 540          }
 541  
 542          $result->seek( $rev_count - 1 ); // move to last
 543          $row = $result->fetchObject(); // get newest archived rev
 544          $oldPageId = (int)$row->ar_page_id; // pass this to ArticleUndelete hook
 545          $result->seek( 0 ); // move back
 546  
 547          // grab the content to check consistency with global state before restoring the page.
 548          $revision = Revision::newFromArchiveRow( $row,
 549              array(
 550                  'title' => $article->getTitle(), // used to derive default content model
 551              )
 552          );
 553          $user = User::newFromName( $revision->getRawUserText(), false );
 554          $content = $revision->getContent( Revision::RAW );
 555  
 556          //NOTE: article ID may not be known yet. prepareSave() should not modify the database.
 557          $status = $content->prepareSave( $article, 0, -1, $user );
 558  
 559          if ( !$status->isOK() ) {
 560              return $status;
 561          }
 562  
 563          if ( $makepage ) {
 564              // Check the state of the newest to-be version...
 565              if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
 566                  return Status::newFatal( "undeleterevdel" );
 567              }
 568              // Safe to insert now...
 569              $newid = $article->insertOn( $dbw );
 570              $pageId = $newid;
 571          } else {
 572              // Check if a deleted revision will become the current revision...
 573              if ( $row->ar_timestamp > $previousTimestamp ) {
 574                  // Check the state of the newest to-be version...
 575                  if ( !$unsuppress && ( $row->ar_deleted & Revision::DELETED_TEXT ) ) {
 576                      return Status::newFatal( "undeleterevdel" );
 577                  }
 578              }
 579  
 580              $newid = false;
 581              $pageId = $article->getId();
 582          }
 583  
 584          $revision = null;
 585          $restored = 0;
 586  
 587          foreach ( $result as $row ) {
 588              // Check for key dupes due to shitty archive integrity.
 589              if ( $row->ar_rev_id ) {
 590                  $exists = $dbw->selectField( 'revision', '1',
 591                      array( 'rev_id' => $row->ar_rev_id ), __METHOD__ );
 592                  if ( $exists ) {
 593                      continue; // don't throw DB errors
 594                  }
 595              }
 596              // Insert one revision at a time...maintaining deletion status
 597              // unless we are specifically removing all restrictions...
 598              $revision = Revision::newFromArchiveRow( $row,
 599                  array(
 600                      'page' => $pageId,
 601                      'title' => $this->title,
 602                      'deleted' => $unsuppress ? 0 : $row->ar_deleted
 603                  ) );
 604  
 605              $revision->insertOn( $dbw );
 606              $restored++;
 607  
 608              wfRunHooks( 'ArticleRevisionUndeleted', array( &$this->title, $revision, $row->ar_page_id ) );
 609          }
 610          # Now that it's safely stored, take it out of the archive
 611          $dbw->delete( 'archive',
 612              $oldWhere,
 613              __METHOD__ );
 614  
 615          // Was anything restored at all?
 616          if ( $restored == 0 ) {
 617              return Status::newGood( 0 );
 618          }
 619  
 620          $created = (bool)$newid;
 621  
 622          // Attach the latest revision to the page...
 623          $wasnew = $article->updateIfNewerOn( $dbw, $revision, $previousRevId );
 624          if ( $created || $wasnew ) {
 625              // Update site stats, link tables, etc
 626              $user = User::newFromName( $revision->getRawUserText(), false );
 627              $article->doEditUpdates(
 628                  $revision,
 629                  $user,
 630                  array( 'created' => $created, 'oldcountable' => $oldcountable )
 631              );
 632          }
 633  
 634          wfRunHooks( 'ArticleUndelete', array( &$this->title, $created, $comment, $oldPageId ) );
 635  
 636          if ( $this->title->getNamespace() == NS_FILE ) {
 637              $update = new HTMLCacheUpdate( $this->title, 'imagelinks' );
 638              $update->doUpdate();
 639          }
 640  
 641          return Status::newGood( $restored );
 642      }
 643  
 644      /**
 645       * @return Status
 646       */
 647  	function getFileStatus() {
 648          return $this->fileStatus;
 649      }
 650  
 651      /**
 652       * @return Status
 653       */
 654  	function getRevisionStatus() {
 655          return $this->revisionStatus;
 656      }
 657  }
 658  
 659  /**
 660   * Special page allowing users with the appropriate permissions to view
 661   * and restore deleted content.
 662   *
 663   * @ingroup SpecialPage
 664   */
 665  class SpecialUndelete extends SpecialPage {
 666      private $mAction;
 667      private    $mTarget;
 668      private    $mTimestamp;
 669      private    $mRestore;
 670      private    $mInvert;
 671      private    $mFilename;
 672      private $mTargetTimestamp;
 673      private    $mAllowed;
 674      private    $mCanView;
 675      private    $mComment;
 676      private    $mToken;
 677  
 678      /** @var Title */
 679      private $mTargetObj;
 680  
 681  	function __construct() {
 682          parent::__construct( 'Undelete', 'deletedhistory' );
 683      }
 684  
 685  	function loadRequest( $par ) {
 686          $request = $this->getRequest();
 687          $user = $this->getUser();
 688  
 689          $this->mAction = $request->getVal( 'action' );
 690          if ( $par !== null && $par !== '' ) {
 691              $this->mTarget = $par;
 692          } else {
 693              $this->mTarget = $request->getVal( 'target' );
 694          }
 695  
 696          $this->mTargetObj = null;
 697  
 698          if ( $this->mTarget !== null && $this->mTarget !== '' ) {
 699              $this->mTargetObj = Title::newFromURL( $this->mTarget );
 700          }
 701  
 702          $this->mSearchPrefix = $request->getText( 'prefix' );
 703          $time = $request->getVal( 'timestamp' );
 704          $this->mTimestamp = $time ? wfTimestamp( TS_MW, $time ) : '';
 705          $this->mFilename = $request->getVal( 'file' );
 706  
 707          $posted = $request->wasPosted() &&
 708              $user->matchEditToken( $request->getVal( 'wpEditToken' ) );
 709          $this->mRestore = $request->getCheck( 'restore' ) && $posted;
 710          $this->mInvert = $request->getCheck( 'invert' ) && $posted;
 711          $this->mPreview = $request->getCheck( 'preview' ) && $posted;
 712          $this->mDiff = $request->getCheck( 'diff' );
 713          $this->mDiffOnly = $request->getBool( 'diffonly', $this->getUser()->getOption( 'diffonly' ) );
 714          $this->mComment = $request->getText( 'wpComment' );
 715          $this->mUnsuppress = $request->getVal( 'wpUnsuppress' ) && $user->isAllowed( 'suppressrevision' );
 716          $this->mToken = $request->getVal( 'token' );
 717  
 718          if ( $this->isAllowed( 'undelete' ) && !$user->isBlocked() ) {
 719              $this->mAllowed = true; // user can restore
 720              $this->mCanView = true; // user can view content
 721          } elseif ( $this->isAllowed( 'deletedtext' ) ) {
 722              $this->mAllowed = false; // user cannot restore
 723              $this->mCanView = true; // user can view content
 724              $this->mRestore = false;
 725          } else { // user can only view the list of revisions
 726              $this->mAllowed = false;
 727              $this->mCanView = false;
 728              $this->mTimestamp = '';
 729              $this->mRestore = false;
 730          }
 731  
 732          if ( $this->mRestore || $this->mInvert ) {
 733              $timestamps = array();
 734              $this->mFileVersions = array();
 735              foreach ( $request->getValues() as $key => $val ) {
 736                  $matches = array();
 737                  if ( preg_match( '/^ts(\d{14})$/', $key, $matches ) ) {
 738                      array_push( $timestamps, $matches[1] );
 739                  }
 740  
 741                  if ( preg_match( '/^fileid(\d+)$/', $key, $matches ) ) {
 742                      $this->mFileVersions[] = intval( $matches[1] );
 743                  }
 744              }
 745              rsort( $timestamps );
 746              $this->mTargetTimestamp = $timestamps;
 747          }
 748      }
 749  
 750      /**
 751       * Checks whether a user is allowed the permission for the
 752       * specific title if one is set.
 753       *
 754       * @param string $permission
 755       * @param User $user
 756       * @return bool
 757       */
 758  	private function isAllowed( $permission, User $user = null ) {
 759          $user = $user ? : $this->getUser();
 760          if ( $this->mTargetObj !== null ) {
 761              return $this->mTargetObj->userCan( $permission, $user );
 762          } else {
 763              return $user->isAllowed( $permission );
 764          }
 765      }
 766  
 767  	function userCanExecute( User $user ) {
 768          return $this->isAllowed( $this->mRestriction, $user );
 769      }
 770  
 771  	function execute( $par ) {
 772          $user = $this->getUser();
 773  
 774          $this->setHeaders();
 775          $this->outputHeader();
 776  
 777          $this->loadRequest( $par );
 778          $this->checkPermissions(); // Needs to be after mTargetObj is set
 779  
 780          $out = $this->getOutput();
 781  
 782          if ( is_null( $this->mTargetObj ) ) {
 783              $out->addWikiMsg( 'undelete-header' );
 784  
 785              # Not all users can just browse every deleted page from the list
 786              if ( $user->isAllowed( 'browsearchive' ) ) {
 787                  $this->showSearchForm();
 788              }
 789  
 790              return;
 791          }
 792  
 793          if ( $this->mAllowed ) {
 794              $out->setPageTitle( $this->msg( 'undeletepage' ) );
 795          } else {
 796              $out->setPageTitle( $this->msg( 'viewdeletedpage' ) );
 797          }
 798  
 799          $this->getSkin()->setRelevantTitle( $this->mTargetObj );
 800  
 801          if ( $this->mTimestamp !== '' ) {
 802              $this->showRevision( $this->mTimestamp );
 803          } elseif ( $this->mFilename !== null && $this->mTargetObj->inNamespace( NS_FILE ) ) {
 804              $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
 805              // Check if user is allowed to see this file
 806              if ( !$file->exists() ) {
 807                  $out->addWikiMsg( 'filedelete-nofile', $this->mFilename );
 808              } elseif ( !$file->userCan( File::DELETED_FILE, $user ) ) {
 809                  if ( $file->isDeleted( File::DELETED_RESTRICTED ) ) {
 810                      throw new PermissionsError( 'suppressrevision' );
 811                  } else {
 812                      throw new PermissionsError( 'deletedtext' );
 813                  }
 814              } elseif ( !$user->matchEditToken( $this->mToken, $this->mFilename ) ) {
 815                  $this->showFileConfirmationForm( $this->mFilename );
 816              } else {
 817                  $this->showFile( $this->mFilename );
 818              }
 819          } elseif ( $this->mRestore && $this->mAction == 'submit' ) {
 820              $this->undelete();
 821          } else {
 822              $this->showHistory();
 823          }
 824      }
 825  
 826  	function showSearchForm() {
 827          $out = $this->getOutput();
 828          $out->setPageTitle( $this->msg( 'undelete-search-title' ) );
 829          $out->addHTML(
 830              Xml::openElement( 'form', array( 'method' => 'get', 'action' => wfScript() ) ) .
 831                  Xml::fieldset( $this->msg( 'undelete-search-box' )->text() ) .
 832                  Html::hidden( 'title', $this->getPageTitle()->getPrefixedDBkey() ) .
 833                  Html::rawElement(
 834                      'label',
 835                      array( 'for' => 'prefix' ),
 836                      $this->msg( 'undelete-search-prefix' )->parse()
 837                  ) .
 838                  Xml::input(
 839                      'prefix',
 840                      20,
 841                      $this->mSearchPrefix,
 842                      array( 'id' => 'prefix', 'autofocus' => true )
 843                  ) . ' ' .
 844                  Xml::submitButton( $this->msg( 'undelete-search-submit' )->text() ) .
 845                  Xml::closeElement( 'fieldset' ) .
 846                  Xml::closeElement( 'form' )
 847          );
 848  
 849          # List undeletable articles
 850          if ( $this->mSearchPrefix ) {
 851              $result = PageArchive::listPagesByPrefix( $this->mSearchPrefix );
 852              $this->showList( $result );
 853          }
 854      }
 855  
 856      /**
 857       * Generic list of deleted pages
 858       *
 859       * @param ResultWrapper $result
 860       * @return bool
 861       */
 862  	private function showList( $result ) {
 863          $out = $this->getOutput();
 864  
 865          if ( $result->numRows() == 0 ) {
 866              $out->addWikiMsg( 'undelete-no-results' );
 867  
 868              return false;
 869          }
 870  
 871          $out->addWikiMsg( 'undeletepagetext', $this->getLanguage()->formatNum( $result->numRows() ) );
 872  
 873          $undelete = $this->getPageTitle();
 874          $out->addHTML( "<ul>\n" );
 875          foreach ( $result as $row ) {
 876              $title = Title::makeTitleSafe( $row->ar_namespace, $row->ar_title );
 877              if ( $title !== null ) {
 878                  $item = Linker::linkKnown(
 879                      $undelete,
 880                      htmlspecialchars( $title->getPrefixedText() ),
 881                      array(),
 882                      array( 'target' => $title->getPrefixedText() )
 883                  );
 884              } else {
 885                  // The title is no longer valid, show as text
 886                  $item = Html::element(
 887                      'span',
 888                      array( 'class' => 'mw-invalidtitle' ),
 889                      Linker::getInvalidTitleDescription(
 890                          $this->getContext(),
 891                          $row->ar_namespace,
 892                          $row->ar_title
 893                      )
 894                  );
 895              }
 896              $revs = $this->msg( 'undeleterevisions' )->numParams( $row->count )->parse();
 897              $out->addHTML( "<li>{$item} ({$revs})</li>\n" );
 898          }
 899          $result->free();
 900          $out->addHTML( "</ul>\n" );
 901  
 902          return true;
 903      }
 904  
 905  	private function showRevision( $timestamp ) {
 906          if ( !preg_match( '/[0-9]{14}/', $timestamp ) ) {
 907              return;
 908          }
 909  
 910          $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
 911          if ( !wfRunHooks( 'UndeleteForm::showRevision', array( &$archive, $this->mTargetObj ) ) ) {
 912              return;
 913          }
 914          $rev = $archive->getRevision( $timestamp );
 915  
 916          $out = $this->getOutput();
 917          $user = $this->getUser();
 918  
 919          if ( !$rev ) {
 920              $out->addWikiMsg( 'undeleterevision-missing' );
 921  
 922              return;
 923          }
 924  
 925          if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
 926              if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
 927                  $out->wrapWikiMsg(
 928                      "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
 929                  $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
 930                      'rev-suppressed-text-permission' : 'rev-deleted-text-permission'
 931                  );
 932  
 933                  return;
 934              }
 935  
 936              $out->wrapWikiMsg(
 937                  "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
 938                  $rev->isDeleted( Revision::DELETED_RESTRICTED ) ?
 939                      'rev-suppressed-text-view' : 'rev-deleted-text-view'
 940              );
 941              $out->addHTML( '<br />' );
 942              // and we are allowed to see...
 943          }
 944  
 945          if ( $this->mDiff ) {
 946              $previousRev = $archive->getPreviousRevision( $timestamp );
 947              if ( $previousRev ) {
 948                  $this->showDiff( $previousRev, $rev );
 949                  if ( $this->mDiffOnly ) {
 950                      return;
 951                  }
 952  
 953                  $out->addHTML( '<hr />' );
 954              } else {
 955                  $out->addWikiMsg( 'undelete-nodiff' );
 956              }
 957          }
 958  
 959          $link = Linker::linkKnown(
 960              $this->getPageTitle( $this->mTargetObj->getPrefixedDBkey() ),
 961              htmlspecialchars( $this->mTargetObj->getPrefixedText() )
 962          );
 963  
 964          $lang = $this->getLanguage();
 965  
 966          // date and time are separate parameters to facilitate localisation.
 967          // $time is kept for backward compat reasons.
 968          $time = $lang->userTimeAndDate( $timestamp, $user );
 969          $d = $lang->userDate( $timestamp, $user );
 970          $t = $lang->userTime( $timestamp, $user );
 971          $userLink = Linker::revUserTools( $rev );
 972  
 973          $content = $rev->getContent( Revision::FOR_THIS_USER, $user );
 974  
 975          $isText = ( $content instanceof TextContent );
 976  
 977          if ( $this->mPreview || $isText ) {
 978              $openDiv = '<div id="mw-undelete-revision" class="mw-warning">';
 979          } else {
 980              $openDiv = '<div id="mw-undelete-revision">';
 981          }
 982          $out->addHTML( $openDiv );
 983  
 984          // Revision delete links
 985          if ( !$this->mDiff ) {
 986              $revdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
 987              if ( $revdel ) {
 988                  $out->addHTML( "$revdel " );
 989              }
 990          }
 991  
 992          $out->addHTML( $this->msg( 'undelete-revision' )->rawParams( $link )->params(
 993              $time )->rawParams( $userLink )->params( $d, $t )->parse() . '</div>' );
 994  
 995          if ( !wfRunHooks( 'UndeleteShowRevision', array( $this->mTargetObj, $rev ) ) ) {
 996              return;
 997          }
 998  
 999          if ( $this->mPreview || !$isText ) {
1000              // NOTE: non-text content has no source view, so always use rendered preview
1001  
1002              // Hide [edit]s
1003              $popts = $out->parserOptions();
1004              $popts->setEditSection( false );
1005  
1006              $pout = $content->getParserOutput( $this->mTargetObj, $rev->getId(), $popts, true );
1007              $out->addParserOutput( $pout );
1008          }
1009  
1010          if ( $isText ) {
1011              // source view for textual content
1012              $sourceView = Xml::element(
1013                  'textarea',
1014                  array(
1015                      'readonly' => 'readonly',
1016                      'cols' => $user->getIntOption( 'cols' ),
1017                      'rows' => $user->getIntOption( 'rows' )
1018                  ),
1019                  $content->getNativeData() . "\n"
1020              );
1021  
1022              $previewButton = Xml::element( 'input', array(
1023                  'type' => 'submit',
1024                  'name' => 'preview',
1025                  'value' => $this->msg( 'showpreview' )->text()
1026              ) );
1027          } else {
1028              $sourceView = '';
1029              $previewButton = '';
1030          }
1031  
1032          $diffButton = Xml::element( 'input', array(
1033              'name' => 'diff',
1034              'type' => 'submit',
1035              'value' => $this->msg( 'showdiff' )->text() ) );
1036  
1037          $out->addHTML(
1038              $sourceView .
1039                  Xml::openElement( 'div', array(
1040                      'style' => 'clear: both' ) ) .
1041                  Xml::openElement( 'form', array(
1042                      'method' => 'post',
1043                      'action' => $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) ) ) ) .
1044                  Xml::element( 'input', array(
1045                      'type' => 'hidden',
1046                      'name' => 'target',
1047                      'value' => $this->mTargetObj->getPrefixedDBkey() ) ) .
1048                  Xml::element( 'input', array(
1049                      'type' => 'hidden',
1050                      'name' => 'timestamp',
1051                      'value' => $timestamp ) ) .
1052                  Xml::element( 'input', array(
1053                      'type' => 'hidden',
1054                      'name' => 'wpEditToken',
1055                      'value' => $user->getEditToken() ) ) .
1056                  $previewButton .
1057                  $diffButton .
1058                  Xml::closeElement( 'form' ) .
1059                  Xml::closeElement( 'div' )
1060          );
1061      }
1062  
1063      /**
1064       * Build a diff display between this and the previous either deleted
1065       * or non-deleted edit.
1066       *
1067       * @param Revision $previousRev
1068       * @param Revision $currentRev
1069       * @return string HTML
1070       */
1071  	function showDiff( $previousRev, $currentRev ) {
1072          $diffContext = clone $this->getContext();
1073          $diffContext->setTitle( $currentRev->getTitle() );
1074          $diffContext->setWikiPage( WikiPage::factory( $currentRev->getTitle() ) );
1075  
1076          $diffEngine = $currentRev->getContentHandler()->createDifferenceEngine( $diffContext );
1077          $diffEngine->showDiffStyle();
1078  
1079          $formattedDiff = $diffEngine->generateContentDiffBody(
1080              $previousRev->getContent( Revision::FOR_THIS_USER, $this->getUser() ),
1081              $currentRev->getContent( Revision::FOR_THIS_USER, $this->getUser() )
1082          );
1083  
1084          $formattedDiff = $diffEngine->addHeader(
1085              $formattedDiff,
1086              $this->diffHeader( $previousRev, 'o' ),
1087              $this->diffHeader( $currentRev, 'n' )
1088          );
1089  
1090          $this->getOutput()->addHTML( "<div>$formattedDiff</div>\n" );
1091      }
1092  
1093      /**
1094       * @param Revision $rev
1095       * @param string $prefix
1096       * @return string
1097       */
1098  	private function diffHeader( $rev, $prefix ) {
1099          $isDeleted = !( $rev->getId() && $rev->getTitle() );
1100          if ( $isDeleted ) {
1101              /// @todo FIXME: $rev->getTitle() is null for deleted revs...?
1102              $targetPage = $this->getPageTitle();
1103              $targetQuery = array(
1104                  'target' => $this->mTargetObj->getPrefixedText(),
1105                  'timestamp' => wfTimestamp( TS_MW, $rev->getTimestamp() )
1106              );
1107          } else {
1108              /// @todo FIXME: getId() may return non-zero for deleted revs...
1109              $targetPage = $rev->getTitle();
1110              $targetQuery = array( 'oldid' => $rev->getId() );
1111          }
1112  
1113          // Add show/hide deletion links if available
1114          $user = $this->getUser();
1115          $lang = $this->getLanguage();
1116          $rdel = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
1117  
1118          if ( $rdel ) {
1119              $rdel = " $rdel";
1120          }
1121  
1122          $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
1123  
1124          $tags = wfGetDB( DB_SLAVE )->selectField(
1125              'tag_summary',
1126              'ts_tags',
1127              array( 'ts_rev_id' => $rev->getId() ),
1128              __METHOD__
1129          );
1130          $tagSummary = ChangeTags::formatSummaryRow( $tags, 'deleteddiff' );
1131  
1132          // FIXME This is reimplementing DifferenceEngine#getRevisionHeader
1133          // and partially #showDiffPage, but worse
1134          return '<div id="mw-diff-' . $prefix . 'title1"><strong>' .
1135              Linker::link(
1136                  $targetPage,
1137                  $this->msg(
1138                      'revisionasof',
1139                      $lang->userTimeAndDate( $rev->getTimestamp(), $user ),
1140                      $lang->userDate( $rev->getTimestamp(), $user ),
1141                      $lang->userTime( $rev->getTimestamp(), $user )
1142                  )->escaped(),
1143                  array(),
1144                  $targetQuery
1145              ) .
1146              '</strong></div>' .
1147              '<div id="mw-diff-' . $prefix . 'title2">' .
1148              Linker::revUserTools( $rev ) . '<br />' .
1149              '</div>' .
1150              '<div id="mw-diff-' . $prefix . 'title3">' .
1151              $minor . Linker::revComment( $rev ) . $rdel . '<br />' .
1152              '</div>' .
1153              '<div id="mw-diff-' . $prefix . 'title5">' .
1154              $tagSummary[0] . '<br />' .
1155              '</div>';
1156      }
1157  
1158      /**
1159       * Show a form confirming whether a tokenless user really wants to see a file
1160       * @param string $key
1161       */
1162  	private function showFileConfirmationForm( $key ) {
1163          $out = $this->getOutput();
1164          $lang = $this->getLanguage();
1165          $user = $this->getUser();
1166          $file = new ArchivedFile( $this->mTargetObj, '', $this->mFilename );
1167          $out->addWikiMsg( 'undelete-show-file-confirm',
1168              $this->mTargetObj->getText(),
1169              $lang->userDate( $file->getTimestamp(), $user ),
1170              $lang->userTime( $file->getTimestamp(), $user ) );
1171          $out->addHTML(
1172              Xml::openElement( 'form', array(
1173                      'method' => 'POST',
1174                      'action' => $this->getPageTitle()->getLocalURL( array(
1175                          'target' => $this->mTarget,
1176                          'file' => $key,
1177                          'token' => $user->getEditToken( $key ),
1178                      ) ),
1179                  )
1180              ) .
1181                  Xml::submitButton( $this->msg( 'undelete-show-file-submit' )->text() ) .
1182                  '</form>'
1183          );
1184      }
1185  
1186      /**
1187       * Show a deleted file version requested by the visitor.
1188       * @param string $key
1189       */
1190  	private function showFile( $key ) {
1191          $this->getOutput()->disable();
1192  
1193          # We mustn't allow the output to be Squid cached, otherwise
1194          # if an admin previews a deleted image, and it's cached, then
1195          # a user without appropriate permissions can toddle off and
1196          # nab the image, and Squid will serve it
1197          $response = $this->getRequest()->response();
1198          $response->header( 'Expires: ' . gmdate( 'D, d M Y H:i:s', 0 ) . ' GMT' );
1199          $response->header( 'Cache-Control: no-cache, no-store, max-age=0, must-revalidate' );
1200          $response->header( 'Pragma: no-cache' );
1201  
1202          $repo = RepoGroup::singleton()->getLocalRepo();
1203          $path = $repo->getZonePath( 'deleted' ) . '/' . $repo->getDeletedHashPath( $key ) . $key;
1204          $repo->streamFile( $path );
1205      }
1206  
1207  	private function showHistory() {
1208          $out = $this->getOutput();
1209          if ( $this->mAllowed ) {
1210              $out->addModules( 'mediawiki.special.undelete' );
1211          }
1212          $out->wrapWikiMsg(
1213              "<div class='mw-undelete-pagetitle'>\n$1\n</div>\n",
1214              array( 'undeletepagetitle', wfEscapeWikiText( $this->mTargetObj->getPrefixedText() ) )
1215          );
1216  
1217          $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1218          wfRunHooks( 'UndeleteForm::showHistory', array( &$archive, $this->mTargetObj ) );
1219          /*
1220          $text = $archive->getLastRevisionText();
1221          if( is_null( $text ) ) {
1222              $out->addWikiMsg( 'nohistory' );
1223              return;
1224          }
1225          */
1226          $out->addHTML( '<div class="mw-undelete-history">' );
1227          if ( $this->mAllowed ) {
1228              $out->addWikiMsg( 'undeletehistory' );
1229              $out->addWikiMsg( 'undeleterevdel' );
1230          } else {
1231              $out->addWikiMsg( 'undeletehistorynoadmin' );
1232          }
1233          $out->addHTML( '</div>' );
1234  
1235          # List all stored revisions
1236          $revisions = $archive->listRevisions();
1237          $files = $archive->listFiles();
1238  
1239          $haveRevisions = $revisions && $revisions->numRows() > 0;
1240          $haveFiles = $files && $files->numRows() > 0;
1241  
1242          # Batch existence check on user and talk pages
1243          if ( $haveRevisions ) {
1244              $batch = new LinkBatch();
1245              foreach ( $revisions as $row ) {
1246                  $batch->addObj( Title::makeTitleSafe( NS_USER, $row->ar_user_text ) );
1247                  $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->ar_user_text ) );
1248              }
1249              $batch->execute();
1250              $revisions->seek( 0 );
1251          }
1252          if ( $haveFiles ) {
1253              $batch = new LinkBatch();
1254              foreach ( $files as $row ) {
1255                  $batch->addObj( Title::makeTitleSafe( NS_USER, $row->fa_user_text ) );
1256                  $batch->addObj( Title::makeTitleSafe( NS_USER_TALK, $row->fa_user_text ) );
1257              }
1258              $batch->execute();
1259              $files->seek( 0 );
1260          }
1261  
1262          if ( $this->mAllowed ) {
1263              $action = $this->getPageTitle()->getLocalURL( array( 'action' => 'submit' ) );
1264              # Start the form here
1265              $top = Xml::openElement(
1266                  'form',
1267                  array( 'method' => 'post', 'action' => $action, 'id' => 'undelete' )
1268              );
1269              $out->addHTML( $top );
1270          }
1271  
1272          # Show relevant lines from the deletion log:
1273          $deleteLogPage = new LogPage( 'delete' );
1274          $out->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) . "\n" );
1275          LogEventsList::showLogExtract( $out, 'delete', $this->mTargetObj );
1276          # Show relevant lines from the suppression log:
1277          $suppressLogPage = new LogPage( 'suppress' );
1278          if ( $this->getUser()->isAllowed( 'suppressionlog' ) ) {
1279              $out->addHTML( Xml::element( 'h2', null, $suppressLogPage->getName()->text() ) . "\n" );
1280              LogEventsList::showLogExtract( $out, 'suppress', $this->mTargetObj );
1281          }
1282  
1283          if ( $this->mAllowed && ( $haveRevisions || $haveFiles ) ) {
1284              # Format the user-visible controls (comment field, submission button)
1285              # in a nice little table
1286              if ( $this->getUser()->isAllowed( 'suppressrevision' ) ) {
1287                  $unsuppressBox =
1288                      "<tr>
1289                          <td>&#160;</td>
1290                          <td class='mw-input'>" .
1291                          Xml::checkLabel( $this->msg( 'revdelete-unsuppress' )->text(),
1292                              'wpUnsuppress', 'mw-undelete-unsuppress', $this->mUnsuppress ) .
1293                          "</td>
1294                      </tr>";
1295              } else {
1296                  $unsuppressBox = '';
1297              }
1298  
1299              $table = Xml::fieldset( $this->msg( 'undelete-fieldset-title' )->text() ) .
1300                  Xml::openElement( 'table', array( 'id' => 'mw-undelete-table' ) ) .
1301                  "<tr>
1302                      <td colspan='2' class='mw-undelete-extrahelp'>" .
1303                  $this->msg( 'undeleteextrahelp' )->parseAsBlock() .
1304                  "</td>
1305              </tr>
1306              <tr>
1307                  <td class='mw-label'>" .
1308                  Xml::label( $this->msg( 'undeletecomment' )->text(), 'wpComment' ) .
1309                  "</td>
1310                  <td class='mw-input'>" .
1311                  Xml::input(
1312                      'wpComment',
1313                      50,
1314                      $this->mComment,
1315                      array( 'id' => 'wpComment', 'autofocus' => true )
1316                  ) .
1317                  "</td>
1318              </tr>
1319              <tr>
1320                  <td>&#160;</td>
1321                  <td class='mw-submit'>" .
1322                  Xml::submitButton(
1323                      $this->msg( 'undeletebtn' )->text(),
1324                      array( 'name' => 'restore', 'id' => 'mw-undelete-submit' )
1325                  ) . ' ' .
1326                  Xml::submitButton(
1327                      $this->msg( 'undeleteinvert' )->text(),
1328                      array( 'name' => 'invert', 'id' => 'mw-undelete-invert' )
1329                  ) .
1330                  "</td>
1331              </tr>" .
1332                  $unsuppressBox .
1333                  Xml::closeElement( 'table' ) .
1334                  Xml::closeElement( 'fieldset' );
1335  
1336              $out->addHTML( $table );
1337          }
1338  
1339          $out->addHTML( Xml::element( 'h2', null, $this->msg( 'history' )->text() ) . "\n" );
1340  
1341          if ( $haveRevisions ) {
1342              # The page's stored (deleted) history:
1343              $out->addHTML( '<ul>' );
1344              $remaining = $revisions->numRows();
1345              $earliestLiveTime = $this->mTargetObj->getEarliestRevTime();
1346  
1347              foreach ( $revisions as $row ) {
1348                  $remaining--;
1349                  $out->addHTML( $this->formatRevisionRow( $row, $earliestLiveTime, $remaining ) );
1350              }
1351              $revisions->free();
1352              $out->addHTML( '</ul>' );
1353          } else {
1354              $out->addWikiMsg( 'nohistory' );
1355          }
1356  
1357          if ( $haveFiles ) {
1358              $out->addHTML( Xml::element( 'h2', null, $this->msg( 'filehist' )->text() ) . "\n" );
1359              $out->addHTML( '<ul>' );
1360              foreach ( $files as $row ) {
1361                  $out->addHTML( $this->formatFileRow( $row ) );
1362              }
1363              $files->free();
1364              $out->addHTML( '</ul>' );
1365          }
1366  
1367          if ( $this->mAllowed ) {
1368              # Slip in the hidden controls here
1369              $misc = Html::hidden( 'target', $this->mTarget );
1370              $misc .= Html::hidden( 'wpEditToken', $this->getUser()->getEditToken() );
1371              $misc .= Xml::closeElement( 'form' );
1372              $out->addHTML( $misc );
1373          }
1374  
1375          return true;
1376      }
1377  
1378  	private function formatRevisionRow( $row, $earliestLiveTime, $remaining ) {
1379          $rev = Revision::newFromArchiveRow( $row,
1380              array(
1381                  'title' => $this->mTargetObj
1382              ) );
1383  
1384          $revTextSize = '';
1385          $ts = wfTimestamp( TS_MW, $row->ar_timestamp );
1386          // Build checkboxen...
1387          if ( $this->mAllowed ) {
1388              if ( $this->mInvert ) {
1389                  if ( in_array( $ts, $this->mTargetTimestamp ) ) {
1390                      $checkBox = Xml::check( "ts$ts" );
1391                  } else {
1392                      $checkBox = Xml::check( "ts$ts", true );
1393                  }
1394              } else {
1395                  $checkBox = Xml::check( "ts$ts" );
1396              }
1397          } else {
1398              $checkBox = '';
1399          }
1400  
1401          // Build page & diff links...
1402          $user = $this->getUser();
1403          if ( $this->mCanView ) {
1404              $titleObj = $this->getPageTitle();
1405              # Last link
1406              if ( !$rev->userCan( Revision::DELETED_TEXT, $this->getUser() ) ) {
1407                  $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1408                  $last = $this->msg( 'diff' )->escaped();
1409              } elseif ( $remaining > 0 || ( $earliestLiveTime && $ts > $earliestLiveTime ) ) {
1410                  $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
1411                  $last = Linker::linkKnown(
1412                      $titleObj,
1413                      $this->msg( 'diff' )->escaped(),
1414                      array(),
1415                      array(
1416                          'target' => $this->mTargetObj->getPrefixedText(),
1417                          'timestamp' => $ts,
1418                          'diff' => 'prev'
1419                      )
1420                  );
1421              } else {
1422                  $pageLink = $this->getPageLink( $rev, $titleObj, $ts );
1423                  $last = $this->msg( 'diff' )->escaped();
1424              }
1425          } else {
1426              $pageLink = htmlspecialchars( $this->getLanguage()->userTimeAndDate( $ts, $user ) );
1427              $last = $this->msg( 'diff' )->escaped();
1428          }
1429  
1430          // User links
1431          $userLink = Linker::revUserTools( $rev );
1432  
1433          // Minor edit
1434          $minor = $rev->isMinor() ? ChangesList::flag( 'minor' ) : '';
1435  
1436          // Revision text size
1437          $size = $row->ar_len;
1438          if ( !is_null( $size ) ) {
1439              $revTextSize = Linker::formatRevisionSize( $size );
1440          }
1441  
1442          // Edit summary
1443          $comment = Linker::revComment( $rev );
1444  
1445          // Tags
1446          $attribs = array();
1447          list( $tagSummary, $classes ) = ChangeTags::formatSummaryRow( $row->ts_tags, 'deletedhistory' );
1448          if ( $classes ) {
1449              $attribs['class'] = implode( ' ', $classes );
1450          }
1451  
1452          // Revision delete links
1453          $revdlink = Linker::getRevDeleteLink( $user, $rev, $this->mTargetObj );
1454  
1455          $revisionRow = $this->msg( 'undelete-revision-row' )
1456              ->rawParams(
1457                  $checkBox,
1458                  $revdlink,
1459                  $last,
1460                  $pageLink,
1461                  $userLink,
1462                  $minor,
1463                  $revTextSize,
1464                  $comment,
1465                  $tagSummary
1466              )
1467              ->escaped();
1468  
1469          return Xml::tags( 'li', $attribs, $revisionRow ) . "\n";
1470      }
1471  
1472  	private function formatFileRow( $row ) {
1473          $file = ArchivedFile::newFromRow( $row );
1474          $ts = wfTimestamp( TS_MW, $row->fa_timestamp );
1475          $user = $this->getUser();
1476  
1477          $checkBox = '';
1478          if ( $this->mCanView && $row->fa_storage_key ) {
1479              if ( $this->mAllowed ) {
1480                  $checkBox = Xml::check( 'fileid' . $row->fa_id );
1481              }
1482              $key = urlencode( $row->fa_storage_key );
1483              $pageLink = $this->getFileLink( $file, $this->getPageTitle(), $ts, $key );
1484          } else {
1485              $pageLink = $this->getLanguage()->userTimeAndDate( $ts, $user );
1486          }
1487          $userLink = $this->getFileUser( $file );
1488          $data = $this->msg( 'widthheight' )->numParams( $row->fa_width, $row->fa_height )->text();
1489          $bytes = $this->msg( 'parentheses' )
1490              ->rawParams( $this->msg( 'nbytes' )->numParams( $row->fa_size )->text() )
1491              ->plain();
1492          $data = htmlspecialchars( $data . ' ' . $bytes );
1493          $comment = $this->getFileComment( $file );
1494  
1495          // Add show/hide deletion links if available
1496          $canHide = $this->isAllowed( 'deleterevision' );
1497          if ( $canHide || ( $file->getVisibility() && $this->isAllowed( 'deletedhistory' ) ) ) {
1498              if ( !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1499                  // Revision was hidden from sysops
1500                  $revdlink = Linker::revDeleteLinkDisabled( $canHide );
1501              } else {
1502                  $query = array(
1503                      'type' => 'filearchive',
1504                      'target' => $this->mTargetObj->getPrefixedDBkey(),
1505                      'ids' => $row->fa_id
1506                  );
1507                  $revdlink = Linker::revDeleteLink( $query,
1508                      $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1509              }
1510          } else {
1511              $revdlink = '';
1512          }
1513  
1514          return "<li>$checkBox $revdlink $pageLink . . $userLink $data $comment</li>\n";
1515      }
1516  
1517      /**
1518       * Fetch revision text link if it's available to all users
1519       *
1520       * @param Revision $rev
1521       * @param Title $titleObj
1522       * @param string $ts Timestamp
1523       * @return string
1524       */
1525  	function getPageLink( $rev, $titleObj, $ts ) {
1526          $user = $this->getUser();
1527          $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1528  
1529          if ( !$rev->userCan( Revision::DELETED_TEXT, $user ) ) {
1530              return '<span class="history-deleted">' . $time . '</span>';
1531          }
1532  
1533          $link = Linker::linkKnown(
1534              $titleObj,
1535              htmlspecialchars( $time ),
1536              array(),
1537              array(
1538                  'target' => $this->mTargetObj->getPrefixedText(),
1539                  'timestamp' => $ts
1540              )
1541          );
1542  
1543          if ( $rev->isDeleted( Revision::DELETED_TEXT ) ) {
1544              $link = '<span class="history-deleted">' . $link . '</span>';
1545          }
1546  
1547          return $link;
1548      }
1549  
1550      /**
1551       * Fetch image view link if it's available to all users
1552       *
1553       * @param File|ArchivedFile $file
1554       * @param Title $titleObj
1555       * @param string $ts A timestamp
1556       * @param string $key A storage key
1557       *
1558       * @return string HTML fragment
1559       */
1560  	function getFileLink( $file, $titleObj, $ts, $key ) {
1561          $user = $this->getUser();
1562          $time = $this->getLanguage()->userTimeAndDate( $ts, $user );
1563  
1564          if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1565              return '<span class="history-deleted">' . $time . '</span>';
1566          }
1567  
1568          $link = Linker::linkKnown(
1569              $titleObj,
1570              htmlspecialchars( $time ),
1571              array(),
1572              array(
1573                  'target' => $this->mTargetObj->getPrefixedText(),
1574                  'file' => $key,
1575                  'token' => $user->getEditToken( $key )
1576              )
1577          );
1578  
1579          if ( $file->isDeleted( File::DELETED_FILE ) ) {
1580              $link = '<span class="history-deleted">' . $link . '</span>';
1581          }
1582  
1583          return $link;
1584      }
1585  
1586      /**
1587       * Fetch file's user id if it's available to this user
1588       *
1589       * @param File|ArchivedFile $file
1590       * @return string HTML fragment
1591       */
1592  	function getFileUser( $file ) {
1593          if ( !$file->userCan( File::DELETED_USER, $this->getUser() ) ) {
1594              return '<span class="history-deleted">' .
1595                  $this->msg( 'rev-deleted-user' )->escaped() .
1596                  '</span>';
1597          }
1598  
1599          $link = Linker::userLink( $file->getRawUser(), $file->getRawUserText() ) .
1600              Linker::userToolLinks( $file->getRawUser(), $file->getRawUserText() );
1601  
1602          if ( $file->isDeleted( File::DELETED_USER ) ) {
1603              $link = '<span class="history-deleted">' . $link . '</span>';
1604          }
1605  
1606          return $link;
1607      }
1608  
1609      /**
1610       * Fetch file upload comment if it's available to this user
1611       *
1612       * @param File|ArchivedFile $file
1613       * @return string HTML fragment
1614       */
1615  	function getFileComment( $file ) {
1616          if ( !$file->userCan( File::DELETED_COMMENT, $this->getUser() ) ) {
1617              return '<span class="history-deleted"><span class="comment">' .
1618                  $this->msg( 'rev-deleted-comment' )->escaped() . '</span></span>';
1619          }
1620  
1621          $link = Linker::commentBlock( $file->getRawDescription() );
1622  
1623          if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1624              $link = '<span class="history-deleted">' . $link . '</span>';
1625          }
1626  
1627          return $link;
1628      }
1629  
1630  	function undelete() {
1631          if ( $this->getConfig()->get( 'UploadMaintenance' ) && $this->mTargetObj->getNamespace() == NS_FILE ) {
1632              throw new ErrorPageError( 'undelete-error', 'filedelete-maintenance' );
1633          }
1634  
1635          if ( wfReadOnly() ) {
1636              throw new ReadOnlyError;
1637          }
1638  
1639          $out = $this->getOutput();
1640          $archive = new PageArchive( $this->mTargetObj, $this->getConfig() );
1641          wfRunHooks( 'UndeleteForm::undelete', array( &$archive, $this->mTargetObj ) );
1642          $ok = $archive->undelete(
1643              $this->mTargetTimestamp,
1644              $this->mComment,
1645              $this->mFileVersions,
1646              $this->mUnsuppress,
1647              $this->getUser()
1648          );
1649  
1650          if ( is_array( $ok ) ) {
1651              if ( $ok[1] ) { // Undeleted file count
1652                  wfRunHooks( 'FileUndeleteComplete', array(
1653                      $this->mTargetObj, $this->mFileVersions,
1654                      $this->getUser(), $this->mComment ) );
1655              }
1656  
1657              $link = Linker::linkKnown( $this->mTargetObj );
1658              $out->addHTML( $this->msg( 'undeletedpage' )->rawParams( $link )->parse() );
1659          } else {
1660              $out->setPageTitle( $this->msg( 'undelete-error' ) );
1661          }
1662  
1663          // Show revision undeletion warnings and errors
1664          $status = $archive->getRevisionStatus();
1665          if ( $status && !$status->isGood() ) {
1666              $out->addWikiText( '<div class="error">' .
1667                  $status->getWikiText(
1668                      'cannotundelete',
1669                      'cannotundelete'
1670                  ) . '</div>'
1671              );
1672          }
1673  
1674          // Show file undeletion warnings and errors
1675          $status = $archive->getFileStatus();
1676          if ( $status && !$status->isGood() ) {
1677              $out->addWikiText( '<div class="error">' .
1678                  $status->getWikiText(
1679                      'undelete-error-short',
1680                      'undelete-error-long'
1681                  ) . '</div>'
1682              );
1683          }
1684      }
1685  
1686  	protected function getGroupName() {
1687          return 'pagetools';
1688      }
1689  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1