[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/page/ -> ImagePage.php (source)

   1  <?php
   2  /**
   3   * Special handling for file description pages.
   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   */
  22  
  23  /**
  24   * Class for viewing MediaWiki file description pages
  25   *
  26   * @ingroup Media
  27   */
  28  class ImagePage extends Article {
  29      /** @var File */
  30      private $displayImg;
  31  
  32      /** @var FileRepo */
  33      private $repo;
  34  
  35      /** @var bool */
  36      private $fileLoaded;
  37  
  38      /** @var bool */
  39      protected $mExtraDescription = false;
  40  
  41      /**
  42       * @param Title $title
  43       * @return WikiFilePage
  44       */
  45  	protected function newPage( Title $title ) {
  46          // Overload mPage with a file-specific page
  47          return new WikiFilePage( $title );
  48      }
  49  
  50      /**
  51       * Constructor from a page id
  52       * @param int $id Article ID to load
  53       * @return ImagePage|null
  54       */
  55  	public static function newFromID( $id ) {
  56          $t = Title::newFromID( $id );
  57          # @todo FIXME: Doesn't inherit right
  58          return $t == null ? null : new self( $t );
  59          # return $t == null ? null : new static( $t ); // PHP 5.3
  60      }
  61  
  62      /**
  63       * @param File $file
  64       * @return void
  65       */
  66  	public function setFile( $file ) {
  67          $this->mPage->setFile( $file );
  68          $this->displayImg = $file;
  69          $this->fileLoaded = true;
  70      }
  71  
  72  	protected function loadFile() {
  73          if ( $this->fileLoaded ) {
  74              return;
  75          }
  76          $this->fileLoaded = true;
  77  
  78          $this->displayImg = $img = false;
  79          wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) );
  80          if ( !$img ) { // not set by hook?
  81              $img = wfFindFile( $this->getTitle() );
  82              if ( !$img ) {
  83                  $img = wfLocalFile( $this->getTitle() );
  84              }
  85          }
  86          $this->mPage->setFile( $img );
  87          if ( !$this->displayImg ) { // not set by hook?
  88              $this->displayImg = $img;
  89          }
  90          $this->repo = $img->getRepo();
  91      }
  92  
  93      /**
  94       * Handler for action=render
  95       * Include body text only; none of the image extras
  96       */
  97  	public function render() {
  98          $this->getContext()->getOutput()->setArticleBodyOnly( true );
  99          parent::view();
 100      }
 101  
 102  	public function view() {
 103          global $wgShowEXIF;
 104  
 105          $out = $this->getContext()->getOutput();
 106          $request = $this->getContext()->getRequest();
 107          $diff = $request->getVal( 'diff' );
 108          $diffOnly = $request->getBool(
 109              'diffonly',
 110              $this->getContext()->getUser()->getOption( 'diffonly' )
 111          );
 112  
 113          if ( $this->getTitle()->getNamespace() != NS_FILE || ( $diff !== null && $diffOnly ) ) {
 114              parent::view();
 115              return;
 116          }
 117  
 118          $this->loadFile();
 119  
 120          if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) {
 121              if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || $diff !== null ) {
 122                  // mTitle is the same as the redirect target so ask Article
 123                  // to perform the redirect for us.
 124                  $request->setVal( 'diffonly', 'true' );
 125                  parent::view();
 126                  return;
 127              } else {
 128                  // mTitle is not the same as the redirect target so it is
 129                  // probably the redirect page itself. Fake the redirect symbol
 130                  $out->setPageTitle( $this->getTitle()->getPrefixedText() );
 131                  $out->addHTML( $this->viewRedirect(
 132                      Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ),
 133                      /* $appendSubtitle */ true,
 134                      /* $forceKnown */ true )
 135                  );
 136                  $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
 137                  return;
 138              }
 139          }
 140  
 141          if ( $wgShowEXIF && $this->displayImg->exists() ) {
 142              // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
 143              $formattedMetadata = $this->displayImg->formatMetadata();
 144              $showmeta = $formattedMetadata !== false;
 145          } else {
 146              $showmeta = false;
 147          }
 148  
 149          if ( !$diff && $this->displayImg->exists() ) {
 150              $out->addHTML( $this->showTOC( $showmeta ) );
 151          }
 152  
 153          if ( !$diff ) {
 154              $this->openShowImage();
 155          }
 156  
 157          # No need to display noarticletext, we use our own message, output in openShowImage()
 158          if ( $this->mPage->getID() ) {
 159              # NS_FILE is in the user language, but this section (the actual wikitext)
 160              # should be in page content language
 161              $pageLang = $this->getTitle()->getPageViewLanguage();
 162              $out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content',
 163                  'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
 164                  'class' => 'mw-content-' . $pageLang->getDir() ) ) );
 165  
 166              parent::view();
 167  
 168              $out->addHTML( Xml::closeElement( 'div' ) );
 169          } else {
 170              # Just need to set the right headers
 171              $out->setArticleFlag( true );
 172              $out->setPageTitle( $this->getTitle()->getPrefixedText() );
 173              $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() );
 174          }
 175  
 176          # Show shared description, if needed
 177          if ( $this->mExtraDescription ) {
 178              $fol = wfMessage( 'shareddescriptionfollows' );
 179              if ( !$fol->isDisabled() ) {
 180                  $out->addWikiText( $fol->plain() );
 181              }
 182              $out->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
 183          }
 184  
 185          $this->closeShowImage();
 186          $this->imageHistory();
 187          // TODO: Cleanup the following
 188  
 189          $out->addHTML( Xml::element( 'h2',
 190              array( 'id' => 'filelinks' ),
 191              wfMessage( 'imagelinks' )->text() ) . "\n" );
 192          $this->imageDupes();
 193          # @todo FIXME: For some freaky reason, we can't redirect to foreign images.
 194          # Yet we return metadata about the target. Definitely an issue in the FileRepo
 195          $this->imageLinks();
 196  
 197          # Allow extensions to add something after the image links
 198          $html = '';
 199          wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) );
 200          if ( $html ) {
 201              $out->addHTML( $html );
 202          }
 203  
 204          if ( $showmeta ) {
 205              $out->addHTML( Xml::element(
 206                  'h2',
 207                  array( 'id' => 'metadata' ),
 208                  wfMessage( 'metadata' )->text() ) . "\n" );
 209              $out->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
 210              $out->addModules( array( 'mediawiki.action.view.metadata' ) );
 211          }
 212  
 213          // Add remote Filepage.css
 214          if ( !$this->repo->isLocal() ) {
 215              $css = $this->repo->getDescriptionStylesheetUrl();
 216              if ( $css ) {
 217                  $out->addStyle( $css );
 218              }
 219          }
 220          // always show the local local Filepage.css, bug 29277
 221          $out->addModuleStyles( 'filepage' );
 222      }
 223  
 224      /**
 225       * @return File
 226       */
 227  	public function getDisplayedFile() {
 228          $this->loadFile();
 229          return $this->displayImg;
 230      }
 231  
 232      /**
 233       * Create the TOC
 234       *
 235       * @param bool $metadata Whether or not to show the metadata link
 236       * @return string
 237       */
 238  	protected function showTOC( $metadata ) {
 239          $r = array(
 240              '<li><a href="#file">' . wfMessage( 'file-anchor-link' )->escaped() . '</a></li>',
 241              '<li><a href="#filehistory">' . wfMessage( 'filehist' )->escaped() . '</a></li>',
 242              '<li><a href="#filelinks">' . wfMessage( 'imagelinks' )->escaped() . '</a></li>',
 243          );
 244          if ( $metadata ) {
 245              $r[] = '<li><a href="#metadata">' . wfMessage( 'metadata' )->escaped() . '</a></li>';
 246          }
 247  
 248          wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) );
 249  
 250          return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
 251      }
 252  
 253      /**
 254       * Make a table with metadata to be shown in the output page.
 255       *
 256       * @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata().
 257       *
 258       * @param array $metadata The array containing the Exif data
 259       * @return string The metadata table. This is treated as Wikitext (!)
 260       */
 261  	protected function makeMetadataTable( $metadata ) {
 262          $r = "<div class=\"mw-imagepage-section-metadata\">";
 263          $r .= wfMessage( 'metadata-help' )->plain();
 264          $r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
 265          foreach ( $metadata as $type => $stuff ) {
 266              foreach ( $stuff as $v ) {
 267                  # @todo FIXME: Why is this using escapeId for a class?!
 268                  $class = Sanitizer::escapeId( $v['id'] );
 269                  if ( $type == 'collapsed' ) {
 270                      // Handled by mediawiki.action.view.metadata module.
 271                      $class .= ' collapsable';
 272                  }
 273                  $r .= "<tr class=\"$class\">\n";
 274                  $r .= "<th>{$v['name']}</th>\n";
 275                  $r .= "<td>{$v['value']}</td>\n</tr>";
 276              }
 277          }
 278          $r .= "</table>\n</div>\n";
 279          return $r;
 280      }
 281  
 282      /**
 283       * Overloading Article's getContentObject method.
 284       *
 285       * Omit noarticletext if sharedupload; text will be fetched from the
 286       * shared upload server if possible.
 287       * @return string
 288       */
 289  	public function getContentObject() {
 290          $this->loadFile();
 291          if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) {
 292              return null;
 293          }
 294          return parent::getContentObject();
 295      }
 296  
 297  	protected function openShowImage() {
 298          global $wgEnableUploads, $wgSend404Code;
 299  
 300          $this->loadFile();
 301          $out = $this->getContext()->getOutput();
 302          $user = $this->getContext()->getUser();
 303          $lang = $this->getContext()->getLanguage();
 304          $dirmark = $lang->getDirMarkEntity();
 305          $request = $this->getContext()->getRequest();
 306  
 307          $max = $this->getImageLimitsFromOption( $user, 'imagesize' );
 308          $maxWidth = $max[0];
 309          $maxHeight = $max[1];
 310  
 311          if ( $this->displayImg->exists() ) {
 312              # image
 313              $page = $request->getIntOrNull( 'page' );
 314              if ( is_null( $page ) ) {
 315                  $params = array();
 316                  $page = 1;
 317              } else {
 318                  $params = array( 'page' => $page );
 319              }
 320  
 321              $renderLang = $request->getVal( 'lang' );
 322              if ( !is_null( $renderLang ) ) {
 323                  $handler = $this->displayImg->getHandler();
 324                  if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) {
 325                      $params['lang'] = $renderLang;
 326                  } else {
 327                      $renderLang = null;
 328                  }
 329              }
 330  
 331              $width_orig = $this->displayImg->getWidth( $page );
 332              $width = $width_orig;
 333              $height_orig = $this->displayImg->getHeight( $page );
 334              $height = $height_orig;
 335  
 336              $filename = wfEscapeWikiText( $this->displayImg->getName() );
 337              $linktext = $filename;
 338  
 339              wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) );
 340  
 341              if ( $this->displayImg->allowInlineDisplay() ) {
 342                  # image
 343                  # "Download high res version" link below the image
 344                  # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig,
 345                  #   Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped();
 346                  # We'll show a thumbnail of this image
 347                  if ( $width > $maxWidth || $height > $maxHeight || $this->displayImg->isVectorized() ) {
 348                      list( $width, $height ) = $this->getDisplayWidthHeight(
 349                          $maxWidth, $maxHeight, $width, $height
 350                      );
 351                      $linktext = wfMessage( 'show-big-image' )->escaped();
 352  
 353                      $thumbSizes = $this->getThumbSizes( $width, $height, $width_orig, $height_orig );
 354                      # Generate thumbnails or thumbnail links as needed...
 355                      $otherSizes = array();
 356                      foreach ( $thumbSizes as $size ) {
 357                          // We include a thumbnail size in the list, if it is
 358                          // less than or equal to the original size of the image
 359                          // asset ($width_orig/$height_orig). We also exclude
 360                          // the current thumbnail's size ($width/$height)
 361                          // since that is added to the message separately, so
 362                          // it can be denoted as the current size being shown.
 363                          // Vectorized images are "infinitely" big, so all thumb
 364                          // sizes are shown.
 365                          if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig )
 366                                  || $this->displayImg->isVectorized() )
 367                              && $size[0] != $width && $size[1] != $height
 368                          ) {
 369                              $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] );
 370                              if ( $sizeLink ) {
 371                                  $otherSizes[] = $sizeLink;
 372                              }
 373                          }
 374                      }
 375                      $otherSizes = array_unique( $otherSizes );
 376  
 377                      $msgsmall = '';
 378                      $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height );
 379                      if ( $sizeLinkBigImagePreview ) {
 380                          $msgsmall .= wfMessage( 'show-big-image-preview' )->
 381                              rawParams( $sizeLinkBigImagePreview )->
 382                              parse();
 383                      }
 384                      if ( count( $otherSizes ) ) {
 385                          $msgsmall .= ' ' .
 386                          Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ),
 387                              wfMessage( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )->
 388                              params( count( $otherSizes ) )->parse()
 389                          );
 390                      }
 391                  } elseif ( $width == 0 && $height == 0 ) {
 392                      # Some sort of audio file that doesn't have dimensions
 393                      # Don't output a no hi res message for such a file
 394                      $msgsmall = '';
 395                  } else {
 396                      # Image is small enough to show full size on image page
 397                      $msgsmall = wfMessage( 'file-nohires' )->parse();
 398                  }
 399  
 400                  $params['width'] = $width;
 401                  $params['height'] = $height;
 402                  $thumbnail = $this->displayImg->transform( $params );
 403                  Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params );
 404  
 405                  $anchorclose = Html::rawElement(
 406                      'div',
 407                      array( 'class' => 'mw-filepage-resolutioninfo' ),
 408                      $msgsmall
 409                  );
 410  
 411                  $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
 412                  if ( $isMulti ) {
 413                      $out->addModules( 'mediawiki.page.image.pagination' );
 414                      $out->addHTML( '<table class="multipageimage"><tr><td>' );
 415                  }
 416  
 417                  if ( $thumbnail ) {
 418                      $options = array(
 419                          'alt' => $this->displayImg->getTitle()->getPrefixedText(),
 420                          'file-link' => true,
 421                      );
 422                      $out->addHTML( '<div class="fullImageLink" id="file">' .
 423                          $thumbnail->toHtml( $options ) .
 424                          $anchorclose . "</div>\n" );
 425                  }
 426  
 427                  if ( $isMulti ) {
 428                      $count = $this->displayImg->pageCount();
 429  
 430                      if ( $page > 1 ) {
 431                          $label = $out->parse( wfMessage( 'imgmultipageprev' )->text(), false );
 432                          // on the client side, this link is generated in ajaxifyPageNavigation()
 433                          // in the mediawiki.page.image.pagination module
 434                          $link = Linker::linkKnown(
 435                              $this->getTitle(),
 436                              $label,
 437                              array(),
 438                              array( 'page' => $page - 1 )
 439                          );
 440                          $thumb1 = Linker::makeThumbLinkObj(
 441                              $this->getTitle(),
 442                              $this->displayImg,
 443                              $link,
 444                              $label,
 445                              'none',
 446                              array( 'page' => $page - 1 )
 447                          );
 448                      } else {
 449                          $thumb1 = '';
 450                      }
 451  
 452                      if ( $page < $count ) {
 453                          $label = wfMessage( 'imgmultipagenext' )->text();
 454                          $link = Linker::linkKnown(
 455                              $this->getTitle(),
 456                              $label,
 457                              array(),
 458                              array( 'page' => $page + 1 )
 459                          );
 460                          $thumb2 = Linker::makeThumbLinkObj(
 461                              $this->getTitle(),
 462                              $this->displayImg,
 463                              $link,
 464                              $label,
 465                              'none',
 466                              array( 'page' => $page + 1 )
 467                          );
 468                      } else {
 469                          $thumb2 = '';
 470                      }
 471  
 472                      global $wgScript;
 473  
 474                      $formParams = array(
 475                          'name' => 'pageselector',
 476                          'action' => $wgScript,
 477                      );
 478                      $options = array();
 479                      for ( $i = 1; $i <= $count; $i++ ) {
 480                          $options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page );
 481                      }
 482                      $select = Xml::tags( 'select',
 483                          array( 'id' => 'pageselector', 'name' => 'page' ),
 484                          implode( "\n", $options ) );
 485  
 486                      $out->addHTML(
 487                          '</td><td><div class="multipageimagenavbox">' .
 488                          Xml::openElement( 'form', $formParams ) .
 489                          Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) .
 490                              wfMessage( 'imgmultigoto' )->rawParams( $select )->parse() .
 491                          Xml::submitButton( wfMessage( 'imgmultigo' )->text() ) .
 492                          Xml::closeElement( 'form' ) .
 493                          "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>"
 494                      );
 495                  }
 496              } elseif ( $this->displayImg->isSafeFile() ) {
 497                  # if direct link is allowed but it's not a renderable image, show an icon.
 498                  $icon = $this->displayImg->iconThumb();
 499  
 500                  $out->addHTML( '<div class="fullImageLink" id="file">' .
 501                      $icon->toHtml( array( 'file-link' => true ) ) .
 502                      "</div>\n" );
 503              }
 504  
 505              $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text();
 506  
 507              $medialink = "[[Media:$filename|$linktext]]";
 508  
 509              if ( !$this->displayImg->isSafeFile() ) {
 510                  $warning = wfMessage( 'mediawarning' )->plain();
 511                  // dirmark is needed here to separate the file name, which
 512                  // most likely ends in Latin characters, from the description,
 513                  // which may begin with the file type. In RTL environment
 514                  // this will get messy.
 515                  // The dirmark, however, must not be immediately adjacent
 516                  // to the filename, because it can get copied with it.
 517                  // See bug 25277.
 518                  // @codingStandardsIgnoreStart Ignore long line
 519                  $out->addWikiText( <<<EOT
 520  <div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div>
 521  <div class="mediaWarning">$warning</div>
 522  EOT
 523                  );
 524                  // @codingStandardsIgnoreEnd
 525              } else {
 526                  $out->addWikiText( <<<EOT
 527  <div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span>
 528  </div>
 529  EOT
 530                  );
 531              }
 532  
 533              $renderLangOptions = $this->displayImg->getAvailableLanguages();
 534              if ( count( $renderLangOptions ) >= 1 ) {
 535                  $currentLanguage = $renderLang;
 536                  $defaultLang = $this->displayImg->getDefaultRenderLanguage();
 537                  if ( is_null( $currentLanguage ) ) {
 538                      $currentLanguage = $defaultLang;
 539                  }
 540                  $out->addHtml( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) );
 541              }
 542  
 543              // Add cannot animate thumbnail warning
 544              if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) {
 545                  // Include the extension so wiki admins can
 546                  // customize it on a per file-type basis
 547                  // (aka say things like use format X instead).
 548                  // additionally have a specific message for
 549                  // file-no-thumb-animation-gif
 550                  $ext = $this->displayImg->getExtension();
 551                  $noAnimMesg = wfMessageFallback(
 552                      'file-no-thumb-animation-' . $ext,
 553                      'file-no-thumb-animation'
 554                  )->plain();
 555  
 556                  $out->addWikiText( <<<EOT
 557  <div class="mw-noanimatethumb">{$noAnimMesg}</div>
 558  EOT
 559                  );
 560              }
 561  
 562              if ( !$this->displayImg->isLocal() ) {
 563                  $this->printSharedImageText();
 564              }
 565          } else {
 566              # Image does not exist
 567              if ( !$this->getID() ) {
 568                  # No article exists either
 569                  # Show deletion log to be consistent with normal articles
 570                  LogEventsList::showLogExtract(
 571                      $out,
 572                      array( 'delete', 'move' ),
 573                      $this->getTitle()->getPrefixedText(),
 574                      '',
 575                      array( 'lim' => 10,
 576                          'conds' => array( "log_action != 'revision'" ),
 577                          'showIfEmpty' => false,
 578                          'msgKey' => array( 'moveddeleted-notice' )
 579                      )
 580                  );
 581              }
 582  
 583              if ( $wgEnableUploads && $user->isAllowed( 'upload' ) ) {
 584                  // Only show an upload link if the user can upload
 585                  $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
 586                  $nofile = array(
 587                      'filepage-nofile-link',
 588                      $uploadTitle->getFullURL( array( 'wpDestFile' => $this->mPage->getFile()->getName() ) )
 589                  );
 590              } else {
 591                  $nofile = 'filepage-nofile';
 592              }
 593              // Note, if there is an image description page, but
 594              // no image, then this setRobotPolicy is overridden
 595              // by Article::View().
 596              $out->setRobotPolicy( 'noindex,nofollow' );
 597              $out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
 598              if ( !$this->getID() && $wgSend404Code ) {
 599                  // If there is no image, no shared image, and no description page,
 600                  // output a 404, to be consistent with articles.
 601                  $request->response()->header( 'HTTP/1.1 404 Not Found' );
 602              }
 603          }
 604          $out->setFileVersion( $this->displayImg );
 605      }
 606  
 607      /**
 608       * Creates an thumbnail of specified size and returns an HTML link to it
 609       * @param array $params Scaler parameters
 610       * @param int $width
 611       * @param int $height
 612       * @return string
 613       */
 614  	private function makeSizeLink( $params, $width, $height ) {
 615          $params['width'] = $width;
 616          $params['height'] = $height;
 617          $thumbnail = $this->displayImg->transform( $params );
 618          if ( $thumbnail && !$thumbnail->isError() ) {
 619              return Html::rawElement( 'a', array(
 620                  'href' => $thumbnail->getUrl(),
 621                  'class' => 'mw-thumbnail-link'
 622                  ), wfMessage( 'show-big-image-size' )->numParams(
 623                      $thumbnail->getWidth(), $thumbnail->getHeight()
 624                  )->parse() );
 625          } else {
 626              return '';
 627          }
 628      }
 629  
 630      /**
 631       * Show a notice that the file is from a shared repository
 632       */
 633  	protected function printSharedImageText() {
 634          $out = $this->getContext()->getOutput();
 635          $this->loadFile();
 636  
 637          $descUrl = $this->mPage->getFile()->getDescriptionUrl();
 638          $descText = $this->mPage->getFile()->getDescriptionText( $this->getContext()->getLanguage() );
 639  
 640          /* Add canonical to head if there is no local page for this shared file */
 641          if ( $descUrl && $this->mPage->getID() == 0 ) {
 642              $out->setCanonicalUrl( $descUrl );
 643          }
 644  
 645          $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
 646          $repo = $this->mPage->getFile()->getRepo()->getDisplayName();
 647  
 648          if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) {
 649              $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
 650          } elseif ( $descUrl && wfMessage( 'sharedupload-desc-there' )->plain() !== '-' ) {
 651              $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) );
 652          } else {
 653              $out->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ );
 654          }
 655  
 656          if ( $descText ) {
 657              $this->mExtraDescription = $descText;
 658          }
 659      }
 660  
 661  	public function getUploadUrl() {
 662          $this->loadFile();
 663          $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
 664          return $uploadTitle->getFullURL( array(
 665              'wpDestFile' => $this->mPage->getFile()->getName(),
 666              'wpForReUpload' => 1
 667          ) );
 668      }
 669  
 670      /**
 671       * Print out the various links at the bottom of the image page, e.g. reupload,
 672       * external editing (and instructions link) etc.
 673       */
 674  	protected function uploadLinksBox() {
 675          global $wgEnableUploads;
 676  
 677          if ( !$wgEnableUploads ) {
 678              return;
 679          }
 680  
 681          $this->loadFile();
 682          if ( !$this->mPage->getFile()->isLocal() ) {
 683              return;
 684          }
 685  
 686          $out = $this->getContext()->getOutput();
 687          $out->addHTML( "<ul>\n" );
 688  
 689          # "Upload a new version of this file" link
 690          $canUpload = $this->getTitle()->userCan( 'upload', $this->getContext()->getUser() );
 691          if ( $canUpload && UploadBase::userCanReUpload(
 692                  $this->getContext()->getUser(),
 693                  $this->mPage->getFile()->name )
 694          ) {
 695              $ulink = Linker::makeExternalLink(
 696                  $this->getUploadUrl(),
 697                  wfMessage( 'uploadnewversion-linktext' )->text()
 698              );
 699              $out->addHTML( "<li id=\"mw-imagepage-reupload-link\">"
 700                  . "<div class=\"plainlinks\">{$ulink}</div></li>\n" );
 701          } else {
 702              $out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">"
 703                  . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" );
 704          }
 705  
 706          $out->addHTML( "</ul>\n" );
 707      }
 708  
 709      /**
 710       * For overloading
 711       */
 712  	protected function closeShowImage() {
 713      }
 714  
 715      /**
 716       * If the page we've just displayed is in the "Image" namespace,
 717       * we follow it with an upload history of the image and its usage.
 718       */
 719  	protected function imageHistory() {
 720          $this->loadFile();
 721          $out = $this->getContext()->getOutput();
 722          $pager = new ImageHistoryPseudoPager( $this );
 723          $out->addHTML( $pager->getBody() );
 724          $out->preventClickjacking( $pager->getPreventClickjacking() );
 725  
 726          $this->mPage->getFile()->resetHistory(); // free db resources
 727  
 728          # Exist check because we don't want to show this on pages where an image
 729          # doesn't exist along with the noimage message, that would suck. -ævar
 730          if ( $this->mPage->getFile()->exists() ) {
 731              $this->uploadLinksBox();
 732          }
 733      }
 734  
 735      /**
 736       * @param string $target
 737       * @param int $limit
 738       * @return ResultWrapper
 739       */
 740  	protected function queryImageLinks( $target, $limit ) {
 741          $dbr = wfGetDB( DB_SLAVE );
 742  
 743          return $dbr->select(
 744              array( 'imagelinks', 'page' ),
 745              array( 'page_namespace', 'page_title', 'il_to' ),
 746              array( 'il_to' => $target, 'il_from = page_id' ),
 747              __METHOD__,
 748              array( 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', )
 749          );
 750      }
 751  
 752  	protected function imageLinks() {
 753          $limit = 100;
 754  
 755          $out = $this->getContext()->getOutput();
 756  
 757          $rows = array();
 758          $redirects = array();
 759          foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) {
 760              $redirects[$redir->getDBkey()] = array();
 761              $rows[] = (object)array(
 762                  'page_namespace' => NS_FILE,
 763                  'page_title' => $redir->getDBkey(),
 764              );
 765          }
 766  
 767          $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 );
 768          foreach ( $res as $row ) {
 769              $rows[] = $row;
 770          }
 771          $count = count( $rows );
 772  
 773          $hasMore = $count > $limit;
 774          if ( !$hasMore && count( $redirects ) ) {
 775              $res = $this->queryImageLinks( array_keys( $redirects ),
 776                  $limit - count( $rows ) + 1 );
 777              foreach ( $res as $row ) {
 778                  $redirects[$row->il_to][] = $row;
 779                  $count++;
 780              }
 781              $hasMore = ( $res->numRows() + count( $rows ) ) > $limit;
 782          }
 783  
 784          if ( $count == 0 ) {
 785              $out->wrapWikiMsg(
 786                  Html::rawElement( 'div',
 787                      array( 'id' => 'mw-imagepage-nolinkstoimage' ), "\n$1\n" ),
 788                  'nolinkstoimage'
 789              );
 790              return;
 791          }
 792  
 793          $out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
 794          if ( !$hasMore ) {
 795              $out->addWikiMsg( 'linkstoimage', $count );
 796          } else {
 797              // More links than the limit. Add a link to [[Special:Whatlinkshere]]
 798              $out->addWikiMsg( 'linkstoimage-more',
 799                  $this->getContext()->getLanguage()->formatNum( $limit ),
 800                  $this->getTitle()->getPrefixedDBkey()
 801              );
 802          }
 803  
 804          $out->addHTML(
 805              Html::openElement( 'ul',
 806                  array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n"
 807          );
 808          $count = 0;
 809  
 810          // Sort the list by namespace:title
 811          usort( $rows, array( $this, 'compare' ) );
 812  
 813          // Create links for every element
 814          $currentCount = 0;
 815          foreach ( $rows as $element ) {
 816              $currentCount++;
 817              if ( $currentCount > $limit ) {
 818                  break;
 819              }
 820  
 821              $query = array();
 822              # Add a redirect=no to make redirect pages reachable
 823              if ( isset( $redirects[$element->page_title] ) ) {
 824                  $query['redirect'] = 'no';
 825              }
 826              $link = Linker::linkKnown(
 827                  Title::makeTitle( $element->page_namespace, $element->page_title ),
 828                  null, array(), $query
 829              );
 830              if ( !isset( $redirects[$element->page_title] ) ) {
 831                  # No redirects
 832                  $liContents = $link;
 833              } elseif ( count( $redirects[$element->page_title] ) === 0 ) {
 834                  # Redirect without usages
 835                  $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse();
 836              } else {
 837                  # Redirect with usages
 838                  $li = '';
 839                  foreach ( $redirects[$element->page_title] as $row ) {
 840                      $currentCount++;
 841                      if ( $currentCount > $limit ) {
 842                          break;
 843                      }
 844  
 845                      $link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) );
 846                      $li .= Html::rawElement(
 847                          'li',
 848                          array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
 849                          $link2
 850                          ) . "\n";
 851                  }
 852  
 853                  $ul = Html::rawElement(
 854                      'ul',
 855                      array( 'class' => 'mw-imagepage-redirectstofile' ),
 856                      $li
 857                      ) . "\n";
 858                  $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams(
 859                      $link, $ul )->parse();
 860              }
 861              $out->addHTML( Html::rawElement(
 862                      'li',
 863                      array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ),
 864                      $liContents
 865                  ) . "\n"
 866              );
 867  
 868          };
 869          $out->addHTML( Html::closeElement( 'ul' ) . "\n" );
 870          $res->free();
 871  
 872          // Add a links to [[Special:Whatlinkshere]]
 873          if ( $count > $limit ) {
 874              $out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() );
 875          }
 876          $out->addHTML( Html::closeElement( 'div' ) . "\n" );
 877      }
 878  
 879  	protected function imageDupes() {
 880          $this->loadFile();
 881          $out = $this->getContext()->getOutput();
 882  
 883          $dupes = $this->mPage->getDuplicates();
 884          if ( count( $dupes ) == 0 ) {
 885              return;
 886          }
 887  
 888          $out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
 889          $out->addWikiMsg( 'duplicatesoffile',
 890              $this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey()
 891          );
 892          $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
 893  
 894          /**
 895           * @var $file File
 896           */
 897          foreach ( $dupes as $file ) {
 898              $fromSrc = '';
 899              if ( $file->isLocal() ) {
 900                  $link = Linker::linkKnown( $file->getTitle() );
 901              } else {
 902                  $link = Linker::makeExternalLink( $file->getDescriptionUrl(),
 903                      $file->getTitle()->getPrefixedText() );
 904                  $fromSrc = wfMessage( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text();
 905              }
 906              $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
 907          }
 908          $out->addHTML( "</ul></div>\n" );
 909      }
 910  
 911      /**
 912       * Delete the file, or an earlier version of it
 913       */
 914  	public function delete() {
 915          $file = $this->mPage->getFile();
 916          if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) {
 917              // Standard article deletion
 918              parent::delete();
 919              return;
 920          }
 921  
 922          $deleter = new FileDeleteForm( $file );
 923          $deleter->execute();
 924      }
 925  
 926      /**
 927       * Display an error with a wikitext description
 928       *
 929       * @param string $description
 930       */
 931  	function showError( $description ) {
 932          $out = $this->getContext()->getOutput();
 933          $out->setPageTitle( wfMessage( 'internalerror' ) );
 934          $out->setRobotPolicy( 'noindex,nofollow' );
 935          $out->setArticleRelated( false );
 936          $out->enableClientCache( false );
 937          $out->addWikiText( $description );
 938      }
 939  
 940      /**
 941       * Callback for usort() to do link sorts by (namespace, title)
 942       * Function copied from Title::compare()
 943       *
 944       * @param object $a Object page to compare with
 945       * @param object $b Object page to compare with
 946       * @return int Result of string comparison, or namespace comparison
 947       */
 948  	protected function compare( $a, $b ) {
 949          if ( $a->page_namespace == $b->page_namespace ) {
 950              return strcmp( $a->page_title, $b->page_title );
 951          } else {
 952              return $a->page_namespace - $b->page_namespace;
 953          }
 954      }
 955  
 956      /**
 957       * Returns the corresponding $wgImageLimits entry for the selected user option
 958       *
 959       * @param User $user
 960       * @param string $optionName Name of a option to check, typically imagesize or thumbsize
 961       * @return array
 962       * @since 1.21
 963       */
 964  	public function getImageLimitsFromOption( $user, $optionName ) {
 965          global $wgImageLimits;
 966  
 967          $option = $user->getIntOption( $optionName );
 968          if ( !isset( $wgImageLimits[$option] ) ) {
 969              $option = User::getDefaultOption( $optionName );
 970          }
 971  
 972          // The user offset might still be incorrect, specially if
 973          // $wgImageLimits got changed (see bug #8858).
 974          if ( !isset( $wgImageLimits[$option] ) ) {
 975              // Default to the first offset in $wgImageLimits
 976              $option = 0;
 977          }
 978  
 979          return isset( $wgImageLimits[$option] )
 980              ? $wgImageLimits[$option]
 981              : array( 800, 600 ); // if nothing is set, fallback to a hardcoded default
 982      }
 983  
 984      /**
 985       * Output a drop-down box for language options for the file
 986       *
 987       * @param array $langChoices Array of string language codes
 988       * @param string $curLang Language code file is being viewed in.
 989       * @param string $defaultLang Language code that image is rendered in by default
 990       * @return string HTML to insert underneath image.
 991       */
 992  	protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) {
 993          global $wgScript;
 994          sort( $langChoices );
 995          $curLang = wfBCP47( $curLang );
 996          $defaultLang = wfBCP47( $defaultLang );
 997          $opts = '';
 998          $haveCurrentLang = false;
 999          $haveDefaultLang = false;
1000  
1001          // We make a list of all the language choices in the file.
1002          // Additionally if the default language to render this file
1003          // is not included as being in this file (for example, in svgs
1004          // usually the fallback content is the english content) also
1005          // include a choice for that. Last of all, if we're viewing
1006          // the file in a language not on the list, add it as a choice.
1007          foreach ( $langChoices as $lang ) {
1008              $code = wfBCP47( $lang );
1009              $name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() );
1010              if ( $name !== '' ) {
1011                  $display = wfMessage( 'img-lang-opt', $code, $name )->text();
1012              } else {
1013                  $display = $code;
1014              }
1015              $opts .= "\n" . Xml::option( $display, $code, $curLang === $code );
1016              if ( $curLang === $code ) {
1017                  $haveCurrentLang = true;
1018              }
1019              if ( $defaultLang === $code ) {
1020                  $haveDefaultLang = true;
1021              }
1022          }
1023          if ( !$haveDefaultLang ) {
1024              // Its hard to know if the content is really in the default language, or
1025              // if its just unmarked content that could be in any language.
1026              $opts = Xml::option(
1027                  wfMessage( 'img-lang-default' )->text(),
1028                  $defaultLang,
1029                  $defaultLang === $curLang
1030              ) . $opts;
1031          }
1032          if ( !$haveCurrentLang && $defaultLang !== $curLang ) {
1033              $name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() );
1034              if ( $name !== '' ) {
1035                  $display = wfMessage( 'img-lang-opt', $curLang, $name )->text();
1036              } else {
1037                  $display = $curLang;
1038              }
1039              $opts = Xml::option( $display, $curLang, true ) . $opts;
1040          }
1041  
1042          $select = Html::rawElement(
1043              'select',
1044              array( 'id' => 'mw-imglangselector', 'name' => 'lang' ),
1045              $opts
1046          );
1047          $submit = Xml::submitButton( wfMessage( 'img-lang-go' )->text() );
1048  
1049          $formContents = wfMessage( 'img-lang-info' )->rawParams( $select, $submit )->parse()
1050              . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() );
1051  
1052          $langSelectLine = Html::rawElement( 'div', array( 'id' => 'mw-imglangselector-line' ),
1053              Html::rawElement( 'form', array( 'action' => $wgScript ), $formContents )
1054          );
1055          return $langSelectLine;
1056      }
1057  
1058      /**
1059       * Get the width and height to display image at.
1060       *
1061       * @note This method assumes that it is only called if one
1062       *  of the dimensions are bigger than the max, or if the
1063       *  image is vectorized.
1064       *
1065       * @param int $maxWidth Max width to display at
1066       * @param int $maxHeight Max height to display at
1067       * @param int $width Actual width of the image
1068       * @param int $height Actual height of the image
1069       * @throws MWException
1070       * @return array Array (width, height)
1071       */
1072  	protected function getDisplayWidthHeight( $maxWidth, $maxHeight, $width, $height ) {
1073          if ( !$maxWidth || !$maxHeight ) {
1074              // should never happen
1075              throw new MWException( 'Using a choice from $wgImageLimits that is 0x0' );
1076          }
1077  
1078          if ( !$width || !$height ) {
1079              return array( 0, 0 );
1080          }
1081  
1082          # Calculate the thumbnail size.
1083          if ( $width <= $maxWidth && $height <= $maxHeight ) {
1084              // Vectorized image, do nothing.
1085          } elseif ( $width / $height >= $maxWidth / $maxHeight ) {
1086              # The limiting factor is the width, not the height.
1087              $height = round( $height * $maxWidth / $width );
1088              $width = $maxWidth;
1089              # Note that $height <= $maxHeight now.
1090          } else {
1091              $newwidth = floor( $width * $maxHeight / $height );
1092              $height = round( $height * $newwidth / $width );
1093              $width = $newwidth;
1094              # Note that $height <= $maxHeight now, but might not be identical
1095              # because of rounding.
1096          }
1097          return array( $width, $height );
1098      }
1099  
1100      /**
1101       * Get alternative thumbnail sizes.
1102       *
1103       * @note This will only list several alternatives if thumbnails are rendered on 404
1104       * @param int $origWidth Actual width of image
1105       * @param int $origHeight Actual height of image
1106       * @return array An array of [width, height] pairs.
1107       */
1108  	protected function getThumbSizes( $origWidth, $origHeight ) {
1109          global $wgImageLimits;
1110          if ( $this->displayImg->getRepo()->canTransformVia404() ) {
1111              $thumbSizes = $wgImageLimits;
1112              // Also include the full sized resolution in the list, so
1113              // that users know they can get it. This will link to the
1114              // original file asset if mustRender() === false. In the case
1115              // that we mustRender, some users have indicated that they would
1116              // find it useful to have the full size image in the rendered
1117              // image format.
1118              $thumbSizes[] = array( $origWidth, $origHeight );
1119          } else {
1120              # Creating thumb links triggers thumbnail generation.
1121              # Just generate the thumb for the current users prefs.
1122              $thumbSizes = array( $this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' ) );
1123              if ( !$this->displayImg->mustRender() ) {
1124                  // We can safely include a link to the "full-size" preview,
1125                  // without actually rendering.
1126                  $thumbSizes[] = array( $origWidth, $origHeight );
1127              }
1128          }
1129          return $thumbSizes;
1130      }
1131  
1132  }
1133  
1134  /**
1135   * Builds the image revision log shown on image pages
1136   *
1137   * @ingroup Media
1138   */
1139  class ImageHistoryList extends ContextSource {
1140  
1141      /**
1142       * @var Title
1143       */
1144      protected $title;
1145  
1146      /**
1147       * @var File
1148       */
1149      protected $img;
1150  
1151      /**
1152       * @var ImagePage
1153       */
1154      protected $imagePage;
1155  
1156      /**
1157       * @var File
1158       */
1159      protected $current;
1160  
1161      protected $repo, $showThumb;
1162      protected $preventClickjacking = false;
1163  
1164      /**
1165       * @param ImagePage $imagePage
1166       */
1167  	public function __construct( $imagePage ) {
1168          global $wgShowArchiveThumbnails;
1169          $this->current = $imagePage->getFile();
1170          $this->img = $imagePage->getDisplayedFile();
1171          $this->title = $imagePage->getTitle();
1172          $this->imagePage = $imagePage;
1173          $this->showThumb = $wgShowArchiveThumbnails && $this->img->canRender();
1174          $this->setContext( $imagePage->getContext() );
1175      }
1176  
1177      /**
1178       * @return ImagePage
1179       */
1180  	public function getImagePage() {
1181          return $this->imagePage;
1182      }
1183  
1184      /**
1185       * @return File
1186       */
1187  	public function getFile() {
1188          return $this->img;
1189      }
1190  
1191      /**
1192       * @param string $navLinks
1193       * @return string
1194       */
1195  	public function beginImageHistoryList( $navLinks = '' ) {
1196          return Xml::element( 'h2', array( 'id' => 'filehistory' ), $this->msg( 'filehist' )->text() )
1197              . "\n"
1198              . "<div id=\"mw-imagepage-section-filehistory\">\n"
1199              . $this->msg( 'filehist-help' )->parseAsBlock()
1200              . $navLinks . "\n"
1201              . Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n"
1202              . '<tr><td></td>'
1203              . ( $this->current->isLocal()
1204                  && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' )
1205              . '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>'
1206              . ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' )
1207              . '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>'
1208              . '<th>' . $this->msg( 'filehist-user' )->escaped() . '</th>'
1209              . '<th>' . $this->msg( 'filehist-comment' )->escaped() . '</th>'
1210              . "</tr>\n";
1211      }
1212  
1213      /**
1214       * @param string $navLinks
1215       * @return string
1216       */
1217  	public function endImageHistoryList( $navLinks = '' ) {
1218          return "</table>\n$navLinks\n</div>\n";
1219      }
1220  
1221      /**
1222       * @param bool $iscur
1223       * @param File $file
1224       * @return string
1225       */
1226  	public function imageHistoryLine( $iscur, $file ) {
1227          global $wgContLang;
1228  
1229          $user = $this->getUser();
1230          $lang = $this->getLanguage();
1231          $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
1232          $img = $iscur ? $file->getName() : $file->getArchiveName();
1233          $userId = $file->getUser( 'id' );
1234          $userText = $file->getUser( 'text' );
1235          $description = $file->getDescription( File::FOR_THIS_USER, $user );
1236  
1237          $local = $this->current->isLocal();
1238          $row = $selected = '';
1239  
1240          // Deletion link
1241          if ( $local && ( $user->isAllowedAny( 'delete', 'deletedhistory' ) ) ) {
1242              $row .= '<td>';
1243              # Link to remove from history
1244              if ( $user->isAllowed( 'delete' ) ) {
1245                  $q = array( 'action' => 'delete' );
1246                  if ( !$iscur ) {
1247                      $q['oldimage'] = $img;
1248                  }
1249                  $row .= Linker::linkKnown(
1250                      $this->title,
1251                      $this->msg( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' )->escaped(),
1252                      array(), $q
1253                  );
1254              }
1255              # Link to hide content. Don't show useless link to people who cannot hide revisions.
1256              $canHide = $user->isAllowed( 'deleterevision' );
1257              if ( $canHide || ( $user->isAllowed( 'deletedhistory' ) && $file->getVisibility() ) ) {
1258                  if ( $user->isAllowed( 'delete' ) ) {
1259                      $row .= '<br />';
1260                  }
1261                  // If file is top revision or locked from this user, don't link
1262                  if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED, $user ) ) {
1263                      $del = Linker::revDeleteLinkDisabled( $canHide );
1264                  } else {
1265                      list( $ts, ) = explode( '!', $img, 2 );
1266                      $query = array(
1267                          'type' => 'oldimage',
1268                          'target' => $this->title->getPrefixedText(),
1269                          'ids' => $ts,
1270                      );
1271                      $del = Linker::revDeleteLink( $query,
1272                          $file->isDeleted( File::DELETED_RESTRICTED ), $canHide );
1273                  }
1274                  $row .= $del;
1275              }
1276              $row .= '</td>';
1277          }
1278  
1279          // Reversion link/current indicator
1280          $row .= '<td>';
1281          if ( $iscur ) {
1282              $row .= $this->msg( 'filehist-current' )->escaped();
1283          } elseif ( $local && $this->title->quickUserCan( 'edit', $user )
1284              && $this->title->quickUserCan( 'upload', $user )
1285          ) {
1286              if ( $file->isDeleted( File::DELETED_FILE ) ) {
1287                  $row .= $this->msg( 'filehist-revert' )->escaped();
1288              } else {
1289                  $row .= Linker::linkKnown(
1290                      $this->title,
1291                      $this->msg( 'filehist-revert' )->escaped(),
1292                      array(),
1293                      array(
1294                          'action' => 'revert',
1295                          'oldimage' => $img,
1296                          'wpEditToken' => $user->getEditToken( $img )
1297                      )
1298                  );
1299              }
1300          }
1301          $row .= '</td>';
1302  
1303          // Date/time and image link
1304          if ( $file->getTimestamp() === $this->img->getTimestamp() ) {
1305              $selected = "class='filehistory-selected'";
1306          }
1307          $row .= "<td $selected style='white-space: nowrap;'>";
1308          if ( !$file->userCan( File::DELETED_FILE, $user ) ) {
1309              # Don't link to unviewable files
1310              $row .= '<span class="history-deleted">'
1311                  . $lang->userTimeAndDate( $timestamp, $user ) . '</span>';
1312          } elseif ( $file->isDeleted( File::DELETED_FILE ) ) {
1313              if ( $local ) {
1314                  $this->preventClickjacking();
1315                  $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
1316                  # Make a link to review the image
1317                  $url = Linker::linkKnown(
1318                      $revdel,
1319                      $lang->userTimeAndDate( $timestamp, $user ),
1320                      array(),
1321                      array(
1322                          'target' => $this->title->getPrefixedText(),
1323                          'file' => $img,
1324                          'token' => $user->getEditToken( $img )
1325                      )
1326                  );
1327              } else {
1328                  $url = $lang->userTimeAndDate( $timestamp, $user );
1329              }
1330              $row .= '<span class="history-deleted">' . $url . '</span>';
1331          } elseif ( !$file->exists() ) {
1332              $row .= '<span class="mw-file-missing">'
1333                  . $lang->userTimeAndDate( $timestamp, $user ) . '</span>';
1334          } else {
1335              $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
1336              $row .= Xml::element(
1337                  'a',
1338                  array( 'href' => $url ),
1339                  $lang->userTimeAndDate( $timestamp, $user )
1340              );
1341          }
1342          $row .= "</td>";
1343  
1344          // Thumbnail
1345          if ( $this->showThumb ) {
1346              $row .= '<td>' . $this->getThumbForLine( $file ) . '</td>';
1347          }
1348  
1349          // Image dimensions + size
1350          $row .= '<td>';
1351          $row .= htmlspecialchars( $file->getDimensionsString() );
1352          $row .= $this->msg( 'word-separator' )->escaped();
1353          $row .= '<span style="white-space: nowrap;">';
1354          $row .= $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->escaped();
1355          $row .= '</span>';
1356          $row .= '</td>';
1357  
1358          // Uploading user
1359          $row .= '<td>';
1360          // Hide deleted usernames
1361          if ( $file->isDeleted( File::DELETED_USER ) ) {
1362              $row .= '<span class="history-deleted">'
1363                  . $this->msg( 'rev-deleted-user' )->escaped() . '</span>';
1364          } else {
1365              if ( $local ) {
1366                  $row .= Linker::userLink( $userId, $userText );
1367                  $row .= $this->msg( 'word-separator' )->escaped();
1368                  $row .= '<span style="white-space: nowrap;">';
1369                  $row .= Linker::userToolLinks( $userId, $userText );
1370                  $row .= '</span>';
1371              } else {
1372                  $row .= htmlspecialchars( $userText );
1373              }
1374          }
1375          $row .= '</td>';
1376  
1377          // Don't show deleted descriptions
1378          if ( $file->isDeleted( File::DELETED_COMMENT ) ) {
1379              $row .= '<td><span class="history-deleted">' .
1380                  $this->msg( 'rev-deleted-comment' )->escaped() . '</span></td>';
1381          } else {
1382              $row .= '<td dir="' . $wgContLang->getDir() . '">' .
1383                  Linker::formatComment( $description, $this->title ) . '</td>';
1384          }
1385  
1386          $rowClass = null;
1387          wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
1388          $classAttr = $rowClass ? " class='$rowClass'" : '';
1389  
1390          return "<tr{$classAttr}>{$row}</tr>\n";
1391      }
1392  
1393      /**
1394       * @param File $file
1395       * @return string
1396       */
1397  	protected function getThumbForLine( $file ) {
1398          $lang = $this->getLanguage();
1399          $user = $this->getUser();
1400          if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE, $user )
1401              && !$file->isDeleted( File::DELETED_FILE )
1402          ) {
1403              $params = array(
1404                  'width' => '120',
1405                  'height' => '120',
1406              );
1407              $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() );
1408  
1409              $thumbnail = $file->transform( $params );
1410              $options = array(
1411                  'alt' => $this->msg( 'filehist-thumbtext',
1412                      $lang->userTimeAndDate( $timestamp, $user ),
1413                      $lang->userDate( $timestamp, $user ),
1414                      $lang->userTime( $timestamp, $user ) )->text(),
1415                  'file-link' => true,
1416              );
1417  
1418              if ( !$thumbnail ) {
1419                  return $this->msg( 'filehist-nothumb' )->escaped();
1420              }
1421  
1422              return $thumbnail->toHtml( $options );
1423          } else {
1424              return $this->msg( 'filehist-nothumb' )->escaped();
1425          }
1426      }
1427  
1428      /**
1429       * @param bool $enable
1430       */
1431  	protected function preventClickjacking( $enable = true ) {
1432          $this->preventClickjacking = $enable;
1433      }
1434  
1435      /**
1436       * @return bool
1437       */
1438  	public function getPreventClickjacking() {
1439          return $this->preventClickjacking;
1440      }
1441  }
1442  
1443  class ImageHistoryPseudoPager extends ReverseChronologicalPager {
1444      protected $preventClickjacking = false;
1445  
1446      /**
1447       * @var File
1448       */
1449      protected $mImg;
1450  
1451      /**
1452       * @var Title
1453       */
1454      protected $mTitle;
1455  
1456      /**
1457       * @param ImagePage $imagePage
1458       */
1459  	function __construct( $imagePage ) {
1460          parent::__construct( $imagePage->getContext() );
1461          $this->mImagePage = $imagePage;
1462          $this->mTitle = clone ( $imagePage->getTitle() );
1463          $this->mTitle->setFragment( '#filehistory' );
1464          $this->mImg = null;
1465          $this->mHist = array();
1466          $this->mRange = array( 0, 0 ); // display range
1467      }
1468  
1469      /**
1470       * @return Title
1471       */
1472  	function getTitle() {
1473          return $this->mTitle;
1474      }
1475  
1476  	function getQueryInfo() {
1477          return false;
1478      }
1479  
1480      /**
1481       * @return string
1482       */
1483  	function getIndexField() {
1484          return '';
1485      }
1486  
1487      /**
1488       * @param object $row
1489       * @return string
1490       */
1491  	function formatRow( $row ) {
1492          return '';
1493      }
1494  
1495      /**
1496       * @return string
1497       */
1498  	function getBody() {
1499          $s = '';
1500          $this->doQuery();
1501          if ( count( $this->mHist ) ) {
1502              $list = new ImageHistoryList( $this->mImagePage );
1503              # Generate prev/next links
1504              $navLink = $this->getNavigationBar();
1505              $s = $list->beginImageHistoryList( $navLink );
1506              // Skip rows there just for paging links
1507              for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
1508                  $file = $this->mHist[$i];
1509                  $s .= $list->imageHistoryLine( !$file->isOld(), $file );
1510              }
1511              $s .= $list->endImageHistoryList( $navLink );
1512  
1513              if ( $list->getPreventClickjacking() ) {
1514                  $this->preventClickjacking();
1515              }
1516          }
1517          return $s;
1518      }
1519  
1520  	function doQuery() {
1521          if ( $this->mQueryDone ) {
1522              return;
1523          }
1524          $this->mImg = $this->mImagePage->getFile(); // ensure loading
1525          if ( !$this->mImg->exists() ) {
1526              return;
1527          }
1528          $queryLimit = $this->mLimit + 1; // limit plus extra row
1529          if ( $this->mIsBackwards ) {
1530              // Fetch the file history
1531              $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false );
1532              // The current rev may not meet the offset/limit
1533              $numRows = count( $this->mHist );
1534              if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
1535                  $this->mHist = array_merge( array( $this->mImg ), $this->mHist );
1536              }
1537          } else {
1538              // The current rev may not meet the offset
1539              if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
1540                  $this->mHist[] = $this->mImg;
1541              }
1542              // Old image versions (fetch extra row for nav links)
1543              $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1;
1544              // Fetch the file history
1545              $this->mHist = array_merge( $this->mHist,
1546                  $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) );
1547          }
1548          $numRows = count( $this->mHist ); // Total number of query results
1549          if ( $numRows ) {
1550              # Index value of top item in the list
1551              $firstIndex = $this->mIsBackwards ?
1552                  $this->mHist[$numRows - 1]->getTimestamp() : $this->mHist[0]->getTimestamp();
1553              # Discard the extra result row if there is one
1554              if ( $numRows > $this->mLimit && $numRows > 1 ) {
1555                  if ( $this->mIsBackwards ) {
1556                      # Index value of item past the index
1557                      $this->mPastTheEndIndex = $this->mHist[0]->getTimestamp();
1558                      # Index value of bottom item in the list
1559                      $lastIndex = $this->mHist[1]->getTimestamp();
1560                      # Display range
1561                      $this->mRange = array( 1, $numRows - 1 );
1562                  } else {
1563                      # Index value of item past the index
1564                      $this->mPastTheEndIndex = $this->mHist[$numRows - 1]->getTimestamp();
1565                      # Index value of bottom item in the list
1566                      $lastIndex = $this->mHist[$numRows - 2]->getTimestamp();
1567                      # Display range
1568                      $this->mRange = array( 0, $numRows - 2 );
1569                  }
1570              } else {
1571                  # Setting indexes to an empty string means that they will be
1572                  # omitted if they would otherwise appear in URLs. It just so
1573                  # happens that this  is the right thing to do in the standard
1574                  # UI, in all the relevant cases.
1575                  $this->mPastTheEndIndex = '';
1576                  # Index value of bottom item in the list
1577                  $lastIndex = $this->mIsBackwards ?
1578                      $this->mHist[0]->getTimestamp() : $this->mHist[$numRows - 1]->getTimestamp();
1579                  # Display range
1580                  $this->mRange = array( 0, $numRows - 1 );
1581              }
1582          } else {
1583              $firstIndex = '';
1584              $lastIndex = '';
1585              $this->mPastTheEndIndex = '';
1586          }
1587          if ( $this->mIsBackwards ) {
1588              $this->mIsFirst = ( $numRows < $queryLimit );
1589              $this->mIsLast = ( $this->mOffset == '' );
1590              $this->mLastShown = $firstIndex;
1591              $this->mFirstShown = $lastIndex;
1592          } else {
1593              $this->mIsFirst = ( $this->mOffset == '' );
1594              $this->mIsLast = ( $numRows < $queryLimit );
1595              $this->mLastShown = $lastIndex;
1596              $this->mFirstShown = $firstIndex;
1597          }
1598          $this->mQueryDone = true;
1599      }
1600  
1601      /**
1602       * @param bool $enable
1603       */
1604  	protected function preventClickjacking( $enable = true ) {
1605          $this->preventClickjacking = $enable;
1606      }
1607  
1608      /**
1609       * @return bool
1610       */
1611  	public function getPreventClickjacking() {
1612          return $this->preventClickjacking;
1613      }
1614  
1615  }


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