MediaWiki
REL1_24
|
00001 <?php 00028 class ImagePage extends Article { 00030 private $displayImg; 00031 00033 private $repo; 00034 00036 private $fileLoaded; 00037 00039 protected $mExtraDescription = false; 00040 00045 protected function newPage( Title $title ) { 00046 // Overload mPage with a file-specific page 00047 return new WikiFilePage( $title ); 00048 } 00049 00055 public static function newFromID( $id ) { 00056 $t = Title::newFromID( $id ); 00057 # @todo FIXME: Doesn't inherit right 00058 return $t == null ? null : new self( $t ); 00059 # return $t == null ? null : new static( $t ); // PHP 5.3 00060 } 00061 00066 public function setFile( $file ) { 00067 $this->mPage->setFile( $file ); 00068 $this->displayImg = $file; 00069 $this->fileLoaded = true; 00070 } 00071 00072 protected function loadFile() { 00073 if ( $this->fileLoaded ) { 00074 return; 00075 } 00076 $this->fileLoaded = true; 00077 00078 $this->displayImg = $img = false; 00079 wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) ); 00080 if ( !$img ) { // not set by hook? 00081 $img = wfFindFile( $this->getTitle() ); 00082 if ( !$img ) { 00083 $img = wfLocalFile( $this->getTitle() ); 00084 } 00085 } 00086 $this->mPage->setFile( $img ); 00087 if ( !$this->displayImg ) { // not set by hook? 00088 $this->displayImg = $img; 00089 } 00090 $this->repo = $img->getRepo(); 00091 } 00092 00097 public function render() { 00098 $this->getContext()->getOutput()->setArticleBodyOnly( true ); 00099 parent::view(); 00100 } 00101 00102 public function view() { 00103 global $wgShowEXIF; 00104 00105 $out = $this->getContext()->getOutput(); 00106 $request = $this->getContext()->getRequest(); 00107 $diff = $request->getVal( 'diff' ); 00108 $diffOnly = $request->getBool( 00109 'diffonly', 00110 $this->getContext()->getUser()->getOption( 'diffonly' ) 00111 ); 00112 00113 if ( $this->getTitle()->getNamespace() != NS_FILE || ( $diff !== null && $diffOnly ) ) { 00114 parent::view(); 00115 return; 00116 } 00117 00118 $this->loadFile(); 00119 00120 if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) { 00121 if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || $diff !== null ) { 00122 // mTitle is the same as the redirect target so ask Article 00123 // to perform the redirect for us. 00124 $request->setVal( 'diffonly', 'true' ); 00125 parent::view(); 00126 return; 00127 } else { 00128 // mTitle is not the same as the redirect target so it is 00129 // probably the redirect page itself. Fake the redirect symbol 00130 $out->setPageTitle( $this->getTitle()->getPrefixedText() ); 00131 $out->addHTML( $this->viewRedirect( 00132 Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ), 00133 /* $appendSubtitle */ true, 00134 /* $forceKnown */ true ) 00135 ); 00136 $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() ); 00137 return; 00138 } 00139 } 00140 00141 if ( $wgShowEXIF && $this->displayImg->exists() ) { 00142 // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata(). 00143 $formattedMetadata = $this->displayImg->formatMetadata(); 00144 $showmeta = $formattedMetadata !== false; 00145 } else { 00146 $showmeta = false; 00147 } 00148 00149 if ( !$diff && $this->displayImg->exists() ) { 00150 $out->addHTML( $this->showTOC( $showmeta ) ); 00151 } 00152 00153 if ( !$diff ) { 00154 $this->openShowImage(); 00155 } 00156 00157 # No need to display noarticletext, we use our own message, output in openShowImage() 00158 if ( $this->mPage->getID() ) { 00159 # NS_FILE is in the user language, but this section (the actual wikitext) 00160 # should be in page content language 00161 $pageLang = $this->getTitle()->getPageViewLanguage(); 00162 $out->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content', 00163 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(), 00164 'class' => 'mw-content-' . $pageLang->getDir() ) ) ); 00165 00166 parent::view(); 00167 00168 $out->addHTML( Xml::closeElement( 'div' ) ); 00169 } else { 00170 # Just need to set the right headers 00171 $out->setArticleFlag( true ); 00172 $out->setPageTitle( $this->getTitle()->getPrefixedText() ); 00173 $this->mPage->doViewUpdates( $this->getContext()->getUser(), $this->getOldID() ); 00174 } 00175 00176 # Show shared description, if needed 00177 if ( $this->mExtraDescription ) { 00178 $fol = wfMessage( 'shareddescriptionfollows' ); 00179 if ( !$fol->isDisabled() ) { 00180 $out->addWikiText( $fol->plain() ); 00181 } 00182 $out->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" ); 00183 } 00184 00185 $this->closeShowImage(); 00186 $this->imageHistory(); 00187 // TODO: Cleanup the following 00188 00189 $out->addHTML( Xml::element( 'h2', 00190 array( 'id' => 'filelinks' ), 00191 wfMessage( 'imagelinks' )->text() ) . "\n" ); 00192 $this->imageDupes(); 00193 # @todo FIXME: For some freaky reason, we can't redirect to foreign images. 00194 # Yet we return metadata about the target. Definitely an issue in the FileRepo 00195 $this->imageLinks(); 00196 00197 # Allow extensions to add something after the image links 00198 $html = ''; 00199 wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) ); 00200 if ( $html ) { 00201 $out->addHTML( $html ); 00202 } 00203 00204 if ( $showmeta ) { 00205 $out->addHTML( Xml::element( 00206 'h2', 00207 array( 'id' => 'metadata' ), 00208 wfMessage( 'metadata' )->text() ) . "\n" ); 00209 $out->addWikiText( $this->makeMetadataTable( $formattedMetadata ) ); 00210 $out->addModules( array( 'mediawiki.action.view.metadata' ) ); 00211 } 00212 00213 // Add remote Filepage.css 00214 if ( !$this->repo->isLocal() ) { 00215 $css = $this->repo->getDescriptionStylesheetUrl(); 00216 if ( $css ) { 00217 $out->addStyle( $css ); 00218 } 00219 } 00220 // always show the local local Filepage.css, bug 29277 00221 $out->addModuleStyles( 'filepage' ); 00222 } 00223 00227 public function getDisplayedFile() { 00228 $this->loadFile(); 00229 return $this->displayImg; 00230 } 00231 00238 protected function showTOC( $metadata ) { 00239 $r = array( 00240 '<li><a href="#file">' . wfMessage( 'file-anchor-link' )->escaped() . '</a></li>', 00241 '<li><a href="#filehistory">' . wfMessage( 'filehist' )->escaped() . '</a></li>', 00242 '<li><a href="#filelinks">' . wfMessage( 'imagelinks' )->escaped() . '</a></li>', 00243 ); 00244 if ( $metadata ) { 00245 $r[] = '<li><a href="#metadata">' . wfMessage( 'metadata' )->escaped() . '</a></li>'; 00246 } 00247 00248 wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) ); 00249 00250 return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>'; 00251 } 00252 00261 protected function makeMetadataTable( $metadata ) { 00262 $r = "<div class=\"mw-imagepage-section-metadata\">"; 00263 $r .= wfMessage( 'metadata-help' )->plain(); 00264 $r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n"; 00265 foreach ( $metadata as $type => $stuff ) { 00266 foreach ( $stuff as $v ) { 00267 # @todo FIXME: Why is this using escapeId for a class?! 00268 $class = Sanitizer::escapeId( $v['id'] ); 00269 if ( $type == 'collapsed' ) { 00270 // Handled by mediawiki.action.view.metadata module. 00271 $class .= ' collapsable'; 00272 } 00273 $r .= "<tr class=\"$class\">\n"; 00274 $r .= "<th>{$v['name']}</th>\n"; 00275 $r .= "<td>{$v['value']}</td>\n</tr>"; 00276 } 00277 } 00278 $r .= "</table>\n</div>\n"; 00279 return $r; 00280 } 00281 00289 public function getContentObject() { 00290 $this->loadFile(); 00291 if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) { 00292 return null; 00293 } 00294 return parent::getContentObject(); 00295 } 00296 00297 protected function openShowImage() { 00298 global $wgEnableUploads, $wgSend404Code; 00299 00300 $this->loadFile(); 00301 $out = $this->getContext()->getOutput(); 00302 $user = $this->getContext()->getUser(); 00303 $lang = $this->getContext()->getLanguage(); 00304 $dirmark = $lang->getDirMarkEntity(); 00305 $request = $this->getContext()->getRequest(); 00306 00307 $max = $this->getImageLimitsFromOption( $user, 'imagesize' ); 00308 $maxWidth = $max[0]; 00309 $maxHeight = $max[1]; 00310 00311 if ( $this->displayImg->exists() ) { 00312 # image 00313 $page = $request->getIntOrNull( 'page' ); 00314 if ( is_null( $page ) ) { 00315 $params = array(); 00316 $page = 1; 00317 } else { 00318 $params = array( 'page' => $page ); 00319 } 00320 00321 $renderLang = $request->getVal( 'lang' ); 00322 if ( !is_null( $renderLang ) ) { 00323 $handler = $this->displayImg->getHandler(); 00324 if ( $handler && $handler->validateParam( 'lang', $renderLang ) ) { 00325 $params['lang'] = $renderLang; 00326 } else { 00327 $renderLang = null; 00328 } 00329 } 00330 00331 $width_orig = $this->displayImg->getWidth( $page ); 00332 $width = $width_orig; 00333 $height_orig = $this->displayImg->getHeight( $page ); 00334 $height = $height_orig; 00335 00336 $filename = wfEscapeWikiText( $this->displayImg->getName() ); 00337 $linktext = $filename; 00338 00339 wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$out ) ); 00340 00341 if ( $this->displayImg->allowInlineDisplay() ) { 00342 # image 00343 # "Download high res version" link below the image 00344 # $msgsize = wfMessage( 'file-info-size', $width_orig, $height_orig, 00345 # Linker::formatSize( $this->displayImg->getSize() ), $mime )->escaped(); 00346 # We'll show a thumbnail of this image 00347 if ( $width > $maxWidth || $height > $maxHeight || $this->displayImg->isVectorized() ) { 00348 list( $width, $height ) = $this->getDisplayWidthHeight( 00349 $maxWidth, $maxHeight, $width, $height 00350 ); 00351 $linktext = wfMessage( 'show-big-image' )->escaped(); 00352 00353 $thumbSizes = $this->getThumbSizes( $width, $height, $width_orig, $height_orig ); 00354 # Generate thumbnails or thumbnail links as needed... 00355 $otherSizes = array(); 00356 foreach ( $thumbSizes as $size ) { 00357 // We include a thumbnail size in the list, if it is 00358 // less than or equal to the original size of the image 00359 // asset ($width_orig/$height_orig). We also exclude 00360 // the current thumbnail's size ($width/$height) 00361 // since that is added to the message separately, so 00362 // it can be denoted as the current size being shown. 00363 // Vectorized images are "infinitely" big, so all thumb 00364 // sizes are shown. 00365 if ( ( ( $size[0] <= $width_orig && $size[1] <= $height_orig ) 00366 || $this->displayImg->isVectorized() ) 00367 && $size[0] != $width && $size[1] != $height 00368 ) { 00369 $sizeLink = $this->makeSizeLink( $params, $size[0], $size[1] ); 00370 if ( $sizeLink ) { 00371 $otherSizes[] = $sizeLink; 00372 } 00373 } 00374 } 00375 $otherSizes = array_unique( $otherSizes ); 00376 00377 $msgsmall = ''; 00378 $sizeLinkBigImagePreview = $this->makeSizeLink( $params, $width, $height ); 00379 if ( $sizeLinkBigImagePreview ) { 00380 $msgsmall .= wfMessage( 'show-big-image-preview' )-> 00381 rawParams( $sizeLinkBigImagePreview )-> 00382 parse(); 00383 } 00384 if ( count( $otherSizes ) ) { 00385 $msgsmall .= ' ' . 00386 Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ), 00387 wfMessage( 'show-big-image-other' )->rawParams( $lang->pipeList( $otherSizes ) )-> 00388 params( count( $otherSizes ) )->parse() 00389 ); 00390 } 00391 } elseif ( $width == 0 && $height == 0 ) { 00392 # Some sort of audio file that doesn't have dimensions 00393 # Don't output a no hi res message for such a file 00394 $msgsmall = ''; 00395 } else { 00396 # Image is small enough to show full size on image page 00397 $msgsmall = wfMessage( 'file-nohires' )->parse(); 00398 } 00399 00400 $params['width'] = $width; 00401 $params['height'] = $height; 00402 $thumbnail = $this->displayImg->transform( $params ); 00403 Linker::processResponsiveImages( $this->displayImg, $thumbnail, $params ); 00404 00405 $anchorclose = Html::rawElement( 00406 'div', 00407 array( 'class' => 'mw-filepage-resolutioninfo' ), 00408 $msgsmall 00409 ); 00410 00411 $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1; 00412 if ( $isMulti ) { 00413 $out->addModules( 'mediawiki.page.image.pagination' ); 00414 $out->addHTML( '<table class="multipageimage"><tr><td>' ); 00415 } 00416 00417 if ( $thumbnail ) { 00418 $options = array( 00419 'alt' => $this->displayImg->getTitle()->getPrefixedText(), 00420 'file-link' => true, 00421 ); 00422 $out->addHTML( '<div class="fullImageLink" id="file">' . 00423 $thumbnail->toHtml( $options ) . 00424 $anchorclose . "</div>\n" ); 00425 } 00426 00427 if ( $isMulti ) { 00428 $count = $this->displayImg->pageCount(); 00429 00430 if ( $page > 1 ) { 00431 $label = $out->parse( wfMessage( 'imgmultipageprev' )->text(), false ); 00432 // on the client side, this link is generated in ajaxifyPageNavigation() 00433 // in the mediawiki.page.image.pagination module 00434 $link = Linker::linkKnown( 00435 $this->getTitle(), 00436 $label, 00437 array(), 00438 array( 'page' => $page - 1 ) 00439 ); 00440 $thumb1 = Linker::makeThumbLinkObj( 00441 $this->getTitle(), 00442 $this->displayImg, 00443 $link, 00444 $label, 00445 'none', 00446 array( 'page' => $page - 1 ) 00447 ); 00448 } else { 00449 $thumb1 = ''; 00450 } 00451 00452 if ( $page < $count ) { 00453 $label = wfMessage( 'imgmultipagenext' )->text(); 00454 $link = Linker::linkKnown( 00455 $this->getTitle(), 00456 $label, 00457 array(), 00458 array( 'page' => $page + 1 ) 00459 ); 00460 $thumb2 = Linker::makeThumbLinkObj( 00461 $this->getTitle(), 00462 $this->displayImg, 00463 $link, 00464 $label, 00465 'none', 00466 array( 'page' => $page + 1 ) 00467 ); 00468 } else { 00469 $thumb2 = ''; 00470 } 00471 00472 global $wgScript; 00473 00474 $formParams = array( 00475 'name' => 'pageselector', 00476 'action' => $wgScript, 00477 ); 00478 $options = array(); 00479 for ( $i = 1; $i <= $count; $i++ ) { 00480 $options[] = Xml::option( $lang->formatNum( $i ), $i, $i == $page ); 00481 } 00482 $select = Xml::tags( 'select', 00483 array( 'id' => 'pageselector', 'name' => 'page' ), 00484 implode( "\n", $options ) ); 00485 00486 $out->addHTML( 00487 '</td><td><div class="multipageimagenavbox">' . 00488 Xml::openElement( 'form', $formParams ) . 00489 Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . 00490 wfMessage( 'imgmultigoto' )->rawParams( $select )->parse() . 00491 Xml::submitButton( wfMessage( 'imgmultigo' )->text() ) . 00492 Xml::closeElement( 'form' ) . 00493 "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>" 00494 ); 00495 } 00496 } elseif ( $this->displayImg->isSafeFile() ) { 00497 # if direct link is allowed but it's not a renderable image, show an icon. 00498 $icon = $this->displayImg->iconThumb(); 00499 00500 $out->addHTML( '<div class="fullImageLink" id="file">' . 00501 $icon->toHtml( array( 'file-link' => true ) ) . 00502 "</div>\n" ); 00503 } 00504 00505 $longDesc = wfMessage( 'parentheses', $this->displayImg->getLongDesc() )->text(); 00506 00507 $medialink = "[[Media:$filename|$linktext]]"; 00508 00509 if ( !$this->displayImg->isSafeFile() ) { 00510 $warning = wfMessage( 'mediawarning' )->plain(); 00511 // dirmark is needed here to separate the file name, which 00512 // most likely ends in Latin characters, from the description, 00513 // which may begin with the file type. In RTL environment 00514 // this will get messy. 00515 // The dirmark, however, must not be immediately adjacent 00516 // to the filename, because it can get copied with it. 00517 // See bug 25277. 00518 // @codingStandardsIgnoreStart Ignore long line 00519 $out->addWikiText( <<<EOT 00520 <div class="fullMedia"><span class="dangerousLink">{$medialink}</span> $dirmark<span class="fileInfo">$longDesc</span></div> 00521 <div class="mediaWarning">$warning</div> 00522 EOT 00523 ); 00524 // @codingStandardsIgnoreEnd 00525 } else { 00526 $out->addWikiText( <<<EOT 00527 <div class="fullMedia">{$medialink} {$dirmark}<span class="fileInfo">$longDesc</span> 00528 </div> 00529 EOT 00530 ); 00531 } 00532 00533 $renderLangOptions = $this->displayImg->getAvailableLanguages(); 00534 if ( count( $renderLangOptions ) >= 1 ) { 00535 $currentLanguage = $renderLang; 00536 $defaultLang = $this->displayImg->getDefaultRenderLanguage(); 00537 if ( is_null( $currentLanguage ) ) { 00538 $currentLanguage = $defaultLang; 00539 } 00540 $out->addHtml( $this->doRenderLangOpt( $renderLangOptions, $currentLanguage, $defaultLang ) ); 00541 } 00542 00543 // Add cannot animate thumbnail warning 00544 if ( !$this->displayImg->canAnimateThumbIfAppropriate() ) { 00545 // Include the extension so wiki admins can 00546 // customize it on a per file-type basis 00547 // (aka say things like use format X instead). 00548 // additionally have a specific message for 00549 // file-no-thumb-animation-gif 00550 $ext = $this->displayImg->getExtension(); 00551 $noAnimMesg = wfMessageFallback( 00552 'file-no-thumb-animation-' . $ext, 00553 'file-no-thumb-animation' 00554 )->plain(); 00555 00556 $out->addWikiText( <<<EOT 00557 <div class="mw-noanimatethumb">{$noAnimMesg}</div> 00558 EOT 00559 ); 00560 } 00561 00562 if ( !$this->displayImg->isLocal() ) { 00563 $this->printSharedImageText(); 00564 } 00565 } else { 00566 # Image does not exist 00567 if ( !$this->getID() ) { 00568 # No article exists either 00569 # Show deletion log to be consistent with normal articles 00570 LogEventsList::showLogExtract( 00571 $out, 00572 array( 'delete', 'move' ), 00573 $this->getTitle()->getPrefixedText(), 00574 '', 00575 array( 'lim' => 10, 00576 'conds' => array( "log_action != 'revision'" ), 00577 'showIfEmpty' => false, 00578 'msgKey' => array( 'moveddeleted-notice' ) 00579 ) 00580 ); 00581 } 00582 00583 if ( $wgEnableUploads && $user->isAllowed( 'upload' ) ) { 00584 // Only show an upload link if the user can upload 00585 $uploadTitle = SpecialPage::getTitleFor( 'Upload' ); 00586 $nofile = array( 00587 'filepage-nofile-link', 00588 $uploadTitle->getFullURL( array( 'wpDestFile' => $this->mPage->getFile()->getName() ) ) 00589 ); 00590 } else { 00591 $nofile = 'filepage-nofile'; 00592 } 00593 // Note, if there is an image description page, but 00594 // no image, then this setRobotPolicy is overridden 00595 // by Article::View(). 00596 $out->setRobotPolicy( 'noindex,nofollow' ); 00597 $out->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile ); 00598 if ( !$this->getID() && $wgSend404Code ) { 00599 // If there is no image, no shared image, and no description page, 00600 // output a 404, to be consistent with articles. 00601 $request->response()->header( 'HTTP/1.1 404 Not Found' ); 00602 } 00603 } 00604 $out->setFileVersion( $this->displayImg ); 00605 } 00606 00614 private function makeSizeLink( $params, $width, $height ) { 00615 $params['width'] = $width; 00616 $params['height'] = $height; 00617 $thumbnail = $this->displayImg->transform( $params ); 00618 if ( $thumbnail && !$thumbnail->isError() ) { 00619 return Html::rawElement( 'a', array( 00620 'href' => $thumbnail->getUrl(), 00621 'class' => 'mw-thumbnail-link' 00622 ), wfMessage( 'show-big-image-size' )->numParams( 00623 $thumbnail->getWidth(), $thumbnail->getHeight() 00624 )->parse() ); 00625 } else { 00626 return ''; 00627 } 00628 } 00629 00633 protected function printSharedImageText() { 00634 $out = $this->getContext()->getOutput(); 00635 $this->loadFile(); 00636 00637 $descUrl = $this->mPage->getFile()->getDescriptionUrl(); 00638 $descText = $this->mPage->getFile()->getDescriptionText( $this->getContext()->getLanguage() ); 00639 00640 /* Add canonical to head if there is no local page for this shared file */ 00641 if ( $descUrl && $this->mPage->getID() == 0 ) { 00642 $out->setCanonicalUrl( $descUrl ); 00643 } 00644 00645 $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n"; 00646 $repo = $this->mPage->getFile()->getRepo()->getDisplayName(); 00647 00648 if ( $descUrl && $descText && wfMessage( 'sharedupload-desc-here' )->plain() !== '-' ) { 00649 $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) ); 00650 } elseif ( $descUrl && wfMessage( 'sharedupload-desc-there' )->plain() !== '-' ) { 00651 $out->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) ); 00652 } else { 00653 $out->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ ); 00654 } 00655 00656 if ( $descText ) { 00657 $this->mExtraDescription = $descText; 00658 } 00659 } 00660 00661 public function getUploadUrl() { 00662 $this->loadFile(); 00663 $uploadTitle = SpecialPage::getTitleFor( 'Upload' ); 00664 return $uploadTitle->getFullURL( array( 00665 'wpDestFile' => $this->mPage->getFile()->getName(), 00666 'wpForReUpload' => 1 00667 ) ); 00668 } 00669 00674 protected function uploadLinksBox() { 00675 global $wgEnableUploads; 00676 00677 if ( !$wgEnableUploads ) { 00678 return; 00679 } 00680 00681 $this->loadFile(); 00682 if ( !$this->mPage->getFile()->isLocal() ) { 00683 return; 00684 } 00685 00686 $out = $this->getContext()->getOutput(); 00687 $out->addHTML( "<ul>\n" ); 00688 00689 # "Upload a new version of this file" link 00690 $canUpload = $this->getTitle()->userCan( 'upload', $this->getContext()->getUser() ); 00691 if ( $canUpload && UploadBase::userCanReUpload( 00692 $this->getContext()->getUser(), 00693 $this->mPage->getFile()->name ) 00694 ) { 00695 $ulink = Linker::makeExternalLink( 00696 $this->getUploadUrl(), 00697 wfMessage( 'uploadnewversion-linktext' )->text() 00698 ); 00699 $out->addHTML( "<li id=\"mw-imagepage-reupload-link\">" 00700 . "<div class=\"plainlinks\">{$ulink}</div></li>\n" ); 00701 } else { 00702 $out->addHTML( "<li id=\"mw-imagepage-upload-disallowed\">" 00703 . $this->getContext()->msg( 'upload-disallowed-here' )->escaped() . "</li>\n" ); 00704 } 00705 00706 $out->addHTML( "</ul>\n" ); 00707 } 00708 00712 protected function closeShowImage() { 00713 } 00714 00719 protected function imageHistory() { 00720 $this->loadFile(); 00721 $out = $this->getContext()->getOutput(); 00722 $pager = new ImageHistoryPseudoPager( $this ); 00723 $out->addHTML( $pager->getBody() ); 00724 $out->preventClickjacking( $pager->getPreventClickjacking() ); 00725 00726 $this->mPage->getFile()->resetHistory(); // free db resources 00727 00728 # Exist check because we don't want to show this on pages where an image 00729 # doesn't exist along with the noimage message, that would suck. -ævar 00730 if ( $this->mPage->getFile()->exists() ) { 00731 $this->uploadLinksBox(); 00732 } 00733 } 00734 00740 protected function queryImageLinks( $target, $limit ) { 00741 $dbr = wfGetDB( DB_SLAVE ); 00742 00743 return $dbr->select( 00744 array( 'imagelinks', 'page' ), 00745 array( 'page_namespace', 'page_title', 'il_to' ), 00746 array( 'il_to' => $target, 'il_from = page_id' ), 00747 __METHOD__, 00748 array( 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', ) 00749 ); 00750 } 00751 00752 protected function imageLinks() { 00753 $limit = 100; 00754 00755 $out = $this->getContext()->getOutput(); 00756 00757 $rows = array(); 00758 $redirects = array(); 00759 foreach ( $this->getTitle()->getRedirectsHere( NS_FILE ) as $redir ) { 00760 $redirects[$redir->getDBkey()] = array(); 00761 $rows[] = (object)array( 00762 'page_namespace' => NS_FILE, 00763 'page_title' => $redir->getDBkey(), 00764 ); 00765 } 00766 00767 $res = $this->queryImageLinks( $this->getTitle()->getDBkey(), $limit + 1 ); 00768 foreach ( $res as $row ) { 00769 $rows[] = $row; 00770 } 00771 $count = count( $rows ); 00772 00773 $hasMore = $count > $limit; 00774 if ( !$hasMore && count( $redirects ) ) { 00775 $res = $this->queryImageLinks( array_keys( $redirects ), 00776 $limit - count( $rows ) + 1 ); 00777 foreach ( $res as $row ) { 00778 $redirects[$row->il_to][] = $row; 00779 $count++; 00780 } 00781 $hasMore = ( $res->numRows() + count( $rows ) ) > $limit; 00782 } 00783 00784 if ( $count == 0 ) { 00785 $out->wrapWikiMsg( 00786 Html::rawElement( 'div', 00787 array( 'id' => 'mw-imagepage-nolinkstoimage' ), "\n$1\n" ), 00788 'nolinkstoimage' 00789 ); 00790 return; 00791 } 00792 00793 $out->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" ); 00794 if ( !$hasMore ) { 00795 $out->addWikiMsg( 'linkstoimage', $count ); 00796 } else { 00797 // More links than the limit. Add a link to [[Special:Whatlinkshere]] 00798 $out->addWikiMsg( 'linkstoimage-more', 00799 $this->getContext()->getLanguage()->formatNum( $limit ), 00800 $this->getTitle()->getPrefixedDBkey() 00801 ); 00802 } 00803 00804 $out->addHTML( 00805 Html::openElement( 'ul', 00806 array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n" 00807 ); 00808 $count = 0; 00809 00810 // Sort the list by namespace:title 00811 usort( $rows, array( $this, 'compare' ) ); 00812 00813 // Create links for every element 00814 $currentCount = 0; 00815 foreach ( $rows as $element ) { 00816 $currentCount++; 00817 if ( $currentCount > $limit ) { 00818 break; 00819 } 00820 00821 $query = array(); 00822 # Add a redirect=no to make redirect pages reachable 00823 if ( isset( $redirects[$element->page_title] ) ) { 00824 $query['redirect'] = 'no'; 00825 } 00826 $link = Linker::linkKnown( 00827 Title::makeTitle( $element->page_namespace, $element->page_title ), 00828 null, array(), $query 00829 ); 00830 if ( !isset( $redirects[$element->page_title] ) ) { 00831 # No redirects 00832 $liContents = $link; 00833 } elseif ( count( $redirects[$element->page_title] ) === 0 ) { 00834 # Redirect without usages 00835 $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( $link, '' )->parse(); 00836 } else { 00837 # Redirect with usages 00838 $li = ''; 00839 foreach ( $redirects[$element->page_title] as $row ) { 00840 $currentCount++; 00841 if ( $currentCount > $limit ) { 00842 break; 00843 } 00844 00845 $link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) ); 00846 $li .= Html::rawElement( 00847 'li', 00848 array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ), 00849 $link2 00850 ) . "\n"; 00851 } 00852 00853 $ul = Html::rawElement( 00854 'ul', 00855 array( 'class' => 'mw-imagepage-redirectstofile' ), 00856 $li 00857 ) . "\n"; 00858 $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( 00859 $link, $ul )->parse(); 00860 } 00861 $out->addHTML( Html::rawElement( 00862 'li', 00863 array( 'class' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ), 00864 $liContents 00865 ) . "\n" 00866 ); 00867 00868 }; 00869 $out->addHTML( Html::closeElement( 'ul' ) . "\n" ); 00870 $res->free(); 00871 00872 // Add a links to [[Special:Whatlinkshere]] 00873 if ( $count > $limit ) { 00874 $out->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() ); 00875 } 00876 $out->addHTML( Html::closeElement( 'div' ) . "\n" ); 00877 } 00878 00879 protected function imageDupes() { 00880 $this->loadFile(); 00881 $out = $this->getContext()->getOutput(); 00882 00883 $dupes = $this->mPage->getDuplicates(); 00884 if ( count( $dupes ) == 0 ) { 00885 return; 00886 } 00887 00888 $out->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" ); 00889 $out->addWikiMsg( 'duplicatesoffile', 00890 $this->getContext()->getLanguage()->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey() 00891 ); 00892 $out->addHTML( "<ul class='mw-imagepage-duplicates'>\n" ); 00893 00897 foreach ( $dupes as $file ) { 00898 $fromSrc = ''; 00899 if ( $file->isLocal() ) { 00900 $link = Linker::linkKnown( $file->getTitle() ); 00901 } else { 00902 $link = Linker::makeExternalLink( $file->getDescriptionUrl(), 00903 $file->getTitle()->getPrefixedText() ); 00904 $fromSrc = wfMessage( 'shared-repo-from', $file->getRepo()->getDisplayName() )->text(); 00905 } 00906 $out->addHTML( "<li>{$link} {$fromSrc}</li>\n" ); 00907 } 00908 $out->addHTML( "</ul></div>\n" ); 00909 } 00910 00914 public function delete() { 00915 $file = $this->mPage->getFile(); 00916 if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) { 00917 // Standard article deletion 00918 parent::delete(); 00919 return; 00920 } 00921 00922 $deleter = new FileDeleteForm( $file ); 00923 $deleter->execute(); 00924 } 00925 00931 function showError( $description ) { 00932 $out = $this->getContext()->getOutput(); 00933 $out->setPageTitle( wfMessage( 'internalerror' ) ); 00934 $out->setRobotPolicy( 'noindex,nofollow' ); 00935 $out->setArticleRelated( false ); 00936 $out->enableClientCache( false ); 00937 $out->addWikiText( $description ); 00938 } 00939 00948 protected function compare( $a, $b ) { 00949 if ( $a->page_namespace == $b->page_namespace ) { 00950 return strcmp( $a->page_title, $b->page_title ); 00951 } else { 00952 return $a->page_namespace - $b->page_namespace; 00953 } 00954 } 00955 00964 public function getImageLimitsFromOption( $user, $optionName ) { 00965 global $wgImageLimits; 00966 00967 $option = $user->getIntOption( $optionName ); 00968 if ( !isset( $wgImageLimits[$option] ) ) { 00969 $option = User::getDefaultOption( $optionName ); 00970 } 00971 00972 // The user offset might still be incorrect, specially if 00973 // $wgImageLimits got changed (see bug #8858). 00974 if ( !isset( $wgImageLimits[$option] ) ) { 00975 // Default to the first offset in $wgImageLimits 00976 $option = 0; 00977 } 00978 00979 return isset( $wgImageLimits[$option] ) 00980 ? $wgImageLimits[$option] 00981 : array( 800, 600 ); // if nothing is set, fallback to a hardcoded default 00982 } 00983 00992 protected function doRenderLangOpt( array $langChoices, $curLang, $defaultLang ) { 00993 global $wgScript; 00994 sort( $langChoices ); 00995 $curLang = wfBCP47( $curLang ); 00996 $defaultLang = wfBCP47( $defaultLang ); 00997 $opts = ''; 00998 $haveCurrentLang = false; 00999 $haveDefaultLang = false; 01000 01001 // We make a list of all the language choices in the file. 01002 // Additionally if the default language to render this file 01003 // is not included as being in this file (for example, in svgs 01004 // usually the fallback content is the english content) also 01005 // include a choice for that. Last of all, if we're viewing 01006 // the file in a language not on the list, add it as a choice. 01007 foreach ( $langChoices as $lang ) { 01008 $code = wfBCP47( $lang ); 01009 $name = Language::fetchLanguageName( $code, $this->getContext()->getLanguage()->getCode() ); 01010 if ( $name !== '' ) { 01011 $display = wfMessage( 'img-lang-opt', $code, $name )->text(); 01012 } else { 01013 $display = $code; 01014 } 01015 $opts .= "\n" . Xml::option( $display, $code, $curLang === $code ); 01016 if ( $curLang === $code ) { 01017 $haveCurrentLang = true; 01018 } 01019 if ( $defaultLang === $code ) { 01020 $haveDefaultLang = true; 01021 } 01022 } 01023 if ( !$haveDefaultLang ) { 01024 // Its hard to know if the content is really in the default language, or 01025 // if its just unmarked content that could be in any language. 01026 $opts = Xml::option( 01027 wfMessage( 'img-lang-default' )->text(), 01028 $defaultLang, 01029 $defaultLang === $curLang 01030 ) . $opts; 01031 } 01032 if ( !$haveCurrentLang && $defaultLang !== $curLang ) { 01033 $name = Language::fetchLanguageName( $curLang, $this->getContext()->getLanguage()->getCode() ); 01034 if ( $name !== '' ) { 01035 $display = wfMessage( 'img-lang-opt', $curLang, $name )->text(); 01036 } else { 01037 $display = $curLang; 01038 } 01039 $opts = Xml::option( $display, $curLang, true ) . $opts; 01040 } 01041 01042 $select = Html::rawElement( 01043 'select', 01044 array( 'id' => 'mw-imglangselector', 'name' => 'lang' ), 01045 $opts 01046 ); 01047 $submit = Xml::submitButton( wfMessage( 'img-lang-go' )->text() ); 01048 01049 $formContents = wfMessage( 'img-lang-info' )->rawParams( $select, $submit )->parse() 01050 . Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ); 01051 01052 $langSelectLine = Html::rawElement( 'div', array( 'id' => 'mw-imglangselector-line' ), 01053 Html::rawElement( 'form', array( 'action' => $wgScript ), $formContents ) 01054 ); 01055 return $langSelectLine; 01056 } 01057 01072 protected function getDisplayWidthHeight( $maxWidth, $maxHeight, $width, $height ) { 01073 if ( !$maxWidth || !$maxHeight ) { 01074 // should never happen 01075 throw new MWException( 'Using a choice from $wgImageLimits that is 0x0' ); 01076 } 01077 01078 if ( !$width || !$height ) { 01079 return array( 0, 0 ); 01080 } 01081 01082 # Calculate the thumbnail size. 01083 if ( $width <= $maxWidth && $height <= $maxHeight ) { 01084 // Vectorized image, do nothing. 01085 } elseif ( $width / $height >= $maxWidth / $maxHeight ) { 01086 # The limiting factor is the width, not the height. 01087 $height = round( $height * $maxWidth / $width ); 01088 $width = $maxWidth; 01089 # Note that $height <= $maxHeight now. 01090 } else { 01091 $newwidth = floor( $width * $maxHeight / $height ); 01092 $height = round( $height * $newwidth / $width ); 01093 $width = $newwidth; 01094 # Note that $height <= $maxHeight now, but might not be identical 01095 # because of rounding. 01096 } 01097 return array( $width, $height ); 01098 } 01099 01108 protected function getThumbSizes( $origWidth, $origHeight ) { 01109 global $wgImageLimits; 01110 if ( $this->displayImg->getRepo()->canTransformVia404() ) { 01111 $thumbSizes = $wgImageLimits; 01112 // Also include the full sized resolution in the list, so 01113 // that users know they can get it. This will link to the 01114 // original file asset if mustRender() === false. In the case 01115 // that we mustRender, some users have indicated that they would 01116 // find it useful to have the full size image in the rendered 01117 // image format. 01118 $thumbSizes[] = array( $origWidth, $origHeight ); 01119 } else { 01120 # Creating thumb links triggers thumbnail generation. 01121 # Just generate the thumb for the current users prefs. 01122 $thumbSizes = array( $this->getImageLimitsFromOption( $this->getContext()->getUser(), 'thumbsize' ) ); 01123 if ( !$this->displayImg->mustRender() ) { 01124 // We can safely include a link to the "full-size" preview, 01125 // without actually rendering. 01126 $thumbSizes[] = array( $origWidth, $origHeight ); 01127 } 01128 } 01129 return $thumbSizes; 01130 } 01131 01132 } 01133 01139 class ImageHistoryList extends ContextSource { 01140 01144 protected $title; 01145 01149 protected $img; 01150 01154 protected $imagePage; 01155 01159 protected $current; 01160 01161 protected $repo, $showThumb; 01162 protected $preventClickjacking = false; 01163 01167 public function __construct( $imagePage ) { 01168 global $wgShowArchiveThumbnails; 01169 $this->current = $imagePage->getFile(); 01170 $this->img = $imagePage->getDisplayedFile(); 01171 $this->title = $imagePage->getTitle(); 01172 $this->imagePage = $imagePage; 01173 $this->showThumb = $wgShowArchiveThumbnails && $this->img->canRender(); 01174 $this->setContext( $imagePage->getContext() ); 01175 } 01176 01180 public function getImagePage() { 01181 return $this->imagePage; 01182 } 01183 01187 public function getFile() { 01188 return $this->img; 01189 } 01190 01195 public function beginImageHistoryList( $navLinks = '' ) { 01196 return Xml::element( 'h2', array( 'id' => 'filehistory' ), $this->msg( 'filehist' )->text() ) 01197 . "\n" 01198 . "<div id=\"mw-imagepage-section-filehistory\">\n" 01199 . $this->msg( 'filehist-help' )->parseAsBlock() 01200 . $navLinks . "\n" 01201 . Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n" 01202 . '<tr><td></td>' 01203 . ( $this->current->isLocal() 01204 && ( $this->getUser()->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' ) 01205 . '<th>' . $this->msg( 'filehist-datetime' )->escaped() . '</th>' 01206 . ( $this->showThumb ? '<th>' . $this->msg( 'filehist-thumb' )->escaped() . '</th>' : '' ) 01207 . '<th>' . $this->msg( 'filehist-dimensions' )->escaped() . '</th>' 01208 . '<th>' . $this->msg( 'filehist-user' )->escaped() . '</th>' 01209 . '<th>' . $this->msg( 'filehist-comment' )->escaped() . '</th>' 01210 . "</tr>\n"; 01211 } 01212 01217 public function endImageHistoryList( $navLinks = '' ) { 01218 return "</table>\n$navLinks\n</div>\n"; 01219 } 01220 01226 public function imageHistoryLine( $iscur, $file ) { 01227 global $wgContLang; 01228 01229 $user = $this->getUser(); 01230 $lang = $this->getLanguage(); 01231 $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() ); 01232 $img = $iscur ? $file->getName() : $file->getArchiveName(); 01233 $userId = $file->getUser( 'id' ); 01234 $userText = $file->getUser( 'text' ); 01235 $description = $file->getDescription( File::FOR_THIS_USER, $user ); 01236 01237 $local = $this->current->isLocal(); 01238 $row = $selected = ''; 01239 01240 // Deletion link 01241 if ( $local && ( $user->isAllowedAny( 'delete', 'deletedhistory' ) ) ) { 01242 $row .= '<td>'; 01243 # Link to remove from history 01244 if ( $user->isAllowed( 'delete' ) ) { 01245 $q = array( 'action' => 'delete' ); 01246 if ( !$iscur ) { 01247 $q['oldimage'] = $img; 01248 } 01249 $row .= Linker::linkKnown( 01250 $this->title, 01251 $this->msg( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' )->escaped(), 01252 array(), $q 01253 ); 01254 } 01255 # Link to hide content. Don't show useless link to people who cannot hide revisions. 01256 $canHide = $user->isAllowed( 'deleterevision' ); 01257 if ( $canHide || ( $user->isAllowed( 'deletedhistory' ) && $file->getVisibility() ) ) { 01258 if ( $user->isAllowed( 'delete' ) ) { 01259 $row .= '<br />'; 01260 } 01261 // If file is top revision or locked from this user, don't link 01262 if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED, $user ) ) { 01263 $del = Linker::revDeleteLinkDisabled( $canHide ); 01264 } else { 01265 list( $ts, ) = explode( '!', $img, 2 ); 01266 $query = array( 01267 'type' => 'oldimage', 01268 'target' => $this->title->getPrefixedText(), 01269 'ids' => $ts, 01270 ); 01271 $del = Linker::revDeleteLink( $query, 01272 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide ); 01273 } 01274 $row .= $del; 01275 } 01276 $row .= '</td>'; 01277 } 01278 01279 // Reversion link/current indicator 01280 $row .= '<td>'; 01281 if ( $iscur ) { 01282 $row .= $this->msg( 'filehist-current' )->escaped(); 01283 } elseif ( $local && $this->title->quickUserCan( 'edit', $user ) 01284 && $this->title->quickUserCan( 'upload', $user ) 01285 ) { 01286 if ( $file->isDeleted( File::DELETED_FILE ) ) { 01287 $row .= $this->msg( 'filehist-revert' )->escaped(); 01288 } else { 01289 $row .= Linker::linkKnown( 01290 $this->title, 01291 $this->msg( 'filehist-revert' )->escaped(), 01292 array(), 01293 array( 01294 'action' => 'revert', 01295 'oldimage' => $img, 01296 'wpEditToken' => $user->getEditToken( $img ) 01297 ) 01298 ); 01299 } 01300 } 01301 $row .= '</td>'; 01302 01303 // Date/time and image link 01304 if ( $file->getTimestamp() === $this->img->getTimestamp() ) { 01305 $selected = "class='filehistory-selected'"; 01306 } 01307 $row .= "<td $selected style='white-space: nowrap;'>"; 01308 if ( !$file->userCan( File::DELETED_FILE, $user ) ) { 01309 # Don't link to unviewable files 01310 $row .= '<span class="history-deleted">' 01311 . $lang->userTimeAndDate( $timestamp, $user ) . '</span>'; 01312 } elseif ( $file->isDeleted( File::DELETED_FILE ) ) { 01313 if ( $local ) { 01314 $this->preventClickjacking(); 01315 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); 01316 # Make a link to review the image 01317 $url = Linker::linkKnown( 01318 $revdel, 01319 $lang->userTimeAndDate( $timestamp, $user ), 01320 array(), 01321 array( 01322 'target' => $this->title->getPrefixedText(), 01323 'file' => $img, 01324 'token' => $user->getEditToken( $img ) 01325 ) 01326 ); 01327 } else { 01328 $url = $lang->userTimeAndDate( $timestamp, $user ); 01329 } 01330 $row .= '<span class="history-deleted">' . $url . '</span>'; 01331 } elseif ( !$file->exists() ) { 01332 $row .= '<span class="mw-file-missing">' 01333 . $lang->userTimeAndDate( $timestamp, $user ) . '</span>'; 01334 } else { 01335 $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img ); 01336 $row .= Xml::element( 01337 'a', 01338 array( 'href' => $url ), 01339 $lang->userTimeAndDate( $timestamp, $user ) 01340 ); 01341 } 01342 $row .= "</td>"; 01343 01344 // Thumbnail 01345 if ( $this->showThumb ) { 01346 $row .= '<td>' . $this->getThumbForLine( $file ) . '</td>'; 01347 } 01348 01349 // Image dimensions + size 01350 $row .= '<td>'; 01351 $row .= htmlspecialchars( $file->getDimensionsString() ); 01352 $row .= $this->msg( 'word-separator' )->escaped(); 01353 $row .= '<span style="white-space: nowrap;">'; 01354 $row .= $this->msg( 'parentheses' )->sizeParams( $file->getSize() )->escaped(); 01355 $row .= '</span>'; 01356 $row .= '</td>'; 01357 01358 // Uploading user 01359 $row .= '<td>'; 01360 // Hide deleted usernames 01361 if ( $file->isDeleted( File::DELETED_USER ) ) { 01362 $row .= '<span class="history-deleted">' 01363 . $this->msg( 'rev-deleted-user' )->escaped() . '</span>'; 01364 } else { 01365 if ( $local ) { 01366 $row .= Linker::userLink( $userId, $userText ); 01367 $row .= $this->msg( 'word-separator' )->escaped(); 01368 $row .= '<span style="white-space: nowrap;">'; 01369 $row .= Linker::userToolLinks( $userId, $userText ); 01370 $row .= '</span>'; 01371 } else { 01372 $row .= htmlspecialchars( $userText ); 01373 } 01374 } 01375 $row .= '</td>'; 01376 01377 // Don't show deleted descriptions 01378 if ( $file->isDeleted( File::DELETED_COMMENT ) ) { 01379 $row .= '<td><span class="history-deleted">' . 01380 $this->msg( 'rev-deleted-comment' )->escaped() . '</span></td>'; 01381 } else { 01382 $row .= '<td dir="' . $wgContLang->getDir() . '">' . 01383 Linker::formatComment( $description, $this->title ) . '</td>'; 01384 } 01385 01386 $rowClass = null; 01387 wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) ); 01388 $classAttr = $rowClass ? " class='$rowClass'" : ''; 01389 01390 return "<tr{$classAttr}>{$row}</tr>\n"; 01391 } 01392 01397 protected function getThumbForLine( $file ) { 01398 $lang = $this->getLanguage(); 01399 $user = $this->getUser(); 01400 if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE, $user ) 01401 && !$file->isDeleted( File::DELETED_FILE ) 01402 ) { 01403 $params = array( 01404 'width' => '120', 01405 'height' => '120', 01406 ); 01407 $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() ); 01408 01409 $thumbnail = $file->transform( $params ); 01410 $options = array( 01411 'alt' => $this->msg( 'filehist-thumbtext', 01412 $lang->userTimeAndDate( $timestamp, $user ), 01413 $lang->userDate( $timestamp, $user ), 01414 $lang->userTime( $timestamp, $user ) )->text(), 01415 'file-link' => true, 01416 ); 01417 01418 if ( !$thumbnail ) { 01419 return $this->msg( 'filehist-nothumb' )->escaped(); 01420 } 01421 01422 return $thumbnail->toHtml( $options ); 01423 } else { 01424 return $this->msg( 'filehist-nothumb' )->escaped(); 01425 } 01426 } 01427 01431 protected function preventClickjacking( $enable = true ) { 01432 $this->preventClickjacking = $enable; 01433 } 01434 01438 public function getPreventClickjacking() { 01439 return $this->preventClickjacking; 01440 } 01441 } 01442 01443 class ImageHistoryPseudoPager extends ReverseChronologicalPager { 01444 protected $preventClickjacking = false; 01445 01449 protected $mImg; 01450 01454 protected $mTitle; 01455 01459 function __construct( $imagePage ) { 01460 parent::__construct( $imagePage->getContext() ); 01461 $this->mImagePage = $imagePage; 01462 $this->mTitle = clone ( $imagePage->getTitle() ); 01463 $this->mTitle->setFragment( '#filehistory' ); 01464 $this->mImg = null; 01465 $this->mHist = array(); 01466 $this->mRange = array( 0, 0 ); // display range 01467 } 01468 01472 function getTitle() { 01473 return $this->mTitle; 01474 } 01475 01476 function getQueryInfo() { 01477 return false; 01478 } 01479 01483 function getIndexField() { 01484 return ''; 01485 } 01486 01491 function formatRow( $row ) { 01492 return ''; 01493 } 01494 01498 function getBody() { 01499 $s = ''; 01500 $this->doQuery(); 01501 if ( count( $this->mHist ) ) { 01502 $list = new ImageHistoryList( $this->mImagePage ); 01503 # Generate prev/next links 01504 $navLink = $this->getNavigationBar(); 01505 $s = $list->beginImageHistoryList( $navLink ); 01506 // Skip rows there just for paging links 01507 for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { 01508 $file = $this->mHist[$i]; 01509 $s .= $list->imageHistoryLine( !$file->isOld(), $file ); 01510 } 01511 $s .= $list->endImageHistoryList( $navLink ); 01512 01513 if ( $list->getPreventClickjacking() ) { 01514 $this->preventClickjacking(); 01515 } 01516 } 01517 return $s; 01518 } 01519 01520 function doQuery() { 01521 if ( $this->mQueryDone ) { 01522 return; 01523 } 01524 $this->mImg = $this->mImagePage->getFile(); // ensure loading 01525 if ( !$this->mImg->exists() ) { 01526 return; 01527 } 01528 $queryLimit = $this->mLimit + 1; // limit plus extra row 01529 if ( $this->mIsBackwards ) { 01530 // Fetch the file history 01531 $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false ); 01532 // The current rev may not meet the offset/limit 01533 $numRows = count( $this->mHist ); 01534 if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) { 01535 $this->mHist = array_merge( array( $this->mImg ), $this->mHist ); 01536 } 01537 } else { 01538 // The current rev may not meet the offset 01539 if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) { 01540 $this->mHist[] = $this->mImg; 01541 } 01542 // Old image versions (fetch extra row for nav links) 01543 $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1; 01544 // Fetch the file history 01545 $this->mHist = array_merge( $this->mHist, 01546 $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) ); 01547 } 01548 $numRows = count( $this->mHist ); // Total number of query results 01549 if ( $numRows ) { 01550 # Index value of top item in the list 01551 $firstIndex = $this->mIsBackwards ? 01552 $this->mHist[$numRows - 1]->getTimestamp() : $this->mHist[0]->getTimestamp(); 01553 # Discard the extra result row if there is one 01554 if ( $numRows > $this->mLimit && $numRows > 1 ) { 01555 if ( $this->mIsBackwards ) { 01556 # Index value of item past the index 01557 $this->mPastTheEndIndex = $this->mHist[0]->getTimestamp(); 01558 # Index value of bottom item in the list 01559 $lastIndex = $this->mHist[1]->getTimestamp(); 01560 # Display range 01561 $this->mRange = array( 1, $numRows - 1 ); 01562 } else { 01563 # Index value of item past the index 01564 $this->mPastTheEndIndex = $this->mHist[$numRows - 1]->getTimestamp(); 01565 # Index value of bottom item in the list 01566 $lastIndex = $this->mHist[$numRows - 2]->getTimestamp(); 01567 # Display range 01568 $this->mRange = array( 0, $numRows - 2 ); 01569 } 01570 } else { 01571 # Setting indexes to an empty string means that they will be 01572 # omitted if they would otherwise appear in URLs. It just so 01573 # happens that this is the right thing to do in the standard 01574 # UI, in all the relevant cases. 01575 $this->mPastTheEndIndex = ''; 01576 # Index value of bottom item in the list 01577 $lastIndex = $this->mIsBackwards ? 01578 $this->mHist[0]->getTimestamp() : $this->mHist[$numRows - 1]->getTimestamp(); 01579 # Display range 01580 $this->mRange = array( 0, $numRows - 1 ); 01581 } 01582 } else { 01583 $firstIndex = ''; 01584 $lastIndex = ''; 01585 $this->mPastTheEndIndex = ''; 01586 } 01587 if ( $this->mIsBackwards ) { 01588 $this->mIsFirst = ( $numRows < $queryLimit ); 01589 $this->mIsLast = ( $this->mOffset == '' ); 01590 $this->mLastShown = $firstIndex; 01591 $this->mFirstShown = $lastIndex; 01592 } else { 01593 $this->mIsFirst = ( $this->mOffset == '' ); 01594 $this->mIsLast = ( $numRows < $queryLimit ); 01595 $this->mLastShown = $lastIndex; 01596 $this->mFirstShown = $firstIndex; 01597 } 01598 $this->mQueryDone = true; 01599 } 01600 01604 protected function preventClickjacking( $enable = true ) { 01605 $this->preventClickjacking = $enable; 01606 } 01607 01611 public function getPreventClickjacking() { 01612 return $this->preventClickjacking; 01613 } 01614 01615 }