00001 <?php
00002
00003 if( !defined( 'MEDIAWIKI' ) )
00004 die( 1 );
00005
00011 class ImagePage extends Article {
00012
00013 var $img;
00014 var $displayImg;
00015 var $repo;
00016 var $fileLoaded;
00017 var $mExtraDescription = false;
00018 var $dupes;
00019
00020 function __construct( $title ) {
00021 parent::__construct( $title );
00022 $this->dupes = null;
00023 $this->repo = null;
00024 }
00025
00026 public function setFile( $file ) {
00027 $this->displayImg = $file;
00028 $this->img = $file;
00029 $this->fileLoaded = true;
00030 }
00031
00032 protected function loadFile() {
00033 if( $this->fileLoaded ) {
00034 return true;
00035 }
00036 $this->fileLoaded = true;
00037
00038 $this->displayImg = $this->img = false;
00039 wfRunHooks( 'ImagePageFindFile', array( $this, &$this->img, &$this->displayImg ) );
00040 if( !$this->img ) {
00041 $this->img = wfFindFile( $this->mTitle );
00042 if( !$this->img ) {
00043 $this->img = wfLocalFile( $this->mTitle );
00044 }
00045 }
00046 if( !$this->displayImg ) {
00047 $this->displayImg = $this->img;
00048 }
00049 $this->repo = $this->img->getRepo();
00050 }
00051
00056 public function render() {
00057 global $wgOut;
00058 $wgOut->setArticleBodyOnly( true );
00059 parent::view();
00060 }
00061
00062 public function view() {
00063 global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
00064 $this->loadFile();
00065
00066 if( $this->mTitle->getNamespace() == NS_FILE && $this->img->getRedirected() ) {
00067 if( $this->mTitle->getDBkey() == $this->img->getName() ) {
00068
00069
00070 return Article::view();
00071 } else {
00072
00073
00074 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
00075 $wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->img->getName() ),
00076 true, true ) );
00077 $this->viewUpdates();
00078 return;
00079 }
00080 }
00081
00082 $diff = $wgRequest->getVal( 'diff' );
00083 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
00084
00085 if( $this->mTitle->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) )
00086 return Article::view();
00087
00088 $this->showRedirectedFromHeader();
00089
00090 if( $wgShowEXIF && $this->displayImg->exists() ) {
00091
00092 $formattedMetadata = $this->displayImg->formatMetadata();
00093 $showmeta = $formattedMetadata !== false;
00094 } else {
00095 $showmeta = false;
00096 }
00097
00098 if( !$diff && $this->displayImg->exists() )
00099 $wgOut->addHTML( $this->showTOC($showmeta) );
00100
00101 if( !$diff )
00102 $this->openShowImage();
00103
00104 # No need to display noarticletext, we use our own message, output in openShowImage()
00105 if( $this->getID() ) {
00106 Article::view();
00107 } else {
00108 # Just need to set the right headers
00109 $wgOut->setArticleFlag( true );
00110 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
00111 $this->viewUpdates();
00112 }
00113
00114 # Show shared description, if needed
00115 if( $this->mExtraDescription ) {
00116 $fol = wfMsgNoTrans( 'shareddescriptionfollows' );
00117 if( $fol != '-' && !wfEmptyMsg( 'shareddescriptionfollows', $fol ) ) {
00118 $wgOut->addWikiText( $fol );
00119 }
00120 $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" );
00121 }
00122
00123 $this->closeShowImage();
00124 $this->imageHistory();
00125
00126
00127 $wgOut->addHTML( Xml::element( 'h2',
00128 array( 'id' => 'filelinks' ),
00129 wfMsg( 'imagelinks' ) ) . "\n" );
00130 $this->imageDupes();
00131 # TODO! FIXME! For some freaky reason, we can't redirect to foreign images.
00132 # Yet we return metadata about the target. Definitely an issue in the FileRepo
00133 $this->imageRedirects();
00134 $this->imageLinks();
00135
00136 # Allow extensions to add something after the image links
00137 $html = '';
00138 wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) );
00139 if ( $html)
00140 $wgOut->addHTML( $html );
00141
00142 if( $showmeta ) {
00143 global $wgStylePath, $wgStyleVersion;
00144 $expand = htmlspecialchars( Xml::escapeJsString( wfMsg( 'metadata-expand' ) ) );
00145 $collapse = htmlspecialchars( Xml::escapeJsString( wfMsg( 'metadata-collapse' ) ) );
00146 $wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), wfMsg( 'metadata' ) ) . "\n" );
00147 $wgOut->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
00148 $wgOut->addScriptFile( 'metadata.js' );
00149 $wgOut->addHTML(
00150 "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" );
00151 }
00152 }
00153
00154 public function getRedirectTarget() {
00155 $this->loadFile();
00156 if( $this->img->isLocal() ) {
00157 return parent::getRedirectTarget();
00158 }
00159
00160 $from = $this->img->getRedirected();
00161 $to = $this->img->getName();
00162 if( $from == $to ) {
00163 return null;
00164 }
00165 return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
00166 }
00167 public function followRedirect() {
00168 $this->loadFile();
00169 if( $this->img->isLocal() ) {
00170 return parent::followRedirect();
00171 }
00172 $from = $this->img->getRedirected();
00173 $to = $this->img->getName();
00174 if( $from == $to ) {
00175 return false;
00176 }
00177 return Title::makeTitle( NS_FILE, $to );
00178 }
00179 public function isRedirect( $text = false ) {
00180 $this->loadFile();
00181 if( $this->img->isLocal() )
00182 return parent::isRedirect( $text );
00183
00184 return (bool)$this->img->getRedirected();
00185 }
00186
00187 public function isLocal() {
00188 $this->loadFile();
00189 return $this->img->isLocal();
00190 }
00191
00192 public function getFile() {
00193 $this->loadFile();
00194 return $this->img;
00195 }
00196
00197 public function getDisplayedFile() {
00198 $this->loadFile();
00199 return $this->displayImg;
00200 }
00201
00202 public function getDuplicates() {
00203 $this->loadFile();
00204 if( !is_null($this->dupes) ) {
00205 return $this->dupes;
00206 }
00207 if( !( $hash = $this->img->getSha1() ) ) {
00208 return $this->dupes = array();
00209 }
00210 $dupes = RepoGroup::singleton()->findBySha1( $hash );
00211
00212 $self = $this->img->getRepoName().':'.$this->img->getName();
00213 $size = $this->img->getSize();
00214 foreach ( $dupes as $index => $file ) {
00215 $key = $file->getRepoName().':'.$file->getName();
00216 if( $key == $self )
00217 unset( $dupes[$index] );
00218 if( $file->getSize() != $size )
00219 unset( $dupes[$index] );
00220 }
00221 return $this->dupes = $dupes;
00222
00223 }
00224
00225
00232 protected function showTOC( $metadata ) {
00233 $r = array(
00234 '<li><a href="#file">' . wfMsgHtml( 'file-anchor-link' ) . '</a></li>',
00235 '<li><a href="#filehistory">' . wfMsgHtml( 'filehist' ) . '</a></li>',
00236 '<li><a href="#filelinks">' . wfMsgHtml( 'imagelinks' ) . '</a></li>',
00237 );
00238 if ( $metadata ) {
00239 $r[] = '<li><a href="#metadata">' . wfMsgHtml( 'metadata' ) . '</a></li>';
00240 }
00241
00242 wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) );
00243
00244 return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>';
00245 }
00246
00255 protected function makeMetadataTable( $metadata ) {
00256 $r = "<div class=\"mw-imagepage-section-metadata\">";
00257 $r .= wfMsgNoTrans( 'metadata-help' );
00258 $r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n";
00259 foreach ( $metadata as $type => $stuff ) {
00260 foreach ( $stuff as $v ) {
00261 # FIXME, why is this using escapeId for a class?!
00262 $class = Sanitizer::escapeId( $v['id'] );
00263 if( $type == 'collapsed' ) {
00264 $class .= ' collapsable';
00265 }
00266 $r .= "<tr class=\"$class\">\n";
00267 $r .= "<th>{$v['name']}</th>\n";
00268 $r .= "<td>{$v['value']}</td>\n</tr>";
00269 }
00270 }
00271 $r .= "</table>\n</div>\n";
00272 return $r;
00273 }
00274
00281 public function getContent() {
00282 $this->loadFile();
00283 if( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
00284 return '';
00285 }
00286 return Article::getContent();
00287 }
00288
00289 protected function openShowImage() {
00290 global $wgOut, $wgUser, $wgImageLimits, $wgRequest,
00291 $wgLang, $wgContLang, $wgEnableUploads;
00292
00293 $this->loadFile();
00294
00295 $full_url = $this->displayImg->getURL();
00296 $linkAttribs = false;
00297 $sizeSel = intval( $wgUser->getOption( 'imagesize') );
00298 if( !isset( $wgImageLimits[$sizeSel] ) ) {
00299 $sizeSel = User::getDefaultOption( 'imagesize' );
00300
00301
00302
00303 if( !isset( $wgImageLimits[$sizeSel] ) ) {
00304
00305 $sizeSel = 0;
00306 }
00307 }
00308 $max = $wgImageLimits[$sizeSel];
00309 $maxWidth = $max[0];
00310 $maxHeight = $max[1];
00311 $sk = $wgUser->getSkin();
00312 $dirmark = $wgContLang->getDirMark();
00313
00314 if( $this->displayImg->exists() ) {
00315 # image
00316 $page = $wgRequest->getIntOrNull( 'page' );
00317 if( is_null( $page ) ) {
00318 $params = array();
00319 $page = 1;
00320 } else {
00321 $params = array( 'page' => $page );
00322 }
00323 $width_orig = $this->displayImg->getWidth();
00324 $width = $width_orig;
00325 $height_orig = $this->displayImg->getHeight();
00326 $height = $height_orig;
00327 $mime = $this->displayImg->getMimeType();
00328 $showLink = false;
00329 $linkAttribs = array( 'href' => $full_url );
00330 $longDesc = $this->displayImg->getLongDesc();
00331
00332 wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$wgOut ) );
00333
00334 if( $this->displayImg->allowInlineDisplay() ) {
00335 # image
00336
00337 # "Download high res version" link below the image
00338 #$msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->displayImg->getSize() ), $mime );
00339 # We'll show a thumbnail of this image
00340 if( $width > $maxWidth || $height > $maxHeight ) {
00341 # Calculate the thumbnail size.
00342 # First case, the limiting factor is the width, not the height.
00343 if( $width / $height >= $maxWidth / $maxHeight ) {
00344 $height = round( $height * $maxWidth / $width);
00345 $width = $maxWidth;
00346 # Note that $height <= $maxHeight now.
00347 } else {
00348 $newwidth = floor( $width * $maxHeight / $height);
00349 $height = round( $height * $newwidth / $width );
00350 $width = $newwidth;
00351 # Note that $height <= $maxHeight now, but might not be identical
00352 # because of rounding.
00353 }
00354 $msgbig = wfMsgHtml( 'show-big-image' );
00355 $msgsmall = wfMsgExt( 'show-big-image-thumb', 'parseinline',
00356 $wgLang->formatNum( $width ),
00357 $wgLang->formatNum( $height )
00358 );
00359 } else {
00360 # Image is small enough to show full size on image page
00361 $msgbig = htmlspecialchars( $this->displayImg->getName() );
00362 $msgsmall = wfMsgExt( 'file-nohires', array( 'parseinline' ) );
00363 }
00364
00365 $params['width'] = $width;
00366 $thumbnail = $this->displayImg->transform( $params );
00367
00368 $anchorclose = "<br />";
00369 if( $this->displayImg->mustRender() ) {
00370 $showLink = true;
00371 } else {
00372 $anchorclose .=
00373 $msgsmall .
00374 '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . "$dirmark " . $longDesc;
00375 }
00376
00377 $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1;
00378 if( $isMulti ) {
00379 $wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
00380 }
00381
00382 if( $thumbnail ) {
00383 $options = array(
00384 'alt' => $this->displayImg->getTitle()->getPrefixedText(),
00385 'file-link' => true,
00386 );
00387 $wgOut->addHTML( '<div class="fullImageLink" id="file">' .
00388 $thumbnail->toHtml( $options ) .
00389 $anchorclose . "</div>\n" );
00390 }
00391
00392 if( $isMulti ) {
00393 $count = $this->displayImg->pageCount();
00394
00395 if( $page > 1 ) {
00396 $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
00397 $link = $sk->link(
00398 $this->mTitle,
00399 $label,
00400 array(),
00401 array( 'page' => $page - 1 ),
00402 array( 'known', 'noclasses' )
00403 );
00404 $thumb1 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
00405 array( 'page' => $page - 1 ) );
00406 } else {
00407 $thumb1 = '';
00408 }
00409
00410 if( $page < $count ) {
00411 $label = wfMsg( 'imgmultipagenext' );
00412 $link = $sk->link(
00413 $this->mTitle,
00414 $label,
00415 array(),
00416 array( 'page' => $page + 1 ),
00417 array( 'known', 'noclasses' )
00418 );
00419 $thumb2 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
00420 array( 'page' => $page + 1 ) );
00421 } else {
00422 $thumb2 = '';
00423 }
00424
00425 global $wgScript;
00426
00427 $formParams = array(
00428 'name' => 'pageselector',
00429 'action' => $wgScript,
00430 'onchange' => 'document.pageselector.submit();',
00431 );
00432
00433 $option = array();
00434 for ( $i=1; $i <= $count; $i++ ) {
00435 $options[] = Xml::option( $wgLang->formatNum($i), $i, $i == $page );
00436 }
00437 $select = Xml::tags( 'select',
00438 array( 'id' => 'pageselector', 'name' => 'page' ),
00439 implode( "\n", $options ) );
00440
00441 $wgOut->addHTML(
00442 '</td><td><div class="multipageimagenavbox">' .
00443 Xml::openElement( 'form', $formParams ) .
00444 Xml::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
00445 wfMsgExt( 'imgmultigoto', array( 'parseinline', 'replaceafter' ), $select ) .
00446 Xml::submitButton( wfMsg( 'imgmultigo' ) ) .
00447 Xml::closeElement( 'form' ) .
00448 "<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>"
00449 );
00450 }
00451 } else {
00452 #if direct link is allowed but it's not a renderable image, show an icon.
00453 if( $this->displayImg->isSafeFile() ) {
00454 $icon= $this->displayImg->iconThumb();
00455
00456 $wgOut->addHTML( '<div class="fullImageLink" id="file">' .
00457 $icon->toHtml( array( 'file-link' => true ) ) .
00458 "</div>\n" );
00459 }
00460
00461 $showLink = true;
00462 }
00463
00464
00465 if($showLink) {
00466 $filename = wfEscapeWikiText( $this->displayImg->getName() );
00467 $medialink = "[[Media:$filename|$filename]]";
00468
00469 if( !$this->displayImg->isSafeFile() ) {
00470 $warning = wfMsgNoTrans( 'mediawarning' );
00471 $wgOut->addWikiText( <<<EOT
00472 <div class="fullMedia">
00473 <span class="dangerousLink">{$medialink}</span>$dirmark
00474 <span class="fileInfo">$longDesc</span>
00475 </div>
00476 <div class="mediaWarning">$warning</div>
00477 EOT
00478 );
00479 } else {
00480 $wgOut->addWikiText( <<<EOT
00481 <div class="fullMedia">
00482 {$medialink}{$dirmark}
00483 <span class="fileInfo">$longDesc</span>
00484 </div>
00485 EOT
00486 );
00487 }
00488 }
00489
00490 if( !$this->displayImg->isLocal() ) {
00491 $this->printSharedImageText();
00492 }
00493 } else {
00494 # Image does not exist
00495 if ( $wgEnableUploads && $wgUser->isAllowed( 'upload' ) ) {
00496
00497 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
00498 $nofile = array(
00499 'filepage-nofile-link',
00500 $uploadTitle->getFullUrl( array( 'wpDestFile' => $this->img->getName() ) )
00501 );
00502 }
00503 else
00504 {
00505 $nofile = 'filepage-nofile';
00506 }
00507 $wgOut->setRobotPolicy( 'noindex,nofollow' );
00508 $wgOut->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile );
00509 }
00510 }
00511
00515 protected function printSharedImageText() {
00516 global $wgOut;
00517
00518 $this->loadFile();
00519
00520 $descUrl = $this->img->getDescriptionUrl();
00521 $descText = $this->img->getDescriptionText();
00522
00523 $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n";
00524 $repo = $this->img->getRepo()->getDisplayName();
00525
00526 $msg = '';
00527 if( $descUrl && $descText && wfMsgNoTrans( 'sharedupload-desc-here' ) !== '-' ) {
00528 $wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) );
00529 } elseif ( $descUrl && wfMsgNoTrans( 'sharedupload-desc-there' ) !== '-' ) {
00530 $wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) );
00531 } else {
00532 $wgOut->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), '' );
00533 }
00534
00535 if( $descText ) {
00536 $this->mExtraDescription = $descText;
00537 }
00538 }
00539
00540 public function getUploadUrl() {
00541 $this->loadFile();
00542 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
00543 return $uploadTitle->getFullUrl( array(
00544 'wpDestFile' => $this->img->getName(),
00545 'wpForReUpload' => 1
00546 ) );
00547 }
00548
00553 protected function uploadLinksBox() {
00554 global $wgUser, $wgOut, $wgEnableUploads, $wgUseExternalEditor;
00555
00556 if( !$wgEnableUploads ) { return; }
00557
00558 $this->loadFile();
00559 if( !$this->img->isLocal() )
00560 return;
00561
00562 $sk = $wgUser->getSkin();
00563
00564 $wgOut->addHTML( "<br /><ul>\n" );
00565
00566 # "Upload a new version of this file" link
00567 if( UploadBase::userCanReUpload($wgUser,$this->img->name) ) {
00568 $ulink = $sk->makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) );
00569 $wgOut->addHTML( "<li id=\"mw-imagepage-reupload-link\"><div class=\"plainlinks\">{$ulink}</div></li>\n" );
00570 }
00571
00572 # External editing link
00573 if ( $wgUseExternalEditor ) {
00574 $elink = $sk->link(
00575 $this->mTitle,
00576 wfMsgHtml( 'edit-externally' ),
00577 array(),
00578 array(
00579 'action' => 'edit',
00580 'externaledit' => 'true',
00581 'mode' => 'file'
00582 ),
00583 array( 'known', 'noclasses' )
00584 );
00585 $wgOut->addHTML( '<li id="mw-imagepage-edit-external">' . $elink . ' <small>' . wfMsgExt( 'edit-externally-help', array( 'parseinline' ) ) . "</small></li>\n" );
00586 }
00587
00588 $wgOut->addHTML( "</ul>\n" );
00589 }
00590
00591 protected function closeShowImage() {} # For overloading
00592
00597 protected function imageHistory() {
00598 global $wgOut;
00599
00600 $this->loadFile();
00601 $pager = new ImageHistoryPseudoPager( $this );
00602 $wgOut->addHTML( $pager->getBody() );
00603 $wgOut->preventClickjacking( $pager->getPreventClickjacking() );
00604
00605 $this->img->resetHistory();
00606
00607 # Exist check because we don't want to show this on pages where an image
00608 # doesn't exist along with the noimage message, that would suck. -ævar
00609 if( $this->img->exists() ) {
00610 $this->uploadLinksBox();
00611 }
00612 }
00613
00614 protected function imageLinks() {
00615 global $wgUser, $wgOut, $wgLang;
00616
00617 $limit = 100;
00618
00619 $dbr = wfGetDB( DB_SLAVE );
00620
00621 $res = $dbr->select(
00622 array( 'imagelinks', 'page' ),
00623 array( 'page_namespace', 'page_title' ),
00624 array( 'il_to' => $this->mTitle->getDBkey(), 'il_from = page_id' ),
00625 __METHOD__,
00626 array( 'LIMIT' => $limit + 1)
00627 );
00628 $count = $dbr->numRows( $res );
00629 if( $count == 0 ) {
00630 $wgOut->addHTML( "<div id='mw-imagepage-nolinkstoimage'>\n" );
00631 $wgOut->addWikiMsg( 'nolinkstoimage' );
00632 $wgOut->addHTML( "</div>\n" );
00633 return;
00634 }
00635
00636 $wgOut->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
00637 if( $count <= $limit - 1 ) {
00638 $wgOut->addWikiMsg( 'linkstoimage', $count );
00639 } else {
00640
00641 $wgOut->addWikiMsg( 'linkstoimage-more',
00642 $wgLang->formatNum( $limit ),
00643 $this->mTitle->getPrefixedDBkey()
00644 );
00645 }
00646
00647 $wgOut->addHTML( "<ul class='mw-imagepage-linkstoimage'>\n" );
00648 $sk = $wgUser->getSkin();
00649 $count = 0;
00650 while ( $s = $res->fetchObject() ) {
00651 $count++;
00652 if( $count <= $limit ) {
00653
00654 $link = $sk->link(
00655 Title::makeTitle( $s->page_namespace, $s->page_title ),
00656 null,
00657 array(),
00658 array(),
00659 array( 'known', 'noclasses' )
00660 );
00661 $wgOut->addHTML( "<li>{$link}</li>\n" );
00662 }
00663 }
00664 $wgOut->addHTML( "</ul>\n" );
00665 $res->free();
00666
00667
00668 if( $count > $limit )
00669 $wgOut->addWikiMsg( 'morelinkstoimage', $this->mTitle->getPrefixedDBkey() );
00670 $wgOut->addHTML( "</div>\n" );
00671 }
00672
00673 protected function imageRedirects() {
00674 global $wgUser, $wgOut, $wgLang;
00675
00676 $redirects = $this->getTitle()->getRedirectsHere( NS_FILE );
00677 if( count( $redirects ) == 0 ) return;
00678
00679 $wgOut->addHTML( "<div id='mw-imagepage-section-redirectstofile'>\n" );
00680 $wgOut->addWikiMsg( 'redirectstofile',
00681 $wgLang->formatNum( count( $redirects ) )
00682 );
00683 $wgOut->addHTML( "<ul class='mw-imagepage-redirectstofile'>\n" );
00684
00685 $sk = $wgUser->getSkin();
00686 foreach ( $redirects as $title ) {
00687 $link = $sk->link(
00688 $title,
00689 null,
00690 array(),
00691 array( 'redirect' => 'no' ),
00692 array( 'known', 'noclasses' )
00693 );
00694 $wgOut->addHTML( "<li>{$link}</li>\n" );
00695 }
00696 $wgOut->addHTML( "</ul></div>\n" );
00697
00698 }
00699
00700 protected function imageDupes() {
00701 global $wgOut, $wgUser, $wgLang;
00702
00703 $this->loadFile();
00704
00705 $dupes = $this->getDuplicates();
00706 if( count( $dupes ) == 0 ) return;
00707
00708 $wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
00709 $wgOut->addWikiMsg( 'duplicatesoffile',
00710 $wgLang->formatNum( count( $dupes ) ), $this->mTitle->getDBkey()
00711 );
00712 $wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
00713
00714 $sk = $wgUser->getSkin();
00715 foreach ( $dupes as $file ) {
00716 $fromSrc = '';
00717 if( $file->isLocal() ) {
00718 $link = $sk->link(
00719 $file->getTitle(),
00720 null,
00721 array(),
00722 array(),
00723 array( 'known', 'noclasses' )
00724 );
00725 } else {
00726 $link = $sk->makeExternalLink( $file->getDescriptionUrl(),
00727 $file->getTitle()->getPrefixedText() );
00728 $fromSrc = wfMsg( 'shared-repo-from', $file->getRepo()->getDisplayName() );
00729 }
00730 $wgOut->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
00731 }
00732 $wgOut->addHTML( "</ul></div>\n" );
00733 }
00734
00738 public function delete() {
00739 global $wgUploadMaintenance;
00740 if( $wgUploadMaintenance && $this->mTitle && $this->mTitle->getNamespace() == NS_FILE ) {
00741 global $wgOut;
00742 $wgOut->wrapWikiMsg( "<div class='error'>\n$1</div>\n", array( 'filedelete-maintenance' ) );
00743 return;
00744 }
00745
00746 $this->loadFile();
00747 if( !$this->img->exists() || !$this->img->isLocal() || $this->img->getRedirected() ) {
00748
00749 Article::delete();
00750 return;
00751 }
00752 $deleter = new FileDeleteForm( $this->img );
00753 $deleter->execute();
00754 }
00755
00759 public function revert() {
00760 $this->loadFile();
00761 $reverter = new FileRevertForm( $this->img );
00762 $reverter->execute();
00763 }
00764
00768 public function doPurge() {
00769 $this->loadFile();
00770 if( $this->img->exists() ) {
00771 wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" );
00772 $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
00773 $update->doUpdate();
00774 $this->img->upgradeRow();
00775 $this->img->purgeCache();
00776 } else {
00777 wfDebug( "ImagePage::doPurge no image for " . $this->img->getName() . "; limiting purge to cache only\n" );
00778
00779
00780 $this->img->purgeCache();
00781 }
00782 parent::doPurge();
00783 }
00784
00788 function showError( $description ) {
00789 global $wgOut;
00790 $wgOut->setPageTitle( wfMsg( "internalerror" ) );
00791 $wgOut->setRobotPolicy( "noindex,nofollow" );
00792 $wgOut->setArticleRelated( false );
00793 $wgOut->enableClientCache( false );
00794 $wgOut->addWikiText( $description );
00795 }
00796
00797 }
00798
00804 class ImageHistoryList {
00805
00806 protected $imagePage, $img, $skin, $title, $repo, $showThumb;
00807 protected $preventClickjacking = false;
00808
00809 public function __construct( $imagePage ) {
00810 global $wgUser, $wgShowArchiveThumbnails;
00811 $this->skin = $wgUser->getSkin();
00812 $this->current = $imagePage->getFile();
00813 $this->img = $imagePage->getDisplayedFile();
00814 $this->title = $imagePage->getTitle();
00815 $this->imagePage = $imagePage;
00816 $this->showThumb = $wgShowArchiveThumbnails && $this->img->canRender();
00817 }
00818
00819 public function getImagePage() {
00820 return $this->imagePage;
00821 }
00822
00823 public function getSkin() {
00824 return $this->skin;
00825 }
00826
00827 public function getFile() {
00828 return $this->img;
00829 }
00830
00831 public function beginImageHistoryList( $navLinks = '' ) {
00832 global $wgOut, $wgUser;
00833 return Xml::element( 'h2', array( 'id' => 'filehistory' ), wfMsg( 'filehist' ) ) . "\n"
00834 . "<div id=\"mw-imagepage-section-filehistory\">\n"
00835 . $wgOut->parse( wfMsgNoTrans( 'filehist-help' ) )
00836 . $navLinks . "\n"
00837 . Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n"
00838 . '<tr><td></td>'
00839 . ( $this->current->isLocal() && ($wgUser->isAllowed('delete') || $wgUser->isAllowed('deletedhistory') ) ? '<td></td>' : '' )
00840 . '<th>' . wfMsgHtml( 'filehist-datetime' ) . '</th>'
00841 . ( $this->showThumb ? '<th>' . wfMsgHtml( 'filehist-thumb' ) . '</th>' : '' )
00842 . '<th>' . wfMsgHtml( 'filehist-dimensions' ) . '</th>'
00843 . '<th>' . wfMsgHtml( 'filehist-user' ) . '</th>'
00844 . '<th>' . wfMsgHtml( 'filehist-comment' ) . '</th>'
00845 . "</tr>\n";
00846 }
00847
00848 public function endImageHistoryList( $navLinks = '' ) {
00849 return "</table>\n$navLinks\n</div>\n";
00850 }
00851
00852 public function imageHistoryLine( $iscur, $file ) {
00853 global $wgUser, $wgLang, $wgContLang, $wgTitle;
00854
00855 $timestamp = wfTimestamp(TS_MW, $file->getTimestamp());
00856 $img = $iscur ? $file->getName() : $file->getArchiveName();
00857 $user = $file->getUser('id');
00858 $usertext = $file->getUser('text');
00859 $description = $file->getDescription();
00860
00861 $local = $this->current->isLocal();
00862 $row = $css = $selected = '';
00863
00864
00865 if( $local && ($wgUser->isAllowed('delete') || $wgUser->isAllowed('deletedhistory') ) ) {
00866 $row .= '<td>';
00867 # Link to remove from history
00868 if( $wgUser->isAllowed( 'delete' ) ) {
00869 $q = array( 'action' => 'delete' );
00870 if( !$iscur )
00871 $q['oldimage'] = $img;
00872 $row .= $this->skin->link(
00873 $this->title,
00874 wfMsgHtml( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' ),
00875 array(), $q, array( 'known' )
00876 );
00877 }
00878 # Link to hide content. Don't show useless link to people who cannot hide revisions.
00879 $canHide = $wgUser->isAllowed( 'deleterevision' );
00880 if( $canHide || ($wgUser->isAllowed('deletedhistory') && $file->getVisibility()) ) {
00881 if( $wgUser->isAllowed('delete') ) {
00882 $row .= '<br />';
00883 }
00884
00885 if( $iscur || !$file->userCan(File::DELETED_RESTRICTED) ) {
00886 $del = $this->skin->revDeleteLinkDisabled( $canHide );
00887 } else {
00888 list( $ts, $name ) = explode( '!', $img, 2 );
00889 $query = array(
00890 'type' => 'oldimage',
00891 'target' => $wgTitle->getPrefixedText(),
00892 'ids' => $ts,
00893 );
00894 $del = $this->skin->revDeleteLink( $query,
00895 $file->isDeleted(File::DELETED_RESTRICTED), $canHide );
00896 }
00897 $row .= $del;
00898 }
00899 $row .= '</td>';
00900 }
00901
00902
00903 $row .= '<td>';
00904 if( $iscur ) {
00905 $row .= wfMsgHtml( 'filehist-current' );
00906 } elseif( $local && $wgUser->isLoggedIn() && $this->title->userCan( 'edit' ) ) {
00907 if( $file->isDeleted(File::DELETED_FILE) ) {
00908 $row .= wfMsgHtml('filehist-revert');
00909 } else {
00910 $row .= $this->skin->link(
00911 $this->title,
00912 wfMsgHtml( 'filehist-revert' ),
00913 array(),
00914 array(
00915 'action' => 'revert',
00916 'oldimage' => $img,
00917 'wpEditToken' => $wgUser->editToken( $img )
00918 ),
00919 array( 'known', 'noclasses' )
00920 );
00921 }
00922 }
00923 $row .= '</td>';
00924
00925
00926 if( $file->getTimestamp() === $this->img->getTimestamp() ) {
00927 $selected = "class='filehistory-selected'";
00928 }
00929 $row .= "<td $selected style='white-space: nowrap;'>";
00930 if( !$file->userCan(File::DELETED_FILE) ) {
00931 # Don't link to unviewable files
00932 $row .= '<span class="history-deleted">' . $wgLang->timeAndDate( $timestamp, true ) . '</span>';
00933 } elseif( $file->isDeleted(File::DELETED_FILE) ) {
00934 $this->preventClickjacking();
00935 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
00936 # Make a link to review the image
00937 $url = $this->skin->link(
00938 $revdel,
00939 $wgLang->timeAndDate( $timestamp, true ),
00940 array(),
00941 array(
00942 'target' => $wgTitle->getPrefixedText(),
00943 'file' => $img,
00944 'token' => $wgUser->editToken( $img )
00945 ),
00946 array( 'known', 'noclasses' )
00947 );
00948 $row .= '<span class="history-deleted">'.$url.'</span>';
00949 } else {
00950 $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
00951 $row .= Xml::element( 'a', array( 'href' => $url ), $wgLang->timeAndDate( $timestamp, true ) );
00952 }
00953 $row .= "</td>";
00954
00955
00956 if ( $this->showThumb ) {
00957 $row .= '<td>' . $this->getThumbForLine( $file ) . '</td>';
00958 }
00959
00960
00961 $row .= '<td>';
00962 $row .= htmlspecialchars( $file->getDimensionsString() );
00963 $row .= " <span style='white-space: nowrap;'>(" . $this->skin->formatSize( $file->getSize() ) . ')</span>';
00964 $row .= '</td>';
00965
00966
00967 $row .= '<td>';
00968 if( $local ) {
00969
00970 if( $file->isDeleted(File::DELETED_USER) ) {
00971 $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
00972 } else {
00973 $row .= $this->skin->userLink( $user, $usertext ) . " <span style='white-space: nowrap;'>" .
00974 $this->skin->userToolLinks( $user, $usertext ) . "</span>";
00975 }
00976 } else {
00977 $row .= htmlspecialchars( $usertext );
00978 }
00979 $row .= '</td><td>';
00980
00981
00982 if( $file->isDeleted(File::DELETED_COMMENT) ) {
00983 $row .= '<span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
00984 } else {
00985 $row .= $this->skin->commentBlock( $description, $this->title );
00986 }
00987 $row .= '</td>';
00988
00989 wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
00990 $classAttr = $rowClass ? " class='$rowClass'" : "";
00991
00992 return "<tr{$classAttr}>{$row}</tr>\n";
00993 }
00994
00995 protected function getThumbForLine( $file ) {
00996 global $wgLang;
00997
00998 if( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE ) && !$file->isDeleted( File::DELETED_FILE ) ) {
00999 $params = array(
01000 'width' => '120',
01001 'height' => '120',
01002 );
01003 $timestamp = wfTimestamp(TS_MW, $file->getTimestamp());
01004
01005 $thumbnail = $file->transform( $params );
01006 $options = array(
01007 'alt' => wfMsg( 'filehist-thumbtext',
01008 $wgLang->timeAndDate( $timestamp, true ),
01009 $wgLang->date( $timestamp, true ),
01010 $wgLang->time( $timestamp, true ) ),
01011 'file-link' => true,
01012 );
01013
01014 if ( !$thumbnail ) return wfMsgHtml( 'filehist-nothumb' );
01015
01016 return $thumbnail->toHtml( $options );
01017 } else {
01018 return wfMsgHtml( 'filehist-nothumb' );
01019 }
01020 }
01021
01022 protected function preventClickjacking( $enable = true ) {
01023 $this->preventClickjacking = $enable;
01024 }
01025
01026 public function getPreventClickjacking() {
01027 return $this->preventClickjacking;
01028 }
01029 }
01030
01031 class ImageHistoryPseudoPager extends ReverseChronologicalPager {
01032 protected $preventClickjacking = false;
01033
01034 function __construct( $imagePage ) {
01035 parent::__construct();
01036 $this->mImagePage = $imagePage;
01037 $this->mTitle = clone( $imagePage->getTitle() );
01038 $this->mTitle->setFragment( '#filehistory' );
01039 $this->mImg = null;
01040 $this->mHist = array();
01041 $this->mRange = array( 0, 0 );
01042 }
01043
01044 function getTitle() {
01045 return $this->mTitle;
01046 }
01047
01048 function getQueryInfo() {
01049 return false;
01050 }
01051
01052 function getIndexField() {
01053 return '';
01054 }
01055
01056 function formatRow( $row ) {
01057 return '';
01058 }
01059
01060 function getBody() {
01061 $s = '';
01062 $this->doQuery();
01063 if( count($this->mHist) ) {
01064 $list = new ImageHistoryList( $this->mImagePage );
01065 # Generate prev/next links
01066 $navLink = $this->getNavigationBar();
01067 $s = $list->beginImageHistoryList($navLink);
01068
01069 for( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
01070 $file = $this->mHist[$i];
01071 $s .= $list->imageHistoryLine( !$file->isOld(), $file );
01072 }
01073 $s .= $list->endImageHistoryList($navLink);
01074
01075 if ( $list->getPreventClickjacking() ) {
01076 $this->preventClickjacking();
01077 }
01078 }
01079 return $s;
01080 }
01081
01082 function doQuery() {
01083 if( $this->mQueryDone ) return;
01084 $this->mImg = $this->mImagePage->getFile();
01085 if( !$this->mImg->exists() ) {
01086 return;
01087 }
01088 $queryLimit = $this->mLimit + 1;
01089 if( $this->mIsBackwards ) {
01090
01091 $this->mHist = $this->mImg->getHistory($queryLimit,null,$this->mOffset,false);
01092
01093 $numRows = count($this->mHist);
01094 if( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
01095 $this->mHist = array_merge( array($this->mImg), $this->mHist );
01096 }
01097 } else {
01098
01099 if( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
01100 $this->mHist[] = $this->mImg;
01101 }
01102
01103 $oiLimit = count($this->mHist) ? $this->mLimit : $this->mLimit+1;
01104
01105 $this->mHist = array_merge( $this->mHist,
01106 $this->mImg->getHistory($oiLimit,$this->mOffset,null,false) );
01107 }
01108 $numRows = count($this->mHist);
01109 if( $numRows ) {
01110 # Index value of top item in the list
01111 $firstIndex = $this->mIsBackwards ?
01112 $this->mHist[$numRows-1]->getTimestamp() : $this->mHist[0]->getTimestamp();
01113 # Discard the extra result row if there is one
01114 if( $numRows > $this->mLimit && $numRows > 1 ) {
01115 if( $this->mIsBackwards ) {
01116 # Index value of item past the index
01117 $this->mPastTheEndIndex = $this->mHist[0]->getTimestamp();
01118 # Index value of bottom item in the list
01119 $lastIndex = $this->mHist[1]->getTimestamp();
01120 # Display range
01121 $this->mRange = array( 1, $numRows-1 );
01122 } else {
01123 # Index value of item past the index
01124 $this->mPastTheEndIndex = $this->mHist[$numRows-1]->getTimestamp();
01125 # Index value of bottom item in the list
01126 $lastIndex = $this->mHist[$numRows-2]->getTimestamp();
01127 # Display range
01128 $this->mRange = array( 0, $numRows-2 );
01129 }
01130 } else {
01131 # Setting indexes to an empty string means that they will be
01132 # omitted if they would otherwise appear in URLs. It just so
01133 # happens that this is the right thing to do in the standard
01134 # UI, in all the relevant cases.
01135 $this->mPastTheEndIndex = '';
01136 # Index value of bottom item in the list
01137 $lastIndex = $this->mIsBackwards ?
01138 $this->mHist[0]->getTimestamp() : $this->mHist[$numRows-1]->getTimestamp();
01139 # Display range
01140 $this->mRange = array( 0, $numRows-1 );
01141 }
01142 } else {
01143 $firstIndex = '';
01144 $lastIndex = '';
01145 $this->mPastTheEndIndex = '';
01146 }
01147 if( $this->mIsBackwards ) {
01148 $this->mIsFirst = ( $numRows < $queryLimit );
01149 $this->mIsLast = ( $this->mOffset == '' );
01150 $this->mLastShown = $firstIndex;
01151 $this->mFirstShown = $lastIndex;
01152 } else {
01153 $this->mIsFirst = ( $this->mOffset == '' );
01154 $this->mIsLast = ( $numRows < $queryLimit );
01155 $this->mLastShown = $lastIndex;
01156 $this->mFirstShown = $firstIndex;
01157 }
01158 $this->mQueryDone = true;
01159 }
01160
01161 protected function preventClickjacking( $enable = true ) {
01162 $this->preventClickjacking = $enable;
01163 }
01164
01165 public function getPreventClickjacking() {
01166 return $this->preventClickjacking;
01167 }
01168
01169 }