]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - includes/ImagePage.php
MediaWiki 1.15.0
[autoinstalls/mediawiki.git] / includes / ImagePage.php
1 <?php
2
3 if( !defined( 'MEDIAWIKI' ) )
4         die( 1 );
5
6 /**
7  * Special handling for image description pages
8  *
9  * @ingroup Media
10  */
11 class ImagePage extends Article {
12
13         /* private */ var $img;  // Image object
14         /* private */ var $displayImg;
15         /* private */ var $repo;
16         /* private */ var $fileLoaded;
17         var $mExtraDescription = false;
18         var $dupes;
19
20         function __construct( $title ) {
21                 parent::__construct( $title );
22                 $this->dupes = null;
23                 $this->repo = null;
24         }
25         
26         public function setFile( $file ) {
27                 $this->displayImg = $file;
28                 $this->img = $file;
29                 $this->fileLoaded = true;
30         }
31
32         protected function loadFile() {
33                 if( $this->fileLoaded ) {
34                         return true;
35                 }
36                 $this->fileLoaded = true;
37
38                 $this->displayImg = $this->img = false;
39                 wfRunHooks( 'ImagePageFindFile', array( $this, &$this->img, &$this->displayImg ) );
40                 if( !$this->img ) {
41                         $this->img = wfFindFile( $this->mTitle );
42                         if( !$this->img ) {
43                                 $this->img = wfLocalFile( $this->mTitle );
44                         }
45                 }
46                 if( !$this->displayImg ) {
47                         $this->displayImg = $this->img;
48                 }
49                 $this->repo = $this->img->getRepo();
50         }
51
52         /**
53          * Handler for action=render
54          * Include body text only; none of the image extras
55          */
56         public function render() {
57                 global $wgOut;
58                 $wgOut->setArticleBodyOnly( true );
59                 parent::view();
60         }
61
62         public function view() {
63                 global $wgOut, $wgShowEXIF, $wgRequest, $wgUser;
64                 $this->loadFile();
65
66                 if( $this->mTitle->getNamespace() == NS_FILE && $this->img->getRedirected() ) {
67                         if( $this->mTitle->getDBkey() == $this->img->getName() ) {
68                                 // mTitle is the same as the redirect target so ask Article
69                                 // to perform the redirect for us.
70                                 return Article::view();
71                         } else {
72                                 // mTitle is not the same as the redirect target so it is 
73                                 // probably the redirect page itself. Fake the redirect symbol
74                                 $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
75                                 $wgOut->addHTML( $this->viewRedirect( Title::makeTitle( NS_FILE, $this->img->getName() ),
76                                         /* $appendSubtitle */ true, /* $forceKnown */ true ) );
77                                 $this->viewUpdates();
78                                 return;
79                         }
80                 }
81
82                 $diff = $wgRequest->getVal( 'diff' );
83                 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
84
85                 if( $this->mTitle->getNamespace() != NS_FILE || ( isset( $diff ) && $diffOnly ) )
86                         return Article::view();
87
88                 if( $wgShowEXIF && $this->displayImg->exists() ) {
89                         // FIXME: bad interface, see note on MediaHandler::formatMetadata().
90                         $formattedMetadata = $this->displayImg->formatMetadata();
91                         $showmeta = $formattedMetadata !== false;
92                 } else {
93                         $showmeta = false;
94                 }
95
96                 if( !$diff && $this->displayImg->exists() )
97                         $wgOut->addHTML( $this->showTOC($showmeta) );
98
99                 if( !$diff )
100                         $this->openShowImage();
101
102                 # No need to display noarticletext, we use our own message, output in openShowImage()
103                 if( $this->getID() ) {
104                         Article::view();
105                 } else {
106                         # Just need to set the right headers
107                         $wgOut->setArticleFlag( true );
108                         $wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
109                         $this->viewUpdates();
110                 }
111
112                 # Show shared description, if needed
113                 if( $this->mExtraDescription ) {
114                         $fol = wfMsgNoTrans( 'shareddescriptionfollows' );
115                         if( $fol != '-' && !wfEmptyMsg( 'shareddescriptionfollows', $fol ) ) {
116                                 $wgOut->addWikiText( $fol );
117                         }
118                         $wgOut->addHTML( '<div id="shared-image-desc">' . $this->mExtraDescription . '</div>' );
119                 }
120
121                 $this->closeShowImage();
122                 $this->imageHistory();
123                 // TODO: Cleanup the following
124                 
125                 $wgOut->addHTML( Xml::element( 'h2',
126                         array( 'id' => 'filelinks' ),
127                         wfMsg( 'imagelinks' ) ) . "\n" );
128                 $this->imageDupes();
129                 # TODO! FIXME! For some freaky reason, we can't redirect to foreign images.
130                 # Yet we return metadata about the target. Definitely an issue in the FileRepo
131                 $this->imageRedirects();
132                 $this->imageLinks();
133
134                 if( $showmeta ) {
135                         global $wgStylePath, $wgStyleVersion;
136                         $expand = htmlspecialchars( Xml::escapeJsString( wfMsg( 'metadata-expand' ) ) );
137                         $collapse = htmlspecialchars( Xml::escapeJsString( wfMsg( 'metadata-collapse' ) ) );
138                         $wgOut->addHTML( Xml::element( 'h2', array( 'id' => 'metadata' ), wfMsg( 'metadata' ) ). "\n" );
139                         $wgOut->addWikiText( $this->makeMetadataTable( $formattedMetadata ) );
140                         $wgOut->addScriptFile( 'metadata.js' );
141                         $wgOut->addHTML(
142                                 "<script type=\"text/javascript\">attachMetadataToggle('mw_metadata', '$expand', '$collapse');</script>\n" );
143                 }
144         }
145         
146         public function getRedirectTarget() {
147                 $this->loadFile();
148                 if( $this->img->isLocal() ) {
149                         return parent::getRedirectTarget();
150                 }
151                 // Foreign image page
152                 $from = $this->img->getRedirected();
153                 $to = $this->img->getName();
154                 if( $from == $to ) {
155                         return null; 
156                 }
157                 return $this->mRedirectTarget = Title::makeTitle( NS_FILE, $to );
158         }
159         public function followRedirect() {
160                 $this->loadFile();
161                 if( $this->img->isLocal() ) {
162                         return parent::followRedirect();
163                 }
164                 $from = $this->img->getRedirected();
165                 $to = $this->img->getName();
166                 if( $from == $to ) {
167                         return false; 
168                 }
169                 return Title::makeTitle( NS_FILE, $to );        
170         }
171         public function isRedirect( $text = false ) {
172                 $this->loadFile();
173                 if( $this->img->isLocal() )
174                         return parent::isRedirect( $text );
175                         
176                 return (bool)$this->img->getRedirected();
177         }
178         
179         public function isLocal() {
180                 $this->loadFile();
181                 return $this->img->isLocal();
182         }
183         
184         public function getFile() {
185                 $this->loadFile();
186                 return $this->img;
187         }
188         
189         public function getDisplayedFile() {
190                 $this->loadFile();
191                 return $this->displayImg;
192         }
193         
194         public function getDuplicates() {
195                 $this->loadFile();
196                 if( !is_null($this->dupes) ) {
197                         return $this->dupes;
198                 }
199                 if( !( $hash = $this->img->getSha1() ) ) {
200                         return $this->dupes = array();
201                 }
202                 $dupes = RepoGroup::singleton()->findBySha1( $hash );
203                 // Remove duplicates with self and non matching file sizes
204                 $self = $this->img->getRepoName().':'.$this->img->getName();
205                 $size = $this->img->getSize();
206                 foreach ( $dupes as $index => $file ) {
207                         $key = $file->getRepoName().':'.$file->getName();
208                         if( $key == $self )
209                                 unset( $dupes[$index] );
210                         if( $file->getSize() != $size )
211                                 unset( $dupes[$index] );
212                 }
213                 return $this->dupes = $dupes;
214                 
215         }
216         
217
218         /**
219          * Create the TOC
220          *
221          * @param bool $metadata Whether or not to show the metadata link
222          * @return string
223          */
224         protected function showTOC( $metadata ) {
225                 global $wgLang;
226                 $r = '<ul id="filetoc">
227                         <li><a href="#file">' . $wgLang->getNsText( NS_FILE ) . '</a></li>
228                         <li><a href="#filehistory">' . wfMsgHtml( 'filehist' ) . '</a></li>
229                         <li><a href="#filelinks">' . wfMsgHtml( 'imagelinks' ) . '</a></li>' .
230                         ($metadata ? ' <li><a href="#metadata">' . wfMsgHtml( 'metadata' ) . '</a></li>' : '') . '
231                 </ul>';
232                 return $r;
233         }
234
235         /**
236          * Make a table with metadata to be shown in the output page.
237          *
238          * FIXME: bad interface, see note on MediaHandler::formatMetadata().
239          *
240          * @param array $exif The array containing the EXIF data
241          * @return string
242          */
243         protected function makeMetadataTable( $metadata ) {
244                 $r = wfMsg( 'metadata-help' ) . "\n\n";
245                 $r .= "{| id=mw_metadata class=mw_metadata\n";
246                 foreach ( $metadata as $type => $stuff ) {
247                         foreach ( $stuff as $v ) {
248                                 # FIXME, why is this using escapeId for a class?!
249                                 $class = Sanitizer::escapeId( $v['id'] );
250                                 if( $type == 'collapsed' ) {
251                                         $class .= ' collapsable';
252                                 }
253                                 $r .= "|- class=\"$class\"\n";
254                                 $r .= "!| {$v['name']}\n";
255                                 $r .= "|| {$v['value']}\n";
256                         }
257                 }
258                 $r .= '|}';
259                 return $r;
260         }
261
262         /**
263          * Overloading Article's getContent method.
264          *
265          * Omit noarticletext if sharedupload; text will be fetched from the
266          * shared upload server if possible.
267          */
268         public function getContent() {
269                 $this->loadFile();
270                 if( $this->img && !$this->img->isLocal() && 0 == $this->getID() ) {
271                         return '';
272                 }
273                 return Article::getContent();
274         }
275
276         protected function openShowImage() {
277                 global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang, $wgContLang;
278
279                 $this->loadFile();
280
281                 $full_url  = $this->displayImg->getURL();
282                 $linkAttribs = false;
283                 $sizeSel = intval( $wgUser->getOption( 'imagesize') );
284                 if( !isset( $wgImageLimits[$sizeSel] ) ) {
285                         $sizeSel = User::getDefaultOption( 'imagesize' );
286
287                         // The user offset might still be incorrect, specially if
288                         // $wgImageLimits got changed (see bug #8858).
289                         if( !isset( $wgImageLimits[$sizeSel] ) ) {
290                                 // Default to the first offset in $wgImageLimits
291                                 $sizeSel = 0;
292                         }
293                 }
294                 $max = $wgImageLimits[$sizeSel];
295                 $maxWidth = $max[0];
296                 $maxHeight = $max[1];
297                 $sk = $wgUser->getSkin();
298                 $dirmark = $wgContLang->getDirMark();
299
300                 if( $this->displayImg->exists() ) {
301                         # image
302                         $page = $wgRequest->getIntOrNull( 'page' );
303                         if( is_null( $page ) ) {
304                                 $params = array();
305                                 $page = 1;
306                         } else {
307                                 $params = array( 'page' => $page );
308                         }
309                         $width_orig = $this->displayImg->getWidth();
310                         $width = $width_orig;
311                         $height_orig = $this->displayImg->getHeight();
312                         $height = $height_orig;
313                         $mime = $this->displayImg->getMimeType();
314                         $showLink = false;
315                         $linkAttribs = array( 'href' => $full_url );
316                         $longDesc = $this->displayImg->getLongDesc();
317
318                         wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this , &$wgOut ) )       ;
319
320                         if( $this->displayImg->allowInlineDisplay() ) {
321                                 # image
322
323                                 # "Download high res version" link below the image
324                                 #$msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->displayImg->getSize() ), $mime );
325                                 # We'll show a thumbnail of this image
326                                 if( $width > $maxWidth || $height > $maxHeight ) {
327                                         # Calculate the thumbnail size.
328                                         # First case, the limiting factor is the width, not the height.
329                                         if( $width / $height >= $maxWidth / $maxHeight ) {
330                                                 $height = round( $height * $maxWidth / $width);
331                                                 $width = $maxWidth;
332                                                 # Note that $height <= $maxHeight now.
333                                         } else {
334                                                 $newwidth = floor( $width * $maxHeight / $height);
335                                                 $height = round( $height * $newwidth / $width );
336                                                 $width = $newwidth;
337                                                 # Note that $height <= $maxHeight now, but might not be identical
338                                                 # because of rounding.
339                                         }
340                                         $msgbig  = wfMsgHtml( 'show-big-image' );
341                                         $msgsmall = wfMsgExt( 'show-big-image-thumb', 'parseinline',
342                                                 $wgLang->formatNum( $width ),
343                                                 $wgLang->formatNum( $height )
344                                         );
345                                 } else {
346                                         # Image is small enough to show full size on image page
347                                         $msgbig = htmlspecialchars( $this->displayImg->getName() );
348                                         $msgsmall = wfMsgExt( 'file-nohires', array( 'parseinline' ) );
349                                 }
350
351                                 $params['width'] = $width;
352                                 $thumbnail = $this->displayImg->transform( $params );
353
354                                 $anchorclose = "<br />";
355                                 if( $this->displayImg->mustRender() ) {
356                                         $showLink = true;
357                                 } else {
358                                         $anchorclose .=
359                                                 $msgsmall .
360                                                 '<br />' . Xml::tags( 'a', $linkAttribs,  $msgbig ) . "$dirmark " . $longDesc;
361                                 }
362
363                                 if( $this->displayImg->isMultipage() ) {
364                                         $wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
365                                 }
366
367                                 if( $thumbnail ) {
368                                         $options = array(
369                                                 'alt' => $this->displayImg->getTitle()->getPrefixedText(),
370                                                 'file-link' => true,
371                                         );
372                                         $wgOut->addHTML( '<div class="fullImageLink" id="file">' .
373                                                 $thumbnail->toHtml( $options ) .
374                                                 $anchorclose . '</div>' );
375                                 }
376
377                                 if( $this->displayImg->isMultipage() ) {
378                                         $count = $this->displayImg->pageCount();
379
380                                         if( $page > 1 ) {
381                                                 $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
382                                                 $link = $sk->makeKnownLinkObj( $this->mTitle, $label, 'page='. ($page-1) );
383                                                 $thumb1 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
384                                                         array( 'page' => $page - 1 ) );
385                                         } else {
386                                                 $thumb1 = '';
387                                         }
388
389                                         if( $page < $count ) {
390                                                 $label = wfMsg( 'imgmultipagenext' );
391                                                 $link = $sk->makeKnownLinkObj( $this->mTitle, $label, 'page='. ($page+1) );
392                                                 $thumb2 = $sk->makeThumbLinkObj( $this->mTitle, $this->displayImg, $link, $label, 'none',
393                                                         array( 'page' => $page + 1 ) );
394                                         } else {
395                                                 $thumb2 = '';
396                                         }
397
398                                         global $wgScript;
399
400                                         $formParams = array(
401                                                 'name' => 'pageselector',
402                                                 'action' => $wgScript,
403                                                 'onchange' => 'document.pageselector.submit();',
404                                         );
405
406                                         $option = array();
407                                         for ( $i=1; $i <= $count; $i++ ) {
408                                                 $options[] = Xml::option( $wgLang->formatNum($i), $i, $i == $page );
409                                         }
410                                         $select = Xml::tags( 'select',
411                                                 array( 'id' => 'pageselector', 'name' => 'page' ),
412                                                 implode( "\n", $options ) );
413
414                                         $wgOut->addHTML(
415                                                 '</td><td><div class="multipageimagenavbox">' .
416                                                 Xml::openElement( 'form', $formParams ) .
417                                                 Xml::hidden( 'title', $this->getTitle()->getPrefixedDbKey() ) .
418                                                 wfMsgExt( 'imgmultigoto', array( 'parseinline', 'replaceafter' ), $select ) .
419                                                 Xml::submitButton( wfMsg( 'imgmultigo' ) ) .
420                                                 Xml::closeElement( 'form' ) .
421                                                 "<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>"
422                                         );
423                                 }
424                         } else {
425                                 #if direct link is allowed but it's not a renderable image, show an icon.
426                                 if( $this->displayImg->isSafeFile() ) {
427                                         $icon= $this->displayImg->iconThumb();
428
429                                         $wgOut->addHTML( '<div class="fullImageLink" id="file">' .
430                                         $icon->toHtml( array( 'desc-link' => true ) ) .
431                                         '</div>' );
432                                 }
433
434                                 $showLink = true;
435                         }
436
437
438                         if($showLink) {
439                                 $filename = wfEscapeWikiText( $this->displayImg->getName() );
440
441                                 if( !$this->displayImg->isSafeFile() ) {
442                                         $warning = wfMsgNoTrans( 'mediawarning' );
443                                         $wgOut->addWikiText( <<<EOT
444 <div class="fullMedia">
445 <span class="dangerousLink">[[Media:$filename|$filename]]</span>$dirmark
446 <span class="fileInfo"> $longDesc</span>
447 </div>
448
449 <div class="mediaWarning">$warning</div>
450 EOT
451                                                 );
452                                 } else {
453                                         $wgOut->addWikiText( <<<EOT
454 <div class="fullMedia">
455 [[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $longDesc</span>
456 </div>
457 EOT
458                                                 );
459                                 }
460                         }
461
462                         if( !$this->displayImg->isLocal() ) {
463                                 $this->printSharedImageText();
464                         }
465                 } else {
466                         # Image does not exist
467
468                         $title = SpecialPage::getTitleFor( 'Upload' );
469                         $link = $sk->makeKnownLinkObj($title, wfMsgHtml('noimage-linktext'),
470                                 'wpDestFile=' . urlencode( $this->displayImg->getName() ) );
471                         $wgOut->setRobotPolicy( 'noindex,nofollow' );
472                         $wgOut->addHTML( wfMsgWikiHtml( 'noimage', $link ) );
473                 }
474         }
475
476         /**
477          * Show a notice that the file is from a shared repository
478          */
479         protected function printSharedImageText() {
480                 global $wgOut, $wgUser;
481
482                 $this->loadFile();
483
484                 $descUrl = $this->img->getDescriptionUrl();
485                 $descText = $this->img->getDescriptionText();
486                 $msg = '';
487                 if( $descUrl ) {
488                         $sk = $wgUser->getSkin();
489                         $link = $sk->makeExternalLink( $descUrl, wfMsg( 'shareduploadwiki-linktext' ) );
490                         $msg = ( $descText ) ? 'shareduploadwiki-desc' : 'shareduploadwiki';
491                         $msg = wfMsgExt( $msg, array( 'parseinline', 'replaceafter' ), $link );
492                         if( $msg == '-' ) {
493                                 $msg = '';
494                         }
495                 }
496                 $s  = "<div class='sharedUploadNotice'>";
497                 $s .= wfMsgWikiHtml( 'sharedupload', $this->img->getRepo()->getDisplayName(), $msg );
498                 $s .= "</div>";
499                 $wgOut->addHTML( $s );
500
501                 if( $descText ) {
502                         $this->mExtraDescription = $descText;
503                 }
504         }
505
506         public function getUploadUrl() {
507                 $this->loadFile();
508                 $uploadTitle = SpecialPage::getTitleFor( 'Upload' );
509                 return $uploadTitle->getFullUrl( 'wpDestFile=' . urlencode( $this->img->getName() ) . '&wpForReUpload=1' );
510         }
511
512         /**
513          * Print out the various links at the bottom of the image page, e.g. reupload,
514          * external editing (and instructions link) etc.
515          */
516         protected function uploadLinksBox() {
517                 global $wgUser, $wgOut;
518
519                 $this->loadFile();
520                 if( !$this->img->isLocal() )
521                         return;
522
523                 $sk = $wgUser->getSkin();
524
525                 $wgOut->addHTML( '<br /><ul>' );
526
527                 # "Upload a new version of this file" link
528                 if( UploadForm::userCanReUpload($wgUser,$this->img->name) ) {
529                         $ulink = $sk->makeExternalLink( $this->getUploadUrl(), wfMsg( 'uploadnewversion-linktext' ) );
530                         $wgOut->addHTML( "<li><div class='plainlinks'>{$ulink}</div></li>" );
531                 }
532
533                 # External editing link
534                 $elink = $sk->makeKnownLinkObj( $this->mTitle, wfMsgHtml( 'edit-externally' ), 'action=edit&externaledit=true&mode=file' );
535                 $wgOut->addHTML( '<li>' . $elink . ' <small>' . wfMsgExt( 'edit-externally-help', array( 'parseinline' ) ) . '</small></li>' );
536
537                 $wgOut->addHTML( '</ul>' );
538         }
539
540         protected function closeShowImage() {} # For overloading
541
542         /**
543          * If the page we've just displayed is in the "Image" namespace,
544          * we follow it with an upload history of the image and its usage.
545          */
546         protected function imageHistory() {
547                 global $wgOut, $wgUseExternalEditor;
548
549                 $this->loadFile();
550                 $pager = new ImageHistoryPseudoPager( $this );
551                 $wgOut->addHTML( $pager->getBody() );
552
553                 $this->img->resetHistory(); // free db resources
554
555                 # Exist check because we don't want to show this on pages where an image
556                 # doesn't exist along with the noimage message, that would suck. -ævar
557                 if( $wgUseExternalEditor && $this->img->exists() ) {
558                         $this->uploadLinksBox();
559                 }
560         }
561
562         protected function imageLinks() {
563                 global $wgUser, $wgOut, $wgLang;
564
565                 $limit = 100;
566
567                 $dbr = wfGetDB( DB_SLAVE );
568
569                 $res = $dbr->select(
570                         array( 'imagelinks', 'page' ),
571                         array( 'page_namespace', 'page_title' ),
572                         array( 'il_to' => $this->mTitle->getDBkey(), 'il_from = page_id' ),
573                         __METHOD__,
574                         array( 'LIMIT' => $limit + 1)   
575                 );
576                 $count = $dbr->numRows( $res );
577                 if( $count == 0 ) {
578                         $wgOut->addHTML( "<div id='mw-imagepage-nolinkstoimage'>\n" );
579                         $wgOut->addWikiMsg( 'nolinkstoimage' );
580                         $wgOut->addHTML( "</div>\n" );
581                         return;
582                 }
583                 
584                 $wgOut->addHTML( "<div id='mw-imagepage-section-linkstoimage'>\n" );
585                 if( $count <= $limit - 1 ) {
586                         $wgOut->addWikiMsg( 'linkstoimage', $count );
587                 } else {
588                         // More links than the limit. Add a link to [[Special:Whatlinkshere]]
589                         $wgOut->addWikiMsg( 'linkstoimage-more',
590                                 $wgLang->formatNum( $limit ),
591                                 $this->mTitle->getPrefixedDBkey()
592                         );
593                 }
594
595                 $wgOut->addHTML( "<ul class='mw-imagepage-linkstoimage'>\n" );
596                 $sk = $wgUser->getSkin();
597                 $count = 0;
598                 while ( $s = $res->fetchObject() ) {
599                         $count++;
600                         if( $count <= $limit ) {
601                                 // We have not yet reached the extra one that tells us there is more to fetch
602                                 $name = Title::makeTitle( $s->page_namespace, $s->page_title );
603                                 $link = $sk->makeKnownLinkObj( $name, "" );
604                                 $wgOut->addHTML( "<li>{$link}</li>\n" );
605                         }
606                 }
607                 $wgOut->addHTML( "</ul></div>\n" );
608                 $res->free();
609
610                 // Add a links to [[Special:Whatlinkshere]]
611                 if( $count > $limit )
612                         $wgOut->addWikiMsg( 'morelinkstoimage', $this->mTitle->getPrefixedDBkey() );
613         }
614         
615         protected function imageRedirects() {
616                 global $wgUser, $wgOut, $wgLang;
617
618                 $redirects = $this->getTitle()->getRedirectsHere( NS_FILE );
619                 if( count( $redirects ) == 0 ) return;
620
621                 $wgOut->addHTML( "<div id='mw-imagepage-section-redirectstofile'>\n" );
622                 $wgOut->addWikiMsg( 'redirectstofile',
623                         $wgLang->formatNum( count( $redirects ) )
624                 );
625                 $wgOut->addHTML( "<ul class='mw-imagepage-redirectstofile'>\n" );
626
627                 $sk = $wgUser->getSkin();
628                 foreach ( $redirects as $title ) {
629                         $link = $sk->makeKnownLinkObj( $title, "", "redirect=no" );
630                         $wgOut->addHTML( "<li>{$link}</li>\n" );
631                 }
632                 $wgOut->addHTML( "</ul></div>\n" );
633
634         }
635
636         protected function imageDupes() {
637                 global $wgOut, $wgUser, $wgLang;
638
639                 $this->loadFile();
640
641                 $dupes = $this->getDuplicates();
642                 if( count( $dupes ) == 0 ) return;
643
644                 $wgOut->addHTML( "<div id='mw-imagepage-section-duplicates'>\n" );
645                 $wgOut->addWikiMsg( 'duplicatesoffile',
646                         $wgLang->formatNum( count( $dupes ) ), $this->mTitle->getDBkey()
647                 );
648                 $wgOut->addHTML( "<ul class='mw-imagepage-duplicates'>\n" );
649
650                 $sk = $wgUser->getSkin();
651                 foreach ( $dupes as $file ) {
652                         $fromSrc = '';
653                         if( $file->isLocal() )
654                                 $link = $sk->makeKnownLinkObj( $file->getTitle(), "" );
655                         else {
656                                 $link = $sk->makeExternalLink( $file->getDescriptionUrl(),
657                                         $file->getTitle()->getPrefixedText() );
658                                 $fromSrc = wfMsg( 'shared-repo-from', $file->getRepo()->getDisplayName() );
659                         }
660                         $wgOut->addHTML( "<li>{$link} {$fromSrc}</li>\n" );
661                 }
662                 $wgOut->addHTML( "</ul></div>\n" );
663         }
664
665         /**
666          * Delete the file, or an earlier version of it
667          */
668         public function delete() {
669                 $this->loadFile();
670                 if( !$this->img->exists() || !$this->img->isLocal() || $this->img->getRedirected() ) {
671                         // Standard article deletion
672                         Article::delete();
673                         return;
674                 }
675                 $deleter = new FileDeleteForm( $this->img );
676                 $deleter->execute();
677         }
678
679         /**
680          * Revert the file to an earlier version
681          */
682         public function revert() {
683                 $this->loadFile();
684                 $reverter = new FileRevertForm( $this->img );
685                 $reverter->execute();
686         }
687
688         /**
689          * Override handling of action=purge
690          */
691         public function doPurge() {
692                 $this->loadFile();
693                 if( $this->img->exists() ) {
694                         wfDebug( "ImagePage::doPurge purging " . $this->img->getName() . "\n" );
695                         $update = new HTMLCacheUpdate( $this->mTitle, 'imagelinks' );
696                         $update->doUpdate();
697                         $this->img->upgradeRow();
698                         $this->img->purgeCache();
699                 } else {
700                         wfDebug( "ImagePage::doPurge no image\n" );
701                 }
702                 parent::doPurge();
703         }
704
705         /**
706          * Display an error with a wikitext description
707          */
708         function showError( $description ) {
709                 global $wgOut;
710                 $wgOut->setPageTitle( wfMsg( "internalerror" ) );
711                 $wgOut->setRobotPolicy( "noindex,nofollow" );
712                 $wgOut->setArticleRelated( false );
713                 $wgOut->enableClientCache( false );
714                 $wgOut->addWikiText( $description );
715         }
716
717 }
718
719 /**
720  * Builds the image revision log shown on image pages
721  *
722  * @ingroup Media
723  */
724 class ImageHistoryList {
725
726         protected $imagePage, $img, $skin, $title, $repo;
727
728         public function __construct( $imagePage ) {
729                 global $wgUser;
730                 $this->skin = $wgUser->getSkin();
731                 $this->current = $imagePage->getFile();
732                 $this->img = $imagePage->getDisplayedFile();
733                 $this->title = $imagePage->getTitle();
734                 $this->imagePage = $imagePage;
735         }
736
737         public function getImagePage() {
738                 return $this->imagePage;
739         }
740
741         public function getSkin() {
742                 return $this->skin;
743         }
744
745         public function getFile() {
746                 return $this->img;
747         }
748
749         public function beginImageHistoryList( $navLinks = '' ) {
750                 global $wgOut, $wgUser;
751                 return Xml::element( 'h2', array( 'id' => 'filehistory' ), wfMsg( 'filehist' ) )
752                         . $wgOut->parse( wfMsgNoTrans( 'filehist-help' ) )
753                         . $navLinks
754                         . Xml::openElement( 'table', array( 'class' => 'filehistory' ) ) . "\n"
755                         . '<tr><td></td>'
756                         . ( $this->current->isLocal() && ($wgUser->isAllowed('delete') || $wgUser->isAllowed('deleterevision') ) ? '<td></td>' : '' )
757                         . '<th>' . wfMsgHtml( 'filehist-datetime' ) . '</th>'
758                         . '<th>' . wfMsgHtml( 'filehist-thumb' ) . '</th>'
759                         . '<th>' . wfMsgHtml( 'filehist-dimensions' ) . '</th>'
760                         . '<th>' . wfMsgHtml( 'filehist-user' ) . '</th>'
761                         . '<th>' . wfMsgHtml( 'filehist-comment' ) . '</th>'
762                         . "</tr>\n";
763         }
764
765         public function endImageHistoryList( $navLinks = '' ) {
766                 return "</table>\n$navLinks\n";
767         }
768
769         public function imageHistoryLine( $iscur, $file ) {
770                 global $wgUser, $wgLang, $wgContLang, $wgTitle;
771
772                 $timestamp = wfTimestamp(TS_MW, $file->getTimestamp());
773                 $img = $iscur ? $file->getName() : $file->getArchiveName();
774                 $user = $file->getUser('id');
775                 $usertext = $file->getUser('text');
776                 $size = $file->getSize();
777                 $description = $file->getDescription();
778                 $dims = $file->getDimensionsString();
779                 $sha1 = $file->getSha1();
780
781                 $local = $this->current->isLocal();
782                 $row = $css = $selected = '';
783
784                 // Deletion link
785                 if( $local && ($wgUser->isAllowed('delete') || $wgUser->isAllowed('deleterevision') ) ) {
786                         $row .= '<td>';
787                         # Link to remove from history
788                         if( $wgUser->isAllowed( 'delete' ) ) {
789                                 $q = array();
790                                 $q[] = 'action=delete';
791                                 if( !$iscur )
792                                         $q[] = 'oldimage=' . urlencode( $img );
793                                 $row .= $this->skin->makeKnownLinkObj(
794                                         $this->title,
795                                         wfMsgHtml( $iscur ? 'filehist-deleteall' : 'filehist-deleteone' ),
796                                         implode( '&', $q )
797                                 );
798                         }
799                         # Link to hide content
800                         if( $wgUser->isAllowed( 'deleterevision' ) ) {
801                                 if( $wgUser->isAllowed('delete') ) {
802                                         $row .= '<br/>';
803                                 }
804                                 $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
805                                 // If file is top revision or locked from this user, don't link
806                                 if( $iscur || !$file->userCan(File::DELETED_RESTRICTED) ) {
807                                         $del = wfMsgHtml( 'rev-delundel' );
808                                 } else {
809                                         // If the file was hidden, link to sha-1
810                                         list($ts,$name) = explode('!',$img,2);
811                                         $del = $this->skin->makeKnownLinkObj( $revdel,  wfMsg( 'rev-delundel' ),
812                                                 'target=' . urlencode( $wgTitle->getPrefixedText() ) .
813                                                 '&oldimage=' . urlencode( $ts ) );
814                                         // Bolden oversighted content
815                                         if( $file->isDeleted(File::DELETED_RESTRICTED) )
816                                                 $del = "<strong>$del</strong>";
817                                 }
818                                 $row .= "<tt style='white-space: nowrap;'><small>$del</small></tt>";
819                         }
820                         $row .= '</td>';
821                 }
822
823                 // Reversion link/current indicator
824                 $row .= '<td>';
825                 if( $iscur ) {
826                         $row .= wfMsgHtml( 'filehist-current' );
827                 } elseif( $local && $wgUser->isLoggedIn() && $this->title->userCan( 'edit' ) ) {
828                         if( $file->isDeleted(File::DELETED_FILE) ) {
829                                 $row .= wfMsgHtml('filehist-revert');
830                         } else {
831                                 $q = array();
832                                 $q[] = 'action=revert';
833                                 $q[] = 'oldimage=' . urlencode( $img );
834                                 $q[] = 'wpEditToken=' . urlencode( $wgUser->editToken( $img ) );
835                                 $row .= $this->skin->makeKnownLinkObj( $this->title,
836                                         wfMsgHtml( 'filehist-revert' ),
837                                         implode( '&', $q ) );
838                         }
839                 }
840                 $row .= '</td>';
841
842                 // Date/time and image link
843                 if( $file->getTimestamp() === $this->img->getTimestamp() ) {
844                         $selected = "class='filehistory-selected'";
845                 }
846                 $row .= "<td $selected style='white-space: nowrap;'>";
847                 if( !$file->userCan(File::DELETED_FILE) ) {
848                         # Don't link to unviewable files
849                         $row .= '<span class="history-deleted">' . $wgLang->timeAndDate( $timestamp, true ) . '</span>';
850                 } else if( $file->isDeleted(File::DELETED_FILE) ) {
851                         $revdel = SpecialPage::getTitleFor( 'Revisiondelete' );
852                         # Make a link to review the image
853                         $url = $this->skin->makeKnownLinkObj( $revdel, $wgLang->timeAndDate( $timestamp, true ),
854                                 "target=".$wgTitle->getPrefixedText()."&file=$sha1.".$this->current->getExtension() );
855                         $row .= '<span class="history-deleted">'.$url.'</span>';
856                 } else {
857                         $url = $iscur ? $this->current->getUrl() : $this->current->getArchiveUrl( $img );
858                         $row .= Xml::element( 'a', array( 'href' => $url ), $wgLang->timeAndDate( $timestamp, true ) );
859                 }
860
861                 // Thumbnail
862                 if( $file->allowInlineDisplay() && $file->userCan( File::DELETED_FILE ) && !$file->isDeleted( File::DELETED_FILE ) ) {
863                         $params = array(
864                                 'width' => '120',
865                                 'height' => '120',
866                         );
867                         $thumbnail = $file->transform( $params );
868                         $options = array(
869                                 'alt' => wfMsg( 'filehist-thumbtext', $wgLang->timeAndDate( $timestamp, true ) ),
870                                 'file-link' => true,
871                         );
872                         $row .= '</td><td>' . ( $thumbnail ? $thumbnail->toHtml( $options ) : 
873                                                                                                         wfMsgHtml( 'filehist-nothumb' ) );
874                 } else {
875                         $row .= '</td><td>' . wfMsgHtml( 'filehist-nothumb' );
876                 }
877                 $row .= "</td><td>";
878
879                 // Image dimensions
880                 $row .= htmlspecialchars( $dims );
881
882                 // File size
883                 $row .= " <span style='white-space: nowrap;'>(" . $this->skin->formatSize( $size ) . ')</span>';
884
885                 // Uploading user
886                 $row .= '</td><td>';
887                 if( $local ) {
888                         // Hide deleted usernames
889                         if( $file->isDeleted(File::DELETED_USER) ) {
890                                 $row .= '<span class="history-deleted">' . wfMsgHtml( 'rev-deleted-user' ) . '</span>';
891                         } else {
892                                 $row .= $this->skin->userLink( $user, $usertext ) . " <span style='white-space: nowrap;'>" . 
893                                         $this->skin->userToolLinks( $user, $usertext ) . "</span>";
894                         }
895                 } else {
896                         $row .= htmlspecialchars( $usertext );
897                 }
898                 $row .= '</td><td>';
899
900                 // Don't show deleted descriptions
901                 if( $file->isDeleted(File::DELETED_COMMENT) ) {
902                         $row .= '<span class="history-deleted">' . wfMsgHtml('rev-deleted-comment') . '</span>';
903                 } else {
904                         $row .= $this->skin->commentBlock( $description, $this->title );
905                 }
906                 $row .= '</td>';
907
908                 wfRunHooks( 'ImagePageFileHistoryLine', array( $this, $file, &$row, &$rowClass ) );
909                 $classAttr = $rowClass ? " class='$rowClass'" : "";
910
911                 return "<tr{$classAttr}>{$row}</tr>\n";
912         }
913 }
914
915 class ImageHistoryPseudoPager extends ReverseChronologicalPager {
916         function __construct( $imagePage ) {
917                 parent::__construct();
918                 $this->mImagePage = $imagePage;
919                 $this->mTitle = clone( $imagePage->getTitle() );
920                 $this->mTitle->setFragment( '#filehistory' );
921                 $this->mImg = NULL;
922                 $this->mHist = array();
923                 $this->mRange = array( 0, 0 ); // display range
924         }
925         
926         function getTitle() {
927                 return $this->mTitle;
928         }
929
930         function getQueryInfo() {
931                 return false;
932         }
933
934         function getIndexField() {
935                 return '';
936         }
937
938         function formatRow( $row ) {
939                 return '';
940         }
941         
942         function getBody() {
943                 $s = '';
944                 $this->doQuery();
945                 if( count($this->mHist) ) {
946                         $list = new ImageHistoryList( $this->mImagePage );
947                         # Generate prev/next links
948                         $navLink = $this->getNavigationBar();
949                         $s = $list->beginImageHistoryList($navLink);
950                         // Skip rows there just for paging links
951                         for( $i = $this->mRange[0]; $i <= $this->mRange[1]; $i++ ) {
952                                 $file = $this->mHist[$i];
953                                 $s .= $list->imageHistoryLine( !$file->isOld(), $file );
954                         }
955                         $s .= $list->endImageHistoryList($navLink);
956                 }
957                 return $s;
958         }
959
960         function doQuery() {
961                 if( $this->mQueryDone ) return;
962                 $this->mImg = $this->mImagePage->getFile(); // ensure loading
963                 if( !$this->mImg->exists() ) {
964                         return;
965                 }
966                 $queryLimit = $this->mLimit + 1; // limit plus extra row
967                 if( $this->mIsBackwards ) {
968                         // Fetch the file history
969                         $this->mHist = $this->mImg->getHistory($queryLimit,null,$this->mOffset,false);
970                         // The current rev may not meet the offset/limit
971                         $numRows = count($this->mHist);
972                         if( $numRows <= $this->mLimit && $this->mImg->getTimestamp() > $this->mOffset ) {
973                                 $this->mHist = array_merge( array($this->mImg), $this->mHist );
974                         }
975                 } else {
976                         // The current rev may not meet the offset
977                         if( !$this->mOffset || $this->mImg->getTimestamp() < $this->mOffset ) {
978                                 $this->mHist[] = $this->mImg;
979                         }
980                         // Old image versions (fetch extra row for nav links)
981                         $oiLimit = count($this->mHist) ? $this->mLimit : $this->mLimit+1;
982                         // Fetch the file history
983                         $this->mHist = array_merge( $this->mHist,
984                                 $this->mImg->getHistory($oiLimit,$this->mOffset,null,false) );
985                 }
986                 $numRows = count($this->mHist); // Total number of query results
987                 if( $numRows ) {
988                         # Index value of top item in the list
989                         $firstIndex = $this->mIsBackwards ?
990                                 $this->mHist[$numRows-1]->getTimestamp() : $this->mHist[0]->getTimestamp();
991                         # Discard the extra result row if there is one
992                         if( $numRows > $this->mLimit && $numRows > 1 ) {
993                                 if( $this->mIsBackwards ) {
994                                         # Index value of item past the index
995                                         $this->mPastTheEndIndex = $this->mHist[0]->getTimestamp();
996                                         # Index value of bottom item in the list
997                                         $lastIndex = $this->mHist[1]->getTimestamp();
998                                         # Display range
999                                         $this->mRange = array( 1, $numRows-1 );
1000                                 } else {
1001                                         # Index value of item past the index
1002                                         $this->mPastTheEndIndex = $this->mHist[$numRows-1]->getTimestamp();
1003                                         # Index value of bottom item in the list
1004                                         $lastIndex = $this->mHist[$numRows-2]->getTimestamp();
1005                                         # Display range
1006                                         $this->mRange = array( 0, $numRows-2 );
1007                                 }
1008                         } else {
1009                                 # Setting indexes to an empty string means that they will be
1010                                 # omitted if they would otherwise appear in URLs. It just so
1011                                 # happens that this  is the right thing to do in the standard
1012                                 # UI, in all the relevant cases.
1013                                 $this->mPastTheEndIndex = '';
1014                                 # Index value of bottom item in the list
1015                                 $lastIndex = $this->mIsBackwards ?
1016                                         $this->mHist[0]->getTimestamp() : $this->mHist[$numRows-1]->getTimestamp();
1017                                 # Display range
1018                                 $this->mRange = array( 0, $numRows-1 );
1019                         }
1020                 } else {
1021                         $firstIndex = '';
1022                         $lastIndex = '';
1023                         $this->mPastTheEndIndex = '';
1024                 }
1025                 if( $this->mIsBackwards ) {
1026                         $this->mIsFirst = ( $numRows < $queryLimit );
1027                         $this->mIsLast = ( $this->mOffset == '' );
1028                         $this->mLastShown = $firstIndex;
1029                         $this->mFirstShown = $lastIndex;
1030                 } else {
1031                         $this->mIsFirst = ( $this->mOffset == '' );
1032                         $this->mIsLast = ( $numRows < $queryLimit );
1033                         $this->mLastShown = $lastIndex;
1034                         $this->mFirstShown = $firstIndex;
1035                 }
1036                 $this->mQueryDone = true;
1037         }
1038 }