[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |