MediaWiki
REL1_19
|
00001 <?php 00007 class ImagePage extends Article { 00008 00012 private $displayImg; 00016 private $repo; 00017 private $fileLoaded; 00018 00019 var $mExtraDescription = false; 00020 00025 protected function newPage( Title $title ) { 00026 // Overload mPage with a file-specific page 00027 return new WikiFilePage( $title ); 00028 } 00029 00034 public static function newFromID( $id ) { 00035 $t = Title::newFromID( $id ); 00036 # @todo FIXME: Doesn't inherit right 00037 return $t == null ? null : new self( $t ); 00038 # return $t == null ? null : new static( $t ); // PHP 5.3 00039 } 00040 00045 public function setFile( $file ) { 00046 $this->mPage->setFile( $file ); 00047 $this->displayImg = $file; 00048 $this->fileLoaded = true; 00049 } 00050 00051 protected function loadFile() { 00052 if ( $this->fileLoaded ) { 00053 return true; 00054 } 00055 $this->fileLoaded = true; 00056 00057 $this->displayImg = $img = false; 00058 wfRunHooks( 'ImagePageFindFile', array( $this, &$img, &$this->displayImg ) ); 00059 if ( !$img ) { // not set by hook? 00060 $img = wfFindFile( $this->getTitle() ); 00061 if ( !$img ) { 00062 $img = wfLocalFile( $this->getTitle() ); 00063 } 00064 } 00065 $this->mPage->setFile( $img ); 00066 if ( !$this->displayImg ) { // not set by hook? 00067 $this->displayImg = $img; 00068 } 00069 $this->repo = $img->getRepo(); 00070 } 00071 00076 public function render() { 00077 global $wgOut; 00078 $wgOut->setArticleBodyOnly( true ); 00079 parent::view(); 00080 } 00081 00082 public function view() { 00083 global $wgOut, $wgShowEXIF, $wgRequest, $wgUser; 00084 00085 $diff = $wgRequest->getVal( 'diff' ); 00086 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) ); 00087 00088 if ( $this->getTitle()->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) ) { 00089 return parent::view(); 00090 } 00091 00092 $this->loadFile(); 00093 00094 if ( $this->getTitle()->getNamespace() == NS_FILE && $this->mPage->getFile()->getRedirected() ) { 00095 if ( $this->getTitle()->getDBkey() == $this->mPage->getFile()->getName() || isset( $diff ) ) { 00096 // mTitle is the same as the redirect target so ask Article 00097 // to perform the redirect for us. 00098 $wgRequest->setVal( 'diffonly', 'true' ); 00099 return parent::view(); 00100 } else { 00101 // mTitle is not the same as the redirect target so it is 00102 // probably the redirect page itself. Fake the redirect symbol 00103 $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() ); 00104 $wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->mPage->getFile()->getName() ), 00105 /* $appendSubtitle */ true, /* $forceKnown */ true ) ); 00106 $this->mPage->doViewUpdates( $this->getContext()->getUser() ); 00107 return; 00108 } 00109 } 00110 00111 if ( $wgShowEXIF && $this->displayImg->exists() ) { 00112 // @todo FIXME: Bad interface, see note on MediaHandler::formatMetadata(). 00113 $formattedMetadata = $this->displayImg->formatMetadata(); 00114 $showmeta = $formattedMetadata !== false; 00115 } else { 00116 $showmeta = false; 00117 } 00118 00119 if ( !$diff && $this->displayImg->exists() ) { 00120 $wgOut->addHTML( $this->showTOC( $showmeta ) ); 00121 } 00122 00123 if ( !$diff ) { 00124 $this->openShowImage(); 00125 } 00126 00127 # No need to display noarticletext, we use our own message, output in openShowImage() 00128 if ( $this->mPage->getID() ) { 00129 # NS_FILE is in the user language, but this section (the actual wikitext) 00130 # should be in page content language 00131 $pageLang = $this->getTitle()->getPageLanguage(); 00132 $wgOut->addHTML( Xml::openElement( 'div', array( 'id' => 'mw-imagepage-content', 00133 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(), 00134 'class' => 'mw-content-'.$pageLang->getDir() ) ) ); 00135 parent::view(); 00136 $wgOut->addHTML( Xml::closeElement( 'div' ) ); 00137 } else { 00138 # Just need to set the right headers 00139 $wgOut->setArticleFlag( true ); 00140 $wgOut->setPageTitle( $this->getTitle()->getPrefixedText() ); 00141 $this->mPage->doViewUpdates( $this->getContext()->getUser() ); 00142 } 00143 00144 # Show shared description, if needed 00145 if ( $this->mExtraDescription ) { 00146 $fol = wfMessage( 'shareddescriptionfollows' ); 00147 if ( !$fol->isDisabled() ) { 00148 $wgOut->addWikiText( $fol->plain() ); 00149 } 00150 $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . "</div>\n" ); 00151 } 00152 00153 $this->closeShowImage(); 00154 $this->imageHistory(); 00155 // TODO: Cleanup the following 00156 00157 $wgOut->addHTML( Xml::element( 'h2', 00158 array( 'id' => 'filelinks' ), 00159 wfMsg( 'imagelinks' ) ) . "\n" ); 00160 $this->imageDupes(); 00161 # @todo FIXME: For some freaky reason, we can't redirect to foreign images. 00162 # Yet we return metadata about the target. Definitely an issue in the FileRepo 00163 $this->imageLinks(); 00164 00165 # Allow extensions to add something after the image links 00166 $html = ''; 00167 wfRunHooks( 'ImagePageAfterImageLinks', array( $this, &$html ) ); 00168 if ( $html ) { 00169 $wgOut->addHTML( $html ); 00170 } 00171 00172 if ( $showmeta ) { 00173 $wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), wfMsg( 'metadata' ) ) . "\n" ); 00174 $wgOut->addWikiText( $this->makeMetadataTable( $formattedMetadata ) ); 00175 $wgOut->addModules( array( 'mediawiki.action.view.metadata' ) ); 00176 } 00177 00178 // Add remote Filepage.css 00179 if( !$this->repo->isLocal() ) { 00180 $css = $this->repo->getDescriptionStylesheetUrl(); 00181 if ( $css ) { 00182 $wgOut->addStyle( $css ); 00183 } 00184 } 00185 // always show the local local Filepage.css, bug 29277 00186 $wgOut->addModuleStyles( 'filepage' ); 00187 } 00188 00192 public function getDisplayedFile() { 00193 $this->loadFile(); 00194 return $this->displayImg; 00195 } 00196 00203 protected function showTOC( $metadata ) { 00204 $r = array( 00205 '<li><a href="#file">' . wfMsgHtml( 'file-anchor-link' ) . '</a></li>', 00206 '<li><a href="#filehistory">' . wfMsgHtml( 'filehist' ) . '</a></li>', 00207 '<li><a href="#filelinks">' . wfMsgHtml( 'imagelinks' ) . '</a></li>', 00208 ); 00209 if ( $metadata ) { 00210 $r[] = '<li><a href="#metadata">' . wfMsgHtml( 'metadata' ) . '</a></li>'; 00211 } 00212 00213 wfRunHooks( 'ImagePageShowTOC', array( $this, &$r ) ); 00214 00215 return '<ul id="filetoc">' . implode( "\n", $r ) . '</ul>'; 00216 } 00217 00226 protected function makeMetadataTable( $metadata ) { 00227 $r = "<div class=\"mw-imagepage-section-metadata\">"; 00228 $r .= wfMsgNoTrans( 'metadata-help' ); 00229 $r .= "<table id=\"mw_metadata\" class=\"mw_metadata\">\n"; 00230 foreach ( $metadata as $type => $stuff ) { 00231 foreach ( $stuff as $v ) { 00232 # @todo FIXME: Why is this using escapeId for a class?! 00233 $class = Sanitizer::escapeId( $v['id'] ); 00234 if ( $type == 'collapsed' ) { 00235 $class .= ' collapsable'; 00236 } 00237 $r .= "<tr class=\"$class\">\n"; 00238 $r .= "<th>{$v['name']}</th>\n"; 00239 $r .= "<td>{$v['value']}</td>\n</tr>"; 00240 } 00241 } 00242 $r .= "</table>\n</div>\n"; 00243 return $r; 00244 } 00245 00253 public function getContent() { 00254 $this->loadFile(); 00255 if ( $this->mPage->getFile() && !$this->mPage->getFile()->isLocal() && 0 == $this->getID() ) { 00256 return ''; 00257 } 00258 return parent::getContent(); 00259 } 00260 00261 protected function openShowImage() { 00262 global $wgOut, $wgUser, $wgImageLimits, $wgRequest, 00263 $wgLang, $wgEnableUploads, $wgSend404Code; 00264 00265 $this->loadFile(); 00266 00267 $sizeSel = intval( $wgUser->getOption( 'imagesize' ) ); 00268 if ( !isset( $wgImageLimits[$sizeSel] ) ) { 00269 $sizeSel = User::getDefaultOption( 'imagesize' ); 00270 00271 // The user offset might still be incorrect, specially if 00272 // $wgImageLimits got changed (see bug #8858). 00273 if ( !isset( $wgImageLimits[$sizeSel] ) ) { 00274 // Default to the first offset in $wgImageLimits 00275 $sizeSel = 0; 00276 } 00277 } 00278 $max = $wgImageLimits[$sizeSel]; 00279 $maxWidth = $max[0]; 00280 $maxHeight = $max[1]; 00281 $dirmark = $wgLang->getDirMark(); 00282 00283 if ( $this->displayImg->exists() ) { 00284 # image 00285 $page = $wgRequest->getIntOrNull( 'page' ); 00286 if ( is_null( $page ) ) { 00287 $params = array(); 00288 $page = 1; 00289 } else { 00290 $params = array( 'page' => $page ); 00291 } 00292 $width_orig = $this->displayImg->getWidth( $page ); 00293 $width = $width_orig; 00294 $height_orig = $this->displayImg->getHeight( $page ); 00295 $height = $height_orig; 00296 00297 $longDesc = wfMsg( 'parentheses', $this->displayImg->getLongDesc() ); 00298 00299 wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this, &$wgOut ) ); 00300 00301 if ( $this->displayImg->allowInlineDisplay() ) { 00302 # image 00303 00304 # "Download high res version" link below the image 00305 # $msgsize = wfMsgHtml( 'file-info-size', $width_orig, $height_orig, Linker::formatSize( $this->displayImg->getSize() ), $mime ); 00306 # We'll show a thumbnail of this image 00307 if ( $width > $maxWidth || $height > $maxHeight ) { 00308 # Calculate the thumbnail size. 00309 # First case, the limiting factor is the width, not the height. 00310 if ( $width / $height >= $maxWidth / $maxHeight ) { 00311 $height = round( $height * $maxWidth / $width ); 00312 $width = $maxWidth; 00313 # Note that $height <= $maxHeight now. 00314 } else { 00315 $newwidth = floor( $width * $maxHeight / $height ); 00316 $height = round( $height * $newwidth / $width ); 00317 $width = $newwidth; 00318 # Note that $height <= $maxHeight now, but might not be identical 00319 # because of rounding. 00320 } 00321 $msgbig = wfMsgHtml( 'show-big-image' ); 00322 $otherSizes = array(); 00323 foreach ( $wgImageLimits as $size ) { 00324 if ( $size[0] < $width_orig && $size[1] < $height_orig && 00325 $size[0] != $width && $size[1] != $height ) { 00326 $otherSizes[] = $this->makeSizeLink( $params, $size[0], $size[1] ); 00327 } 00328 } 00329 $msgsmall = wfMessage( 'show-big-image-preview' )-> 00330 rawParams( $this->makeSizeLink( $params, $width, $height ) )-> 00331 parse(); 00332 if ( count( $otherSizes ) && $this->displayImg->getRepo()->canTransformVia404() ) { 00333 $msgsmall .= ' ' . 00334 Html::rawElement( 'span', array( 'class' => 'mw-filepage-other-resolutions' ), 00335 wfMessage( 'show-big-image-other' )->rawParams( $wgLang->pipeList( $otherSizes ) )-> 00336 params( count( $otherSizes ) )->parse() 00337 ); 00338 } 00339 } elseif ( $width == 0 && $height == 0 ){ 00340 # Some sort of audio file that doesn't have dimensions 00341 # Don't output a no hi res message for such a file 00342 $msgsmall = ''; 00343 } else { 00344 # Image is small enough to show full size on image page 00345 $msgsmall = wfMessage( 'file-nohires' )->parse(); 00346 } 00347 00348 $params['width'] = $width; 00349 $params['height'] = $height; 00350 $thumbnail = $this->displayImg->transform( $params ); 00351 00352 $showLink = true; 00353 $anchorclose = Html::rawElement( 'div', array( 'class' => 'mw-filepage-resolutioninfo' ), $msgsmall ); 00354 00355 $isMulti = $this->displayImg->isMultipage() && $this->displayImg->pageCount() > 1; 00356 if ( $isMulti ) { 00357 $wgOut->addHTML( '<table class="multipageimage"><tr><td>' ); 00358 } 00359 00360 if ( $thumbnail ) { 00361 $options = array( 00362 'alt' => $this->displayImg->getTitle()->getPrefixedText(), 00363 'file-link' => true, 00364 ); 00365 $wgOut->addHTML( '<div class="fullImageLink" id="file">' . 00366 $thumbnail->toHtml( $options ) . 00367 $anchorclose . "</div>\n" ); 00368 } 00369 00370 if ( $isMulti ) { 00371 $count = $this->displayImg->pageCount(); 00372 00373 if ( $page > 1 ) { 00374 $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false ); 00375 $link = Linker::link( 00376 $this->getTitle(), 00377 $label, 00378 array(), 00379 array( 'page' => $page - 1 ), 00380 array( 'known', 'noclasses' ) 00381 ); 00382 $thumb1 = Linker::makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none', 00383 array( 'page' => $page - 1 ) ); 00384 } else { 00385 $thumb1 = ''; 00386 } 00387 00388 if ( $page < $count ) { 00389 $label = wfMsg( 'imgmultipagenext' ); 00390 $link = Linker::link( 00391 $this->getTitle(), 00392 $label, 00393 array(), 00394 array( 'page' => $page + 1 ), 00395 array( 'known', 'noclasses' ) 00396 ); 00397 $thumb2 = Linker::makeThumbLinkObj( $this->getTitle(), $this->displayImg, $link, $label, 'none', 00398 array( 'page' => $page + 1 ) ); 00399 } else { 00400 $thumb2 = ''; 00401 } 00402 00403 global $wgScript; 00404 00405 $formParams = array( 00406 'name' => 'pageselector', 00407 'action' => $wgScript, 00408 'onchange' => 'document.pageselector.submit();', 00409 ); 00410 $options = array(); 00411 for ( $i = 1; $i <= $count; $i++ ) { 00412 $options[] = Xml::option( $wgLang->formatNum( $i ), $i, $i == $page ); 00413 } 00414 $select = Xml::tags( 'select', 00415 array( 'id' => 'pageselector', 'name' => 'page' ), 00416 implode( "\n", $options ) ); 00417 00418 $wgOut->addHTML( 00419 '</td><td><div class="multipageimagenavbox">' . 00420 Xml::openElement( 'form', $formParams ) . 00421 Html::hidden( 'title', $this->getTitle()->getPrefixedDBkey() ) . 00422 wfMsgExt( 'imgmultigoto', array( 'parseinline', 'replaceafter' ), $select ) . 00423 Xml::submitButton( wfMsg( 'imgmultigo' ) ) . 00424 Xml::closeElement( 'form' ) . 00425 "<hr />$thumb1\n$thumb2<br style=\"clear: both\" /></div></td></tr></table>" 00426 ); 00427 } 00428 } else { 00429 # if direct link is allowed but it's not a renderable image, show an icon. 00430 if ( $this->displayImg->isSafeFile() ) { 00431 $icon = $this->displayImg->iconThumb(); 00432 00433 $wgOut->addHTML( '<div class="fullImageLink" id="file">' . 00434 $icon->toHtml( array( 'file-link' => true ) ) . 00435 "</div>\n" ); 00436 } 00437 00438 $showLink = true; 00439 } 00440 00441 if ( $showLink ) { 00442 $filename = wfEscapeWikiText( $this->displayImg->getName() ); 00443 $linktext = $filename; 00444 if ( isset( $msgbig ) ) { 00445 $linktext = wfEscapeWikiText( $msgbig ); 00446 } 00447 $medialink = "[[Media:$filename|$linktext]]"; 00448 00449 if ( !$this->displayImg->isSafeFile() ) { 00450 $warning = wfMsgNoTrans( 'mediawarning' ); 00451 $wgOut->addWikiText( <<<EOT 00452 <div class="fullMedia"><span class="dangerousLink">{$medialink}</span>$dirmark <span class="fileInfo">$longDesc</span></div> 00453 <div class="mediaWarning">$warning</div> 00454 EOT 00455 ); 00456 } else { 00457 $wgOut->addWikiText( <<<EOT 00458 <div class="fullMedia">{$medialink}{$dirmark} <span class="fileInfo">$longDesc</span> 00459 </div> 00460 EOT 00461 ); 00462 } 00463 } 00464 00465 if ( !$this->displayImg->isLocal() ) { 00466 $this->printSharedImageText(); 00467 } 00468 } else { 00469 # Image does not exist 00470 if ( $wgEnableUploads && $wgUser->isAllowed( 'upload' ) ) { 00471 // Only show an upload link if the user can upload 00472 $uploadTitle = SpecialPage::getTitleFor( 'Upload' ); 00473 $nofile = array( 00474 'filepage-nofile-link', 00475 $uploadTitle->getFullURL( array( 'wpDestFile' => $this->mPage->getFile()->getName() ) ) 00476 ); 00477 } else { 00478 $nofile = 'filepage-nofile'; 00479 } 00480 // Note, if there is an image description page, but 00481 // no image, then this setRobotPolicy is overriden 00482 // by Article::View(). 00483 $wgOut->setRobotPolicy( 'noindex,nofollow' ); 00484 $wgOut->wrapWikiMsg( "<div id='mw-imagepage-nofile' class='plainlinks'>\n$1\n</div>", $nofile ); 00485 if ( !$this->getID() && $wgSend404Code ) { 00486 // If there is no image, no shared image, and no description page, 00487 // output a 404, to be consistent with articles. 00488 $wgRequest->response()->header( 'HTTP/1.1 404 Not Found' ); 00489 } 00490 } 00491 $wgOut->setFileVersion( $this->displayImg ); 00492 } 00493 00501 private function makeSizeLink( $params, $width, $height ) { 00502 $params['width'] = $width; 00503 $params['height'] = $height; 00504 $thumbnail = $this->displayImg->transform( $params ); 00505 if ( $thumbnail && !$thumbnail->isError() ) { 00506 return Html::rawElement( 'a', array( 00507 'href' => $thumbnail->getUrl(), 00508 'class' => 'mw-thumbnail-link' 00509 ), wfMessage( 'show-big-image-size' )->numParams( 00510 $thumbnail->getWidth(), $thumbnail->getHeight() 00511 )->parse() ); 00512 } else { 00513 return ''; 00514 } 00515 } 00516 00520 protected function printSharedImageText() { 00521 global $wgOut; 00522 00523 $this->loadFile(); 00524 00525 $descUrl = $this->mPage->getFile()->getDescriptionUrl(); 00526 $descText = $this->mPage->getFile()->getDescriptionText(); 00527 00528 /* Add canonical to head if there is no local page for this shared file */ 00529 if( $descUrl && $this->mPage->getID() == 0 ) { 00530 $wgOut->addLink( array( 'rel' => 'canonical', 'href' => $descUrl ) ); 00531 } 00532 00533 $wrap = "<div class=\"sharedUploadNotice\">\n$1\n</div>\n"; 00534 $repo = $this->mPage->getFile()->getRepo()->getDisplayName(); 00535 00536 if ( $descUrl && $descText && wfMsgNoTrans( 'sharedupload-desc-here' ) !== '-' ) { 00537 $wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-here', $repo, $descUrl ) ); 00538 } elseif ( $descUrl && wfMsgNoTrans( 'sharedupload-desc-there' ) !== '-' ) { 00539 $wgOut->wrapWikiMsg( $wrap, array( 'sharedupload-desc-there', $repo, $descUrl ) ); 00540 } else { 00541 $wgOut->wrapWikiMsg( $wrap, array( 'sharedupload', $repo ), ''/*BACKCOMPAT*/ ); 00542 } 00543 00544 if ( $descText ) { 00545 $this->mExtraDescription = $descText; 00546 } 00547 } 00548 00549 public function getUploadUrl() { 00550 $this->loadFile(); 00551 $uploadTitle = SpecialPage::getTitleFor( 'Upload' ); 00552 return $uploadTitle->getFullURL( array( 00553 'wpDestFile' => $this->mPage->getFile()->getName(), 00554 'wpForReUpload' => 1 00555 ) ); 00556 } 00557 00562 protected function uploadLinksBox() { 00563 global $wgUser, $wgOut, $wgEnableUploads, $wgUseExternalEditor; 00564 00565 if ( !$wgEnableUploads ) { 00566 return; 00567 } 00568 00569 $this->loadFile(); 00570 if ( !$this->mPage->getFile()->isLocal() ) { 00571 return; 00572 } 00573 00574 $wgOut->addHTML( "<br /><ul>\n" ); 00575 00576 # "Upload a new version of this file" link 00577 if ( UploadBase::userCanReUpload( $wgUser, $this->mPage->getFile()->name ) ) { 00578 $ulink = Linker::makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) ); 00579 $wgOut->addHTML( "<li id=\"mw-imagepage-reupload-link\"><div class=\"plainlinks\">{$ulink}</div></li>\n" ); 00580 } 00581 00582 # External editing link 00583 if ( $wgUseExternalEditor ) { 00584 $elink = Linker::link( 00585 $this->getTitle(), 00586 wfMsgHtml( 'edit-externally' ), 00587 array(), 00588 array( 00589 'action' => 'edit', 00590 'externaledit' => 'true', 00591 'mode' => 'file' 00592 ), 00593 array( 'known', 'noclasses' ) 00594 ); 00595 $wgOut->addHTML( 00596 '<li id="mw-imagepage-edit-external">' . $elink . ' <small>' . 00597 wfMsgExt( 'edit-externally-help', array( 'parseinline' ) ) . 00598 "</small></li>\n" 00599 ); 00600 } 00601 00602 $wgOut->addHTML( "</ul>\n" ); 00603 } 00604 00605 protected function closeShowImage() { } # For overloading 00606 00611 protected function imageHistory() { 00612 global $wgOut; 00613 00614 $this->loadFile(); 00615 $pager = new ImageHistoryPseudoPager( $this ); 00616 $wgOut->addHTML( $pager->getBody() ); 00617 $wgOut->preventClickjacking( $pager->getPreventClickjacking() ); 00618 00619 $this->mPage->getFile()->resetHistory(); // free db resources 00620 00621 # Exist check because we don't want to show this on pages where an image 00622 # doesn't exist along with the noimage message, that would suck. -ævar 00623 if ( $this->mPage->getFile()->exists() ) { 00624 $this->uploadLinksBox(); 00625 } 00626 } 00627 00633 protected function queryImageLinks( $target, $limit ) { 00634 $dbr = wfGetDB( DB_SLAVE ); 00635 00636 return $dbr->select( 00637 array( 'imagelinks', 'page' ), 00638 array( 'page_namespace', 'page_title', 'page_is_redirect', 'il_to' ), 00639 array( 'il_to' => $target, 'il_from = page_id' ), 00640 __METHOD__, 00641 array( 'LIMIT' => $limit + 1, 'ORDER BY' => 'il_from', ) 00642 ); 00643 } 00644 00645 protected function imageLinks() { 00646 global $wgOut, $wgLang; 00647 00648 $limit = 100; 00649 00650 $res = $this->queryImageLinks( $this->getTitle()->getDbKey(), $limit + 1); 00651 $rows = array(); 00652 $redirects = array(); 00653 foreach ( $res as $row ) { 00654 if ( $row->page_is_redirect ) { 00655 $redirects[$row->page_title] = array(); 00656 } 00657 $rows[] = $row; 00658 } 00659 $count = count( $rows ); 00660 00661 $hasMore = $count > $limit; 00662 if ( !$hasMore && count( $redirects ) ) { 00663 $res = $this->queryImageLinks( array_keys( $redirects ), 00664 $limit - count( $rows ) + 1 ); 00665 foreach ( $res as $row ) { 00666 $redirects[$row->il_to][] = $row; 00667 $count++; 00668 } 00669 $hasMore = ( $res->numRows() + count( $rows ) ) > $limit; 00670 } 00671 00672 if ( $count == 0 ) { 00673 $wgOut->wrapWikiMsg( 00674 Html::rawElement( 'div', 00675 array( 'id' => 'mw-imagepage-nolinkstoimage' ), "\n$1\n" ), 00676 'nolinkstoimage' 00677 ); 00678 return; 00679 } 00680 00681 $wgOut->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" ); 00682 if ( !$hasMore ) { 00683 $wgOut->addWikiMsg( 'linkstoimage', $count ); 00684 } else { 00685 // More links than the limit. Add a link to [[Special:Whatlinkshere]] 00686 $wgOut->addWikiMsg( 'linkstoimage-more', 00687 $wgLang->formatNum( $limit ), 00688 $this->getTitle()->getPrefixedDBkey() 00689 ); 00690 } 00691 00692 $wgOut->addHTML( 00693 Html::openElement( 'ul', 00694 array( 'class' => 'mw-imagepage-linkstoimage' ) ) . "\n" 00695 ); 00696 $count = 0; 00697 00698 // Sort the list by namespace:title 00699 usort( $rows, array( $this, 'compare' ) ); 00700 00701 // Create links for every element 00702 $currentCount = 0; 00703 foreach( $rows as $element ) { 00704 $currentCount++; 00705 if ( $currentCount > $limit ) { 00706 break; 00707 } 00708 00709 $link = Linker::linkKnown( Title::makeTitle( $element->page_namespace, $element->page_title ) ); 00710 if ( !isset( $redirects[$element->page_title] ) ) { 00711 $liContents = $link; 00712 } else { 00713 $ul = "<ul class='mw-imagepage-redirectstofile'>\n"; 00714 foreach ( $redirects[$element->page_title] as $row ) { 00715 $currentCount++; 00716 if ( $currentCount > $limit ) { 00717 break; 00718 } 00719 00720 $link2 = Linker::linkKnown( Title::makeTitle( $row->page_namespace, $row->page_title ) ); 00721 $ul .= Html::rawElement( 00722 'li', 00723 array( 'id' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ), 00724 $link2 00725 ) . "\n"; 00726 } 00727 $ul .= '</ul>'; 00728 $liContents = wfMessage( 'linkstoimage-redirect' )->rawParams( 00729 $link, $ul )->parse(); 00730 } 00731 $wgOut->addHTML( Html::rawElement( 00732 'li', 00733 array( 'id' => 'mw-imagepage-linkstoimage-ns' . $element->page_namespace ), 00734 $liContents 00735 ) . "\n" 00736 ); 00737 00738 }; 00739 $wgOut->addHTML( Html::closeElement( 'ul' ) . "\n" ); 00740 $res->free(); 00741 00742 // Add a links to [[Special:Whatlinkshere]] 00743 if ( $count > $limit ) { 00744 $wgOut->addWikiMsg( 'morelinkstoimage', $this->getTitle()->getPrefixedDBkey() ); 00745 } 00746 $wgOut->addHTML( Html::closeElement( 'div' ) . "\n" ); 00747 } 00748 00749 protected function imageDupes() { 00750 global $wgOut, $wgLang; 00751 00752 $this->loadFile(); 00753 00754 $dupes = $this->mPage->getDuplicates(); 00755 if ( count( $dupes ) == 0 ) { 00756 return; 00757 } 00758 00759 $wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" ); 00760 $wgOut->addWikiMsg( 'duplicatesoffile', 00761 $wgLang->formatNum( count( $dupes ) ), $this->getTitle()->getDBkey() 00762 ); 00763 $wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" ); 00764 00768 foreach ( $dupes as $file ) { 00769 $fromSrc = ''; 00770 if ( $file->isLocal() ) { 00771 $link = Linker::link( 00772 $file->getTitle(), 00773 null, 00774 array(), 00775 array(), 00776 array( 'known', 'noclasses' ) 00777 ); 00778 } else { 00779 $link = Linker::makeExternalLink( $file->getDescriptionUrl(), 00780 $file->getTitle()->getPrefixedText() ); 00781 $fromSrc = wfMsg( 'shared-repo-from', $file->getRepo()->getDisplayName() ); 00782 } 00783 $wgOut->addHTML( "<li>{$link} {$fromSrc}</li>\n" ); 00784 } 00785 $wgOut->addHTML( "</ul></div>\n" ); 00786 } 00787 00791 public function delete() { 00792 $file = $this->mPage->getFile(); 00793 if ( !$file->exists() || !$file->isLocal() || $file->getRedirected() ) { 00794 // Standard article deletion 00795 parent::delete(); 00796 return; 00797 } 00798 00799 $deleter = new FileDeleteForm( $file ); 00800 $deleter->execute(); 00801 } 00802 00808 function showError( $description ) { 00809 global $wgOut; 00810 $wgOut->setPageTitle( wfMessage( 'internalerror' ) ); 00811 $wgOut->setRobotPolicy( 'noindex,nofollow' ); 00812 $wgOut->setArticleRelated( false ); 00813 $wgOut->enableClientCache( false ); 00814 $wgOut->addWikiText( $description ); 00815 } 00816 00825 protected function compare( $a, $b ) { 00826 if ( $a->page_namespace == $b->page_namespace ) { 00827 return strcmp( $a->page_title, $b->page_title ); 00828 } else { 00829 return $a->page_namespace - $b->page_namespace; 00830 } 00831 } 00832 } 00833 00839 class ImageHistoryList { 00840 00844 protected $title; 00845 00849 protected $img; 00850 00854 protected $imagePage; 00855 00859 protected $current; 00860 00861 protected $repo, $showThumb; 00862 protected $preventClickjacking = false; 00863 00867 public function __construct( $imagePage ) { 00868 global $wgShowArchiveThumbnails; 00869 $this->current = $imagePage->getFile(); 00870 $this->img = $imagePage->getDisplayedFile(); 00871 $this->title = $imagePage->getTitle(); 00872 $this->imagePage = $imagePage; 00873 $this->showThumb = $wgShowArchiveThumbnails && $this->img->canRender(); 00874 } 00875 00879 public function getImagePage() { 00880 return $this->imagePage; 00881 } 00882 00886 public function getFile() { 00887 return $this->img; 00888 } 00889 00894 public function beginImageHistoryList( $navLinks = '' ) { 00895 global $wgOut, $wgUser; 00896 return Xml::element( 'h2', array( 'id' => 'filehistory' ), wfMsg( 'filehist' ) ) . "\n" 00897 . "<div id=\"mw-imagepage-section-filehistory\">\n" 00898 . $wgOut->parse( wfMsgNoTrans( 'filehist-help' ) ) 00899 . $navLinks . "\n" 00900 . Xml::openElement( 'table', array( 'class' => 'wikitable filehistory' ) ) . "\n" 00901 . '<tr><td></td>' 00902 . ( $this->current->isLocal() && ( $wgUser->isAllowedAny( 'delete', 'deletedhistory' ) ) ? '<td></td>' : '' ) 00903 . '<th>' . wfMsgHtml( 'filehist-datetime' ) . '</th>' 00904 . ( $this->showThumb ? '<th>' . wfMsgHtml( 'filehist-thumb' ) . '</th>' : '' ) 00905 . '<th>' . wfMsgHtml( 'filehist-dimensions' ) . '</th>' 00906 . '<th>' . wfMsgHtml( 'filehist-user' ) . '</th>' 00907 . '<th>' . wfMsgHtml( 'filehist-comment' ) . '</th>' 00908 . "</tr>\n"; 00909 } 00910 00915 public function endImageHistoryList( $navLinks = '' ) { 00916 return "</table>\n$navLinks\n</div>\n"; 00917 } 00918 00924 public function imageHistoryLine( $iscur, $file ) { 00925 global $wgUser, $wgLang, $wgContLang; 00926 00927 $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() ); 00928 $img = $iscur ? $file->getName() : $file->getArchiveName(); 00929 $user = $file->getUser( 'id' ); 00930 $usertext = $file->getUser( 'text' ); 00931 $description = $file->getDescription(); 00932 00933 $local = $this->current->isLocal(); 00934 $row = $selected = ''; 00935 00936 // Deletion link 00937 if ( $local && ( $wgUser->isAllowedAny( 'delete', 'deletedhistory' ) ) ) { 00938 $row .= '<td>'; 00939 # Link to remove from history 00940 if ( $wgUser->isAllowed( 'delete' ) ) { 00941 $q = array( 'action' => 'delete' ); 00942 if ( !$iscur ) { 00943 $q['oldimage'] = $img; 00944 } 00945 $row .= Linker::link( 00946 $this->title, 00947 wfMsgHtml( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' ), 00948 array(), $q, array( 'known' ) 00949 ); 00950 } 00951 # Link to hide content. Don't show useless link to people who cannot hide revisions. 00952 $canHide = $wgUser->isAllowed( 'deleterevision' ); 00953 if ( $canHide || ( $wgUser->isAllowed( 'deletedhistory' ) && $file->getVisibility() ) ) { 00954 if ( $wgUser->isAllowed( 'delete' ) ) { 00955 $row .= '<br />'; 00956 } 00957 // If file is top revision or locked from this user, don't link 00958 if ( $iscur || !$file->userCan( File::DELETED_RESTRICTED ) ) { 00959 $del = Linker::revDeleteLinkDisabled( $canHide ); 00960 } else { 00961 list( $ts, $name ) = explode( '!', $img, 2 ); 00962 $query = array( 00963 'type' => 'oldimage', 00964 'target' => $this->title->getPrefixedText(), 00965 'ids' => $ts, 00966 ); 00967 $del = Linker::revDeleteLink( $query, 00968 $file->isDeleted( File::DELETED_RESTRICTED ), $canHide ); 00969 } 00970 $row .= $del; 00971 } 00972 $row .= '</td>'; 00973 } 00974 00975 // Reversion link/current indicator 00976 $row .= '<td>'; 00977 if ( $iscur ) { 00978 $row .= wfMsgHtml( 'filehist-current' ); 00979 } elseif ( $local && $wgUser->isLoggedIn() && $this->title->userCan( 'edit' ) ) { 00980 if ( $file->isDeleted( File::DELETED_FILE ) ) { 00981 $row .= wfMsgHtml( 'filehist-revert' ); 00982 } else { 00983 $row .= Linker::link( 00984 $this->title, 00985 wfMsgHtml( 'filehist-revert' ), 00986 array(), 00987 array( 00988 'action' => 'revert', 00989 'oldimage' => $img, 00990 'wpEditToken' => $wgUser->getEditToken( $img ) 00991 ), 00992 array( 'known', 'noclasses' ) 00993 ); 00994 } 00995 } 00996 $row .= '</td>'; 00997 00998 // Date/time and image link 00999 if ( $file->getTimestamp() === $this->img->getTimestamp() ) { 01000 $selected = "class='filehistory-selected'"; 01001 } 01002 $row .= "<td $selected style='white-space: nowrap;'>"; 01003 if ( !$file->userCan( File::DELETED_FILE ) ) { 01004 # Don't link to unviewable files 01005 $row .= '<span class="history-deleted">' . $wgLang->timeanddate( $timestamp, true ) . '</span>'; 01006 } elseif ( $file->isDeleted( File::DELETED_FILE ) ) { 01007 if ( $local ) { 01008 $this->preventClickjacking(); 01009 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' ); 01010 # Make a link to review the image 01011 $url = Linker::link( 01012 $revdel, 01013 $wgLang->timeanddate( $timestamp, true ), 01014 array(), 01015 array( 01016 'target' => $this->title->getPrefixedText(), 01017 'file' => $img, 01018 'token' => $wgUser->getEditToken( $img ) 01019 ), 01020 array( 'known', 'noclasses' ) 01021 ); 01022 } else { 01023 $url = $wgLang->timeanddate( $timestamp, true ); 01024 } 01025 $row .= '<span class="history-deleted">' . $url . '</span>'; 01026 } else { 01027 $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img ); 01028 $row .= Xml::element( 'a', array( 'href' => $url ), $wgLang->timeanddate( $timestamp, true ) ); 01029 } 01030 $row .= "</td>"; 01031 01032 // Thumbnail 01033 if ( $this->showThumb ) { 01034 $row .= '<td>' . $this->getThumbForLine( $file ) . '</td>'; 01035 } 01036 01037 // Image dimensions + size 01038 $row .= '<td>'; 01039 $row .= htmlspecialchars( $file->getDimensionsString() ); 01040 $row .= ' <span style="white-space: nowrap;">(' . Linker::formatSize( $file->getSize() ) . ')</span>'; 01041 $row .= '</td>'; 01042 01043 // Uploading user 01044 $row .= '<td>'; 01045 // Hide deleted usernames 01046 if ( $file->isDeleted( File::DELETED_USER ) ) { 01047 $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>'; 01048 } else { 01049 if ( $local ) { 01050 $row .= Linker::userLink( $user, $usertext ) . ' <span style="white-space: nowrap;">' . 01051 Linker::userToolLinks( $user, $usertext ) . '</span>'; 01052 } else { 01053 $row .= htmlspecialchars( $usertext ); 01054 } 01055 } 01056 $row .= '</td>'; 01057 01058 // Don't show deleted descriptions 01059 if ( $file->isDeleted( File::DELETED_COMMENT ) ) { 01060 $row .= '<td><span class="history-deleted">' . wfMsgHtml( 'rev-deleted-comment' ) . '</span></td>'; 01061 } else { 01062 $row .= '<td dir="' . $wgContLang->getDir() . '">' . Linker::formatComment( $description, $this->title ) . '</td>'; 01063 } 01064 01065 $rowClass = null; 01066 wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) ); 01067 $classAttr = $rowClass ? " class='$rowClass'" : ''; 01068 01069 return "<tr{$classAttr}>{$row}</tr>\n"; 01070 } 01071 01076 protected function getThumbForLine( $file ) { 01077 global $wgLang; 01078 01079 if ( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE ) && !$file->isDeleted( File::DELETED_FILE ) ) { 01080 $params = array( 01081 'width' => '120', 01082 'height' => '120', 01083 ); 01084 $timestamp = wfTimestamp( TS_MW, $file->getTimestamp() ); 01085 01086 $thumbnail = $file->transform( $params ); 01087 $options = array( 01088 'alt' => wfMsg( 'filehist-thumbtext', 01089 $wgLang->timeanddate( $timestamp, true ), 01090 $wgLang->date( $timestamp, true ), 01091 $wgLang->time( $timestamp, true ) ), 01092 'file-link' => true, 01093 ); 01094 01095 if ( !$thumbnail ) { 01096 return wfMsgHtml( 'filehist-nothumb' ); 01097 } 01098 01099 return $thumbnail->toHtml( $options ); 01100 } else { 01101 return wfMsgHtml( 'filehist-nothumb' ); 01102 } 01103 } 01104 01108 protected function preventClickjacking( $enable = true ) { 01109 $this->preventClickjacking = $enable; 01110 } 01111 01115 public function getPreventClickjacking() { 01116 return $this->preventClickjacking; 01117 } 01118 } 01119 01120 class ImageHistoryPseudoPager extends ReverseChronologicalPager { 01121 protected $preventClickjacking = false; 01122 01126 protected $mImg; 01127 01131 protected $mTitle; 01132 01136 function __construct( $imagePage ) { 01137 parent::__construct(); 01138 $this->mImagePage = $imagePage; 01139 $this->mTitle = clone ( $imagePage->getTitle() ); 01140 $this->mTitle->setFragment( '#filehistory' ); 01141 $this->mImg = null; 01142 $this->mHist = array(); 01143 $this->mRange = array( 0, 0 ); // display range 01144 } 01145 01149 function getTitle() { 01150 return $this->mTitle; 01151 } 01152 01153 function getQueryInfo() { 01154 return false; 01155 } 01156 01160 function getIndexField() { 01161 return ''; 01162 } 01163 01167 function formatRow( $row ) { 01168 return ''; 01169 } 01170 01174 function getBody() { 01175 $s = ''; 01176 $this->doQuery(); 01177 if ( count( $this->mHist ) ) { 01178 $list = new ImageHistoryList( $this->mImagePage ); 01179 # Generate prev/next links 01180 $navLink = $this->getNavigationBar(); 01181 $s = $list->beginImageHistoryList( $navLink ); 01182 // Skip rows there just for paging links 01183 for ( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) { 01184 $file = $this->mHist[$i]; 01185 $s .= $list->imageHistoryLine( !$file->isOld(), $file ); 01186 } 01187 $s .= $list->endImageHistoryList( $navLink ); 01188 01189 if ( $list->getPreventClickjacking() ) { 01190 $this->preventClickjacking(); 01191 } 01192 } 01193 return $s; 01194 } 01195 01196 function doQuery() { 01197 if ( $this->mQueryDone ) { 01198 return; 01199 } 01200 $this->mImg = $this->mImagePage->getFile(); // ensure loading 01201 if ( !$this->mImg->exists() ) { 01202 return; 01203 } 01204 $queryLimit = $this->mLimit + 1; // limit plus extra row 01205 if ( $this->mIsBackwards ) { 01206 // Fetch the file history 01207 $this->mHist = $this->mImg->getHistory( $queryLimit, null, $this->mOffset, false ); 01208 // The current rev may not meet the offset/limit 01209 $numRows = count( $this->mHist ); 01210 if ( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) { 01211 $this->mHist = array_merge( array( $this->mImg ), $this->mHist ); 01212 } 01213 } else { 01214 // The current rev may not meet the offset 01215 if ( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) { 01216 $this->mHist[] = $this->mImg; 01217 } 01218 // Old image versions (fetch extra row for nav links) 01219 $oiLimit = count( $this->mHist ) ? $this->mLimit : $this->mLimit + 1; 01220 // Fetch the file history 01221 $this->mHist = array_merge( $this->mHist, 01222 $this->mImg->getHistory( $oiLimit, $this->mOffset, null, false ) ); 01223 } 01224 $numRows = count( $this->mHist ); // Total number of query results 01225 if ( $numRows ) { 01226 # Index value of top item in the list 01227 $firstIndex = $this->mIsBackwards ? 01228 $this->mHist[$numRows - 1]->getTimestamp() : $this->mHist[0]->getTimestamp(); 01229 # Discard the extra result row if there is one 01230 if ( $numRows > $this->mLimit && $numRows > 1 ) { 01231 if ( $this->mIsBackwards ) { 01232 # Index value of item past the index 01233 $this->mPastTheEndIndex = $this->mHist[0]->getTimestamp(); 01234 # Index value of bottom item in the list 01235 $lastIndex = $this->mHist[1]->getTimestamp(); 01236 # Display range 01237 $this->mRange = array( 1, $numRows - 1 ); 01238 } else { 01239 # Index value of item past the index 01240 $this->mPastTheEndIndex = $this->mHist[$numRows - 1]->getTimestamp(); 01241 # Index value of bottom item in the list 01242 $lastIndex = $this->mHist[$numRows - 2]->getTimestamp(); 01243 # Display range 01244 $this->mRange = array( 0, $numRows - 2 ); 01245 } 01246 } else { 01247 # Setting indexes to an empty string means that they will be 01248 # omitted if they would otherwise appear in URLs. It just so 01249 # happens that this is the right thing to do in the standard 01250 # UI, in all the relevant cases. 01251 $this->mPastTheEndIndex = ''; 01252 # Index value of bottom item in the list 01253 $lastIndex = $this->mIsBackwards ? 01254 $this->mHist[0]->getTimestamp() : $this->mHist[$numRows - 1]->getTimestamp(); 01255 # Display range 01256 $this->mRange = array( 0, $numRows - 1 ); 01257 } 01258 } else { 01259 $firstIndex = ''; 01260 $lastIndex = ''; 01261 $this->mPastTheEndIndex = ''; 01262 } 01263 if ( $this->mIsBackwards ) { 01264 $this->mIsFirst = ( $numRows < $queryLimit ); 01265 $this->mIsLast = ( $this->mOffset == '' ); 01266 $this->mLastShown = $firstIndex; 01267 $this->mFirstShown = $lastIndex; 01268 } else { 01269 $this->mIsFirst = ( $this->mOffset == '' ); 01270 $this->mIsLast = ( $numRows < $queryLimit ); 01271 $this->mLastShown = $lastIndex; 01272 $this->mFirstShown = $firstIndex; 01273 } 01274 $this->mQueryDone = true; 01275 } 01276 01280 protected function preventClickjacking( $enable = true ) { 01281 $this->preventClickjacking = $enable; 01282 } 01283 01287 public function getPreventClickjacking() { 01288 return $this->preventClickjacking; 01289 } 01290 01291 }