MediaWiki  REL1_24
ImagePage.php
Go to the documentation of this file.
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 }