r21411 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r21410‎ | r21411 | r21412 >
Date:12:31, 20 April 2007
Author:tstarling
Status:old (Comments)
Tags:
Comment:
* Introduced media handler modules for file-type specific operations: thumbnailing, img_metadata, capabilities, etc.
* Deprecated $wgUseImageResize, thumbnailing will be enabled unconditionally.
* Fixed interaction of page parameter to ImagePage with the HTML file cache
* Improved error reporting for image thumbnailing
* Fixed MIME type for SVG files, will be silently changed from image/svg to image/svg+xml after loading from the database.
* Workaround for djvutoxml bug #1704049 (poor performance). Use djvudump instead.
* Fixed odd behaviour in ImagePage on DjVu thumbnailing errors
* Improved error reporting for image thumbnailing
* Added sharpening option for ImageMagick thumbnailing
* Removed Image::selectPage(), added page parameters to getWidth() and getHeight(), deprecated Image::renderThumb() and Image::getThumbnail()
* Changed default contents of img_metadata to empty string instead of a:0:{}
* Moved responsibility for respecting $wgGenerateThumbnailOnParse from the UI to Image.php
Modified paths:
  • /trunk/extensions/WebStore/404-handler.php (modified) (history)
  • /trunk/extensions/WebStore/WebStore.i18n.php (modified) (history)
  • /trunk/extensions/WebStore/WebStore.php (modified) (history)
  • /trunk/extensions/WebStore/WebStoreClient.php (modified) (history)
  • /trunk/extensions/WebStore/WebStoreCommon.php (modified) (history)
  • /trunk/extensions/WebStore/inplace-scaler.php (modified) (history)
  • /trunk/extensions/WebStore/metadata.php (modified) (history)
  • /trunk/extensions/WebStore/publish.php (modified) (history)
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/config/index.php (modified) (history)
  • /trunk/phase3/includes/Article.php (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/DjVuImage.php (modified) (history)
  • /trunk/phase3/includes/Exif.php (modified) (history)
  • /trunk/phase3/includes/Image.php (modified) (history)
  • /trunk/phase3/includes/ImageGallery.php (modified) (history)
  • /trunk/phase3/includes/ImagePage.php (modified) (history)
  • /trunk/phase3/includes/Linker.php (modified) (history)
  • /trunk/phase3/includes/MediaTransformOutput.php (added) (history)
  • /trunk/phase3/includes/MimeMagic.php (modified) (history)
  • /trunk/phase3/includes/Parser.php (modified) (history)
  • /trunk/phase3/includes/media (added) (history)
  • /trunk/phase3/includes/media/Bitmap.php (added) (history)
  • /trunk/phase3/includes/media/DjVu.php (added) (history)
  • /trunk/phase3/includes/media/Generic.php (added) (history)
  • /trunk/phase3/includes/media/SVG.php (added) (history)
  • /trunk/phase3/includes/mime.info (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/skins/common/common.css (modified) (history)
  • /trunk/phase3/skins/monobook/main.css (modified) (history)
  • /trunk/phase3/thumb.php (modified) (history)

Diff [purge]

Index: trunk/phase3/skins/monobook/main.css
@@ -1626,3 +1626,13 @@
16271627 .texvc { direction: ltr; unicode-bidi: embed; }
16281628 /* Stop floats from intruding into edit area in previews */
16291629 #toolbar, #wpTextbox1 { clear: both; }
 1630+
 1631+.MediaTransformError {
 1632+ background-color: #ccc;
 1633+ padding: 0.1em;
 1634+}
 1635+.MediaTransformError td {
 1636+ text-align: center;
 1637+ vertical-align: middle;
 1638+ font-size: 90%;
 1639+}
Index: trunk/phase3/skins/common/common.css
@@ -480,4 +480,15 @@
481481 #file img, .gallerybox .thumb img {
482482 background: url(images/Checker-16x16.png) repeat;
483483 }
484 -*/
\ No newline at end of file
 484+*/
 485+.MediaTransformError {
 486+ border: thin solid #777;
 487+ background-color: #ccc;
 488+ padding: 0.1em;
 489+}
 490+.MediaTransformError td {
 491+ text-align: center;
 492+ vertical-align: middle;
 493+ font-size: 90%;
 494+}
 495+
Index: trunk/phase3/includes/Article.php
@@ -2460,6 +2460,7 @@
24612461 $diff = $wgRequest->getVal( 'diff' );
24622462 $redirect = $wgRequest->getVal( 'redirect' );
24632463 $printable = $wgRequest->getVal( 'printable' );
 2464+ $page = $wgRequest->getVal( 'page' );
24642465
24652466 return $wgUseFileCache
24662467 and (!$wgShowIPinHeader)
@@ -2472,6 +2473,7 @@
24732474 and (!isset($diff))
24742475 and (!isset($redirect))
24752476 and (!isset($printable))
 2477+ and !isset($page)
24762478 and (!$this->mRedirectedFrom);
24772479 }
24782480
Index: trunk/phase3/includes/MimeMagic.php
@@ -22,7 +22,7 @@
2323 image/gif gif
2424 image/jpeg jpeg jpg jpe
2525 image/png png
26 -image/svg+xml svg
 26+image/svg+xml image/svg svg
2727 image/tiff tiff tif
2828 image/vnd.djvu djvu
2929 image/x-portable-pixmap ppm
@@ -51,7 +51,7 @@
5252 image/gif [BITMAP]
5353 image/jpeg [BITMAP]
5454 image/png [BITMAP]
55 -image/svg image/svg+xml [DRAWING]
 55+image/svg+xml [DRAWING]
5656 image/tiff [BITMAP]
5757 image/vnd.djvu [BITMAP]
5858 image/x-portable-pixmap [BITMAP]
Index: trunk/phase3/includes/ImagePage.php
@@ -166,11 +166,9 @@
167167
168168 function openShowImage() {
169169 global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang;
170 - global $wgUseImageResize, $wgGenerateThumbnailOnParse;
171170
172171 $full_url = $this->img->getURL();
173 - $anchoropen = '';
174 - $anchorclose = '';
 172+ $linkAttribs = false;
175173 $sizeSel = intval( $wgUser->getOption( 'imagesize') );
176174 if( !isset( $wgImageLimits[$sizeSel] ) ) {
177175 $sizeSel = User::getDefaultOption( 'imagesize' );
@@ -190,10 +188,11 @@
191189 if ( $this->img->exists() ) {
192190 # image
193191 $page = $wgRequest->getIntOrNull( 'page' );
194 - if ( ! is_null( $page ) ) {
195 - $this->img->selectPage( $page );
196 - } else {
 192+ if ( is_null( $page ) ) {
 193+ $params = array();
197194 $page = 1;
 195+ } else {
 196+ $params = array( 'page' => $page );
198197 }
199198 $width_orig = $this->img->getWidth();
200199 $width = $width_orig;
@@ -201,6 +200,7 @@
202201 $height = $height_orig;
203202 $mime = $this->img->getMimeType();
204203 $showLink = false;
 204+ $linkAttribs = array( 'href' => $full_url );
205205
206206 if ( $this->img->allowInlineDisplay() and $width and $height) {
207207 # image
@@ -223,57 +223,51 @@
224224 # Note that $height <= $maxHeight now, but might not be identical
225225 # because of rounding.
226226 }
 227+ }
 228+ $params['width'] = $width;
 229+ $thumbnail = $this->img->transform( $params );
227230
228 - if( $wgUseImageResize ) {
229 - $thumbnail = $this->img->getThumbnail( $width, -1, $wgGenerateThumbnailOnParse );
230 - if ( $thumbnail == null ) {
231 - $url = $this->img->getViewURL();
232 - } else {
233 - $url = $thumbnail->getURL();
234 - }
235 - } else {
236 - # No resize ability? Show the full image, but scale
237 - # it down in the browser so it fits on the page.
238 - $url = $this->img->getViewURL();
239 - }
240 - $anchoropen = "<a href=\"{$full_url}\">";
241 - $anchorclose = "</a><br />";
242 - if( $this->img->mustRender() ) {
243 - $showLink = true;
244 - } else {
245 - $anchorclose .= wfMsg('show-big-image-thumb', $width, $height ) .
246 - '<br />' . "\n$anchoropen{$msgbig}</a> " . $msgsize;
247 - }
248 - } else {
249 - $url = $this->img->getViewURL();
 231+ $anchorclose = "<br />";
 232+ if( $this->img->mustRender() ) {
250233 $showLink = true;
 234+ } else {
 235+ $anchorclose .=
 236+ wfMsg('show-big-image-thumb', $width, $height ) .
 237+ '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $msgsize;
251238 }
252239
253240 if ( $this->img->isMultipage() ) {
254241 $wgOut->addHTML( '<table class="multipageimage"><tr><td>' );
255242 }
256243
257 - $wgOut->addHTML( '<div class="fullImageLink" id="file">' . $anchoropen .
258 - "<img border=\"0\" src=\"{$url}\" width=\"{$width}\" height=\"{$height}\" alt=\"" .
259 - htmlspecialchars( $this->img->getTitle()->getPrefixedText() ).'" />' . $anchorclose . '</div>' );
 244+ $imgAttribs = array(
 245+ 'border' => 0,
 246+ 'alt' => $this->img->getTitle()->getPrefixedText()
 247+ );
260248
 249+ if ( $thumbnail ) {
 250+ $wgOut->addHTML( '<div class="fullImageLink" id="file">' .
 251+ $thumbnail->toHtml( $imgAttribs, $linkAttribs ) .
 252+ $anchorclose . '</div>' );
 253+ }
 254+
261255 if ( $this->img->isMultipage() ) {
262256 $count = $this->img->pageCount();
263257
264258 if ( $page > 1 ) {
265259 $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false );
266260 $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page-1) );
267 - $this->img->selectPage( $page - 1 );
268 - $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
 261+ $thumb1 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none',
 262+ array( 'page' => $page - 1 ) );
269263 } else {
270264 $thumb1 = '';
271265 }
272266
273267 if ( $page < $count ) {
274268 $label = wfMsg( 'imgmultipagenext' );
275 - $this->img->selectPage( $page + 1 );
276269 $link = $sk->makeLinkObj( $this->mTitle, $label, 'page='. ($page+1) );
277 - $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none' );
 270+ $thumb2 = $sk->makeThumbLinkObj( $this->img, $link, $label, 'none',
 271+ array( 'page' => $page + 1 ) );
278272 } else {
279273 $thumb2 = '';
280274 }
@@ -294,7 +288,7 @@
295289 htmlspecialchars( wfMsg( 'imgmultigo' ) ) . '"></form>';
296290
297291 $wgOut->addHTML( '</td><td><div class="multipageimagenavbox">' .
298 - "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
 292+ "$select<hr />$thumb1\n$thumb2<br clear=\"all\" /></div></td></tr></table>" );
299293 }
300294 } else {
301295 #if direct link is allowed but it's not a renderable image, show an icon.
Index: trunk/phase3/includes/Linker.php
@@ -429,26 +429,20 @@
430430 }
431431
432432 /** @todo document */
433 - function makeImageLinkObj( $nt, $label, $alt, $align = '', $width = false, $height = false, $framed = false,
434 - $thumb = false, $manual_thumb = '', $page = null, $valign = '' )
 433+ function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
 434+ $thumb = false, $manual_thumb = '', $valign = '' )
435435 {
436 - global $wgContLang, $wgUser, $wgThumbLimits, $wgGenerateThumbnailOnParse;
 436+ global $wgContLang, $wgUser, $wgThumbLimits;
437437
438438 $img = new Image( $nt );
439439
440 - if ( ! is_null( $page ) ) {
441 - $img->selectPage( $page );
442 - }
443 -
444440 if ( !$img->allowInlineDisplay() && $img->exists() ) {
445441 return $this->makeKnownLinkObj( $nt );
446442 }
447443
448 - $url = $img->getViewURL();
449444 $error = $prefix = $postfix = '';
 445+ $page = isset( $params['page'] ) ? $params['page'] : false;
450446
451 - wfDebug( "makeImageLinkObj: '$width'x'$height', \"$label\"\n" );
452 -
453447 if ( 'center' == $align )
454448 {
455449 $prefix = '<div class="center">';
@@ -456,6 +450,16 @@
457451 $align = 'none';
458452 }
459453
 454+ if ( !isset( $params['width'] ) ) {
 455+ $wopt = $wgUser->getOption( 'thumbsize' );
 456+
 457+ if( !isset( $wgThumbLimits[$wopt] ) ) {
 458+ $wopt = User::getDefaultOption( 'thumbsize' );
 459+ }
 460+
 461+ $params['width'] = min( $img->getWidth( $page ), $wgThumbLimits[$wopt] );
 462+ }
 463+
460464 if ( $thumb || $framed ) {
461465
462466 # Create a thumbnail. Alignment depends on language
@@ -468,73 +472,39 @@
469473 if ( $align == '' ) {
470474 $align = $wgContLang->isRTL() ? 'left' : 'right';
471475 }
 476+ return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
 477+ }
472478
473 -
474 - if ( $width === false ) {
475 - $wopt = $wgUser->getOption( 'thumbsize' );
476 -
477 - if( !isset( $wgThumbLimits[$wopt] ) ) {
478 - $wopt = User::getDefaultOption( 'thumbsize' );
479 - }
480 -
481 - $width = min( $img->getWidth(), $wgThumbLimits[$wopt] );
482 - }
483 -
484 - return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $width, $height, $framed, $manual_thumb ).$postfix;
 479+ if ( $params['width'] && $img->exists() ) {
 480+ # Create a resized image, without the additional thumbnail features
 481+ $thumb = $img->transform( $params );
 482+ } else {
 483+ $thumb = false;
485484 }
486485
487 - if ( $width && $img->exists() ) {
488 -
489 - # Create a resized image, without the additional thumbnail
490 - # features
491 -
492 - if ( $height == false )
493 - $height = -1;
494 - if ( $manual_thumb == '') {
495 - $thumb = $img->getThumbnail( $width, $height, $wgGenerateThumbnailOnParse );
496 - if ( $thumb ) {
497 - // In most cases, $width = $thumb->width or $height = $thumb->height.
498 - // If not, we're scaling the image larger than it can be scaled,
499 - // so we send to the browser a smaller thumbnail, and let the client do the scaling.
500 -
501 - if ($height != -1 && $width > $thumb->width * $height / $thumb->height) {
502 - // $height is the limiting factor, not $width
503 - // set $width to the largest it can be, such that the resulting
504 - // scaled height is at most $height
505 - $width = floor($thumb->width * $height / $thumb->height);
506 - }
507 - $height = round($thumb->height * $width / $thumb->width);
508 -
509 - wfDebug( "makeImageLinkObj: client-size set to '$width x $height'\n" );
510 - $url = $thumb->getUrl();
511 - } else {
512 - $error = htmlspecialchars( $img->getLastError() );
513 - // Do client-side scaling...
514 - $height = intval( $img->getHeight() * $width / $img->getWidth() );
515 - }
516 - }
 486+ if ( $page ) {
 487+ $query = 'page=' . urlencode( $page );
517488 } else {
518 - $width = $img->width;
519 - $height = $img->height;
 489+ $query = '';
520490 }
 491+ $u = $nt->getLocalURL( $query );
 492+ $imgAttribs = array(
 493+ 'alt' => $alt,
 494+ 'longdesc' => $u
 495+ );
 496+ if ( $valign ) {
 497+ $imgAttribs['style'] = "vertical-align: $valign";
 498+ }
 499+ $linkAttribs = array(
 500+ 'href' => $u,
 501+ 'class' => 'image',
 502+ 'title' => $alt
 503+ );
521504
522 - wfDebug( "makeImageLinkObj2: '$width'x'$height'\n" );
523 - $u = $nt->escapeLocalURL();
524 - if ( $error ) {
525 - $s = $error;
526 - } elseif ( $url == '' ) {
 505+ if ( !$thumb ) {
527506 $s = $this->makeBrokenImageLinkObj( $img->getTitle() );
528 - //$s .= "<br />{$alt}<br />{$url}<br />\n";
529507 } else {
530 - $s = '<a href="'.$u.'" class="image" title="'.$alt.'">' .
531 - '<img src="'.$url.'" alt="'.$alt.'" ' .
532 - ( $width
533 - ? ( 'width="'.$width.'" height="'.$height.'" ' )
534 - : '' ) .
535 - ( $valign
536 - ? ( 'style="vertical-align: '.$valign.'" ' )
537 - : '' ) .
538 - 'longdesc="'.$u.'" /></a>';
 508+ $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
539509 }
540510 if ( '' != $align ) {
541511 $s = "<div class=\"float{$align}\"><span>{$s}</span></div>";
@@ -546,86 +516,64 @@
547517 * Make HTML for a thumbnail including image, border and caption
548518 * $img is an Image object
549519 */
550 - function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $boxwidth = 180, $boxheight=false, $framed=false , $manual_thumb = "" ) {
551 - global $wgStylePath, $wgContLang, $wgGenerateThumbnailOnParse;
 520+ function makeThumbLinkObj( $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
 521+ global $wgStylePath, $wgContLang;
552522 $thumbUrl = '';
553523 $error = '';
554524
555 - $width = $height = 0;
556 - if ( $img->exists() ) {
557 - $width = $img->getWidth();
558 - $height = $img->getHeight();
 525+ $page = isset( $params['page'] ) ? $params['page'] : false;
 526+
 527+ if ( empty( $params['width'] ) ) {
 528+ $params['width'] = 180;
559529 }
560 - if ( 0 == $width || 0 == $height ) {
561 - $width = $height = 180;
562 - }
563 - if ( $boxwidth == 0 ) {
564 - $boxwidth = 180;
565 - }
566 - if ( $framed ) {
 530+ $thumb = false;
 531+ if ( $manual_thumb != '' ) {
 532+ # Use manually specified thumbnail
 533+ $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
 534+ if( $manual_title ) {
 535+ $manual_img = new Image( $manual_title );
 536+ $thumb = $manual_img->getUnscaledThumb();
 537+ }
 538+ } elseif ( $framed ) {
567539 // Use image dimensions, don't scale
568 - $boxwidth = $width;
569 - $boxheight = $height;
570 - $thumbUrl = $img->getViewURL();
 540+ $thumb = $img->getUnscaledThumb( $page );
571541 } else {
572 - if ( $boxheight === false )
573 - $boxheight = -1;
574 - if ( '' == $manual_thumb ) {
575 - $thumb = $img->getThumbnail( $boxwidth, $boxheight, $wgGenerateThumbnailOnParse );
576 - if ( $thumb ) {
577 - $thumbUrl = $thumb->getUrl();
578 - $boxwidth = $thumb->width;
579 - $boxheight = $thumb->height;
580 - } else {
581 - $error = $img->getLastError();
582 - }
583 - }
 542+ $thumb = $img->transform( $params );
584543 }
585 - $oboxwidth = $boxwidth + 2;
586544
587 - if ( $manual_thumb != '' ) # Use manually specified thumbnail
588 - {
589 - $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); #new Title ( $manual_thumb ) ;
590 - if( $manual_title ) {
591 - $manual_img = new Image( $manual_title );
592 - $thumbUrl = $manual_img->getViewURL();
593 - if ( $manual_img->exists() )
594 - {
595 - $width = $manual_img->getWidth();
596 - $height = $manual_img->getHeight();
597 - $boxwidth = $width ;
598 - $boxheight = $height ;
599 - $oboxwidth = $boxwidth + 2 ;
600 - }
601 - }
 545+ if ( $thumb ) {
 546+ $outerWidth = $thumb->getWidth() + 2;
 547+ } else {
 548+ $outerWidth = $params['width'] + 2;
602549 }
603550
604 - $u = $img->getEscapeLocalURL();
 551+ $query = $page ? 'page=' . urlencode( $page ) : '';
 552+ $u = $img->getTitle()->getLocalURL( $query );
605553
606554 $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
607555 $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
608556 $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
609557
610 - $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$oboxwidth}px;\">";
611 - if( $thumbUrl == '' ) {
612 - // Couldn't generate thumbnail? Scale the image client-side.
613 - $thumbUrl = $img->getViewURL();
614 - if( $boxheight == -1 ) {
615 - // Approximate...
616 - $boxheight = round( $height * $boxwidth / $width );
617 - }
618 - }
619 - if ( $error ) {
620 - $s .= htmlspecialchars( $error );
 558+ $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
 559+ if ( !$thumb ) {
 560+ $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
621561 $zoomicon = '';
622562 } elseif( !$img->exists() ) {
623563 $s .= $this->makeBrokenImageLinkObj( $img->getTitle() );
624564 $zoomicon = '';
625565 } else {
626 - $s .= '<a href="'.$u.'" class="internal" title="'.$alt.'">'.
627 - '<img src="'.$thumbUrl.'" alt="'.$alt.'" ' .
628 - 'width="'.$boxwidth.'" height="'.$boxheight.'" ' .
629 - 'longdesc="'.$u.'" class="thumbimage" /></a>';
 566+ $imgAttribs = array(
 567+ 'alt' => $alt,
 568+ 'longdesc' => $u,
 569+ 'class' => 'thumbimage'
 570+ );
 571+ $linkAttribs = array(
 572+ 'href' => $u,
 573+ 'title' => $alt,
 574+ 'class' => 'internal'
 575+ );
 576+
 577+ $s .= $thumb->toHtml( $imgAttribs, $linkAttribs );
630578 if ( $framed ) {
631579 $zoomicon="";
632580 } else {
Index: trunk/phase3/includes/Exif.php
@@ -93,9 +93,9 @@
9494 var $basename;
9595
9696 /**
97 - * The private log to log to
 97+ * The private log to log to, e.g. 'exif'
9898 */
99 - var $log = 'exif';
 99+ var $log = false;
100100
101101 //@}
102102
@@ -561,7 +561,10 @@
562562 * @param $fname String:
563563 * @param $action Mixed: , default NULL.
564564 */
565 - function debug( $in, $fname, $action = NULL ) {
 565+ function debug( $in, $fname, $action = NULL ) {
 566+ if ( !$this->log ) {
 567+ return;
 568+ }
566569 $type = gettype( $in );
567570 $class = ucfirst( __CLASS__ );
568571 if ( $type === 'array' )
@@ -586,6 +589,9 @@
587590 * @param $io Boolean: Specify whether we're beginning or ending
588591 */
589592 function debugFile( $fname, $io ) {
 593+ if ( !$this->log ) {
 594+ return;
 595+ }
590596 $class = ucfirst( __CLASS__ );
591597 if ( $io ) {
592598 wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" );
Index: trunk/phase3/includes/Parser.php
@@ -4387,8 +4387,8 @@
43884388 * Parse image options text and use it to make an image
43894389 */
43904390 function makeImage( $nt, $options ) {
4391 - global $wgUseImageResize, $wgDjvuRenderer;
4392 -
 4391+ # @TODO: let the MediaHandler specify its transform parameters
 4392+ #
43934393 # Check if the options text is of the form "options|alt text"
43944394 # Options are:
43954395 # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang
@@ -4408,6 +4408,7 @@
44094409 # * bottom
44104410 # * text-bottom
44114411
 4412+
44124413 $part = array_map( 'trim', explode( '|', $options) );
44134414
44144415 $mwAlign = array();
@@ -4422,13 +4423,14 @@
44234424 $mwPage =& MagicWord::get( 'img_page' );
44244425 $caption = '';
44254426
4426 - $width = $height = $framed = $thumb = false;
4427 - $page = null;
 4427+ $params = array();
 4428+ $framed = $thumb = false;
44284429 $manual_thumb = '' ;
44294430 $align = $valign = '';
 4431+ $sk = $this->mOptions->getSkin();
44304432
44314433 foreach( $part as $val ) {
4432 - if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
 4434+ if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
44334435 $thumb=true;
44344436 } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
44354437 # use manually specified thumbnail
@@ -4446,19 +4448,18 @@
44474449 continue 2;
44484450 }
44494451 }
4450 - if ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer
4451 - && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
 4452+ if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
44524453 # Select a page in a multipage document
4453 - $page = $match;
4454 - } elseif ( $wgUseImageResize && !$width && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
 4454+ $params['page'] = $match;
 4455+ } elseif ( !isset( $params['width'] ) && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
44554456 wfDebug( "img_width match: $match\n" );
44564457 # $match is the image width in pixels
44574458 $m = array();
44584459 if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
4459 - $width = intval( $m[1] );
4460 - $height = intval( $m[2] );
 4460+ $params['width'] = intval( $m[1] );
 4461+ $params['height'] = intval( $m[2] );
44614462 } else {
4462 - $width = intval($match);
 4463+ $params['width'] = intval($match);
44634464 }
44644465 } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
44654466 $framed=true;
@@ -4477,8 +4478,7 @@
44784479 $alt = Sanitizer::stripAllTags( $alt );
44794480
44804481 # Linker does the rest
4481 - $sk = $this->mOptions->getSkin();
4482 - return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $width, $height, $framed, $thumb, $manual_thumb, $page, $valign );
 4482+ return $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $params, $framed, $thumb, $manual_thumb, $valign );
44834483 }
44844484
44854485 /**
Index: trunk/phase3/includes/media/Bitmap.php
@@ -0,0 +1,226 @@
 2+<?php
 3+
 4+class BitmapHandler extends ImageHandler {
 5+ function normaliseParams( $image, &$params ) {
 6+ global $wgMaxImageArea;
 7+ if ( !parent::normaliseParams( $image, $params ) ) {
 8+ return false;
 9+ }
 10+
 11+ $mimeType = $image->getMimeType();
 12+ $srcWidth = $image->getWidth( $params['page'] );
 13+ $srcHeight = $image->getHeight( $params['page'] );
 14+
 15+ # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
 16+ # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
 17+ # an exception for it.
 18+ if ( $mimeType !== 'image/jpeg' &&
 19+ $srcWidth * $srcHeight > $wgMaxImageArea )
 20+ {
 21+ return false;
 22+ }
 23+
 24+ # Don't make an image bigger than the source
 25+ $params['physicalWidth'] = $params['width'];
 26+ $params['physicalHeight'] = $params['height'];
 27+
 28+ if ( $params['physicalWidth'] >= $srcWidth ) {
 29+ $params['physicalWidth'] = $srcWidth;
 30+ $params['physicalHeight'] = $srcHeight;
 31+ return true;
 32+ }
 33+
 34+ return true;
 35+ }
 36+
 37+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
 38+ global $wgUseImageMagick, $wgImageMagickConvertCommand;
 39+ global $wgCustomConvertCommand;
 40+ global $wgSharpenParameter, $wgSharpenReductionThreshold;
 41+
 42+ if ( !$this->normaliseParams( $image, $params ) ) {
 43+ return new TransformParameterError( $params );
 44+ }
 45+ $physicalWidth = $params['physicalWidth'];
 46+ $physicalHeight = $params['physicalHeight'];
 47+ $clientWidth = $params['width'];
 48+ $clientHeight = $params['height'];
 49+ $srcWidth = $image->getWidth();
 50+ $srcHeight = $image->getHeight();
 51+ $mimeType = $image->getMimeType();
 52+ $srcPath = $image->getImagePath();
 53+ $retval = 0;
 54+ wfDebug( __METHOD__.": creating {$physicalWidth}x{$physicalHeight} thumbnail at $dstPath\n" );
 55+
 56+ if ( $physicalWidth == $srcWidth && $physicalHeight == $srcHeight ) {
 57+ # normaliseParams (or the user) wants us to return the unscaled image
 58+ wfDebug( __METHOD__.": returning unscaled image\n" );
 59+ return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
 60+ }
 61+
 62+ if ( $wgUseImageMagick ) {
 63+ $scaler = 'im';
 64+ } elseif ( $wgCustomConvertCommand ) {
 65+ $scaler = 'custom';
 66+ } elseif ( function_exists( 'imagecreatetruecolor' ) ) {
 67+ $scaler = 'gd';
 68+ } else {
 69+ $scaler = 'client';
 70+ }
 71+
 72+ if ( $scaler == 'client' ) {
 73+ # Client-side image scaling, use the source URL
 74+ # Using the destination URL in a TRANSFORM_LATER request would be incorrect
 75+ return new ThumbnailImage( $image->getURL(), $clientWidth, $clientHeight, $srcPath );
 76+ }
 77+
 78+ if ( $flags & self::TRANSFORM_LATER ) {
 79+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
 80+ }
 81+
 82+ if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
 83+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
 84+ wfMsg( 'thumbnail_dest_directory' ) );
 85+ }
 86+
 87+ if ( $scaler == 'im' ) {
 88+ # use ImageMagick
 89+
 90+ $sharpen = '';
 91+ if ( $mimeType == 'image/jpeg' ) {
 92+ $quality = "-quality 80"; // 80%
 93+ # Sharpening, see bug 6193
 94+ if ( ( $physicalWidth + $physicalHeight ) / ( $srcWidth + $srcHeight ) < $wgSharpenReductionThreshold ) {
 95+ $sharpen = "-sharpen " . wfEscapeShellArg( $wgSharpenParameter );
 96+ }
 97+ } elseif ( $mimeType == 'image/png' ) {
 98+ $quality = "-quality 95"; // zlib 9, adaptive filtering
 99+ } else {
 100+ $quality = ''; // default
 101+ }
 102+
 103+ # Specify white background color, will be used for transparent images
 104+ # in Internet Explorer/Windows instead of default black.
 105+
 106+ # Note, we specify "-size {$physicalWidth}" and NOT "-size {$physicalWidth}x{$physicalHeight}".
 107+ # It seems that ImageMagick has a bug wherein it produces thumbnails of
 108+ # the wrong size in the second case.
 109+
 110+ $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) .
 111+ " {$quality} -background white -size {$physicalWidth} ".
 112+ wfEscapeShellArg($srcPath) .
 113+ // Coalesce is needed to scale animated GIFs properly (bug 1017).
 114+ ' -coalesce ' .
 115+ // For the -resize option a "!" is needed to force exact size,
 116+ // or ImageMagick may decide your ratio is wrong and slice off
 117+ // a pixel.
 118+ " -thumbnail " . wfEscapeShellArg( "{$physicalWidth}x{$physicalHeight}!" ) .
 119+ " -depth 8 $sharpen " .
 120+ wfEscapeShellArg($dstPath) . " 2>&1";
 121+ wfDebug( __METHOD__.": running ImageMagick: $cmd\n");
 122+ wfProfileIn( 'convert' );
 123+ $err = wfShellExec( $cmd, $retval );
 124+ wfProfileOut( 'convert' );
 125+ } elseif( $scaler == 'custom' ) {
 126+ # Use a custom convert command
 127+ # Variables: %s %d %w %h
 128+ $src = wfEscapeShellArg( $srcPath );
 129+ $dst = wfEscapeShellArg( $dstPath );
 130+ $cmd = $wgCustomConvertCommand;
 131+ $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
 132+ $cmd = str_replace( '%h', $physicalHeight, str_replace( '%w', $physicalWidth, $cmd ) ); # Size
 133+ wfDebug( __METHOD__.": Running custom convert command $cmd\n" );
 134+ wfProfileIn( 'convert' );
 135+ $err = wfShellExec( $cmd, $retval );
 136+ wfProfileOut( 'convert' );
 137+ } else /* $scaler == 'gd' */ {
 138+ # Use PHP's builtin GD library functions.
 139+ #
 140+ # First find out what kind of file this is, and select the correct
 141+ # input routine for this.
 142+
 143+ $typemap = array(
 144+ 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
 145+ 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
 146+ 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
 147+ 'image/vnd.wap.wbmp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
 148+ 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
 149+ );
 150+ if( !isset( $typemap[$mimeType] ) ) {
 151+ $err = 'Image type not supported';
 152+ wfDebug( "$err\n" );
 153+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
 154+ }
 155+ list( $loader, $colorStyle, $saveType ) = $typemap[$mimeType];
 156+
 157+ if( !function_exists( $loader ) ) {
 158+ $err = "Incomplete GD library configuration: missing function $loader";
 159+ wfDebug( "$err\n" );
 160+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
 161+ }
 162+
 163+ $src_image = call_user_func( $loader, $srcPath );
 164+ $dst_image = imagecreatetruecolor( $physicalWidth, $physicalHeight );
 165+ imagecopyresampled( $dst_image, $src_image,
 166+ 0,0,0,0,
 167+ $physicalWidth, $physicalHeight, imagesx( $src_image ), imagesy( $src_image ) );
 168+ call_user_func( $saveType, $dst_image, $dstPath );
 169+ imagedestroy( $dst_image );
 170+ imagedestroy( $src_image );
 171+ $retval = 0;
 172+ }
 173+
 174+ $removed = $this->removeBadFile( $dstPath, $retval );
 175+ if ( $retval != 0 || $removed ) {
 176+ wfDebugLog( 'thumbnail',
 177+ sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
 178+ wfHostname(), $retval, trim($err), $cmd ) );
 179+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
 180+ } else {
 181+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight, $dstPath );
 182+ }
 183+ }
 184+
 185+ static function imageJpegWrapper( $dst_image, $thumbPath ) {
 186+ imageinterlace( $dst_image );
 187+ imagejpeg( $dst_image, $thumbPath, 95 );
 188+ }
 189+
 190+
 191+ function getMetadata( $image, $filename ) {
 192+ global $wgShowEXIF;
 193+ if( $wgShowEXIF && file_exists( $filename ) ) {
 194+ $exif = new Exif( $filename );
 195+ return serialize( $exif->getFilteredData() );
 196+ } else {
 197+ return '';
 198+ }
 199+ }
 200+
 201+ function getMetadataType( $image ) {
 202+ return 'exif';
 203+ }
 204+
 205+ function isMetadataValid( $image, $metadata ) {
 206+ global $wgShowEXIF;
 207+ if ( !$wgShowEXIF ) {
 208+ # Metadata disabled and so an empty field is expected
 209+ return true;
 210+ }
 211+ if ( $metadata === '0' ) {
 212+ # Special value indicating that there is no EXIF data in the file
 213+ return true;
 214+ }
 215+ $exif = @unserialize( $metadata );
 216+ if ( !isset( $exif['MEDIAWIKI_EXIF_VERSION'] ) ||
 217+ $exif['MEDIAWIKI_EXIF_VERSION'] != Exif::version() )
 218+ {
 219+ # Wrong version
 220+ return false;
 221+ }
 222+ return true;
 223+ }
 224+
 225+}
 226+
 227+?>
Property changes on: trunk/phase3/includes/media/Bitmap.php
___________________________________________________________________
Added: svn:eol-style
1228 + native
Index: trunk/phase3/includes/media/Generic.php
@@ -0,0 +1,292 @@
 2+<?php
 3+
 4+/**
 5+ * Media-handling base classes and generic functionality
 6+ */
 7+
 8+/**
 9+ * Base media handler class
 10+ */
 11+abstract class MediaHandler {
 12+ const TRANSFORM_LATER = 1;
 13+
 14+ /**
 15+ * Instance cache
 16+ */
 17+ static $handlers = array();
 18+
 19+ /**
 20+ * Get a MediaHandler for a given MIME type from the instance cache
 21+ */
 22+ static function getHandler( $type ) {
 23+ global $wgMediaHandlers;
 24+ if ( !isset( $wgMediaHandlers[$type] ) ) {
 25+ return false;
 26+ }
 27+ $class = $wgMediaHandlers[$type];
 28+ if ( !isset( self::$handlers[$class] ) ) {
 29+ self::$handlers[$class] = new $class;
 30+ if ( !self::$handlers[$class]->isEnabled() ) {
 31+ self::$handlers[$class] = false;
 32+ }
 33+ }
 34+ return self::$handlers[$class];
 35+ }
 36+
 37+ /*
 38+ * Validate a thumbnail parameter at parse time.
 39+ * Return true to accept the parameter, and false to reject it.
 40+ * If you return false, the parser will do something quiet and forgiving.
 41+ */
 42+ abstract function validateParam( $name, $value );
 43+
 44+ /**
 45+ * Merge a parameter array into a string appropriate for inclusion in filenames
 46+ */
 47+ abstract function makeParamString( $params );
 48+
 49+ /**
 50+ * Parse a param string made with makeParamString back into an array
 51+ */
 52+ abstract function parseParamString( $str );
 53+
 54+ /**
 55+ * Changes the parameter array as necessary, ready for transformation.
 56+ * Should be idempotent.
 57+ * Returns false if the parameters are unacceptable and the transform should fail
 58+ */
 59+ abstract function normaliseParams( $image, &$params );
 60+
 61+ /**
 62+ * Get an image size array like that returned by getimagesize(), or false if it
 63+ * can't be determined.
 64+ *
 65+ * @param Image $image The image object, or false if there isn't one
 66+ * @param string $fileName The filename
 67+ * @return array
 68+ */
 69+ abstract function getImageSize( $image, $path );
 70+
 71+ /**
 72+ * Get handler-specific metadata which will be saved in the img_metadata field.
 73+ *
 74+ * @param Image $image The image object, or false if there isn't one
 75+ * @param string $fileName The filename
 76+ * @return string
 77+ */
 78+ function getMetadata( $image, $path ) { return ''; }
 79+
 80+ /**
 81+ * Get a string describing the type of metadata, for display purposes.
 82+ */
 83+ function getMetadataType( $image ) { return false; }
 84+
 85+ /**
 86+ * Check if the metadata string is valid for this handler.
 87+ * If it returns false, Image will reload the metadata from the file and update the database
 88+ */
 89+ function isMetadataValid( $image, $metadata ) { return true; }
 90+
 91+ /**
 92+ * Get a MediaTransformOutput object representing the transformed output. Does not
 93+ * actually do the transform.
 94+ *
 95+ * @param Image $image The image object
 96+ * @param string $dstPath Filesystem destination path
 97+ * @param string $dstUrl Destination URL to use in output HTML
 98+ * @param array $params Arbitrary set of parameters validated by $this->validateParam()
 99+ */
 100+ function getTransform( $image, $dstPath, $dstUrl, $params ) {
 101+ return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
 102+ }
 103+
 104+ /**
 105+ * Get a MediaTransformOutput object representing the transformed output. Does the
 106+ * transform unless $flags contains self::TRANSFORM_LATER.
 107+ *
 108+ * @param Image $image The image object
 109+ * @param string $dstPath Filesystem destination path
 110+ * @param string $dstUrl Destination URL to use in output HTML
 111+ * @param array $params Arbitrary set of parameters validated by $this->validateParam()
 112+ * @param integer $flags A bitfield, may contain self::TRANSFORM_LATER
 113+ */
 114+ abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
 115+
 116+ /**
 117+ * Get the thumbnail extension and MIME type for a given source MIME type
 118+ * @return array thumbnail extension and MIME type
 119+ */
 120+ function getThumbType( $ext, $mime ) {
 121+ return array( $ext, $mime );
 122+ }
 123+
 124+ /**
 125+ * True if the handled types can be transformed
 126+ */
 127+ function canRender() { return true; }
 128+ /**
 129+ * True if handled types cannot be displayed directly in a browser
 130+ * but can be rendered
 131+ */
 132+ function mustRender() { return false; }
 133+ /**
 134+ * True if the type has multi-page capabilities
 135+ */
 136+ function isMultiPage() { return false; }
 137+ /**
 138+ * Page count for a multi-page document, false if unsupported or unknown
 139+ */
 140+ function pageCount() { return false; }
 141+ /**
 142+ * False if the handler is disabled for all files
 143+ */
 144+ function isEnabled() { return true; }
 145+
 146+ /**
 147+ * Get an associative array of page dimensions
 148+ * Currently "width" and "height" are understood, but this might be
 149+ * expanded in the future.
 150+ * Returns false if unknown or if the document is not multi-page.
 151+ */
 152+ function getPageDimensions( $image, $page ) {
 153+ $gis = $this->getImageSize( $image, $image->getImagePath() );
 154+ return array(
 155+ 'width' => $gis[0],
 156+ 'height' => $gis[1]
 157+ );
 158+ }
 159+}
 160+
 161+/**
 162+ * Media handler abstract base class for images
 163+ */
 164+abstract class ImageHandler extends MediaHandler {
 165+ function validateParam( $name, $value ) {
 166+ if ( in_array( $name, array( 'width', 'height' ) ) ) {
 167+ if ( $value <= 0 ) {
 168+ return false;
 169+ } else {
 170+ return true;
 171+ }
 172+ } else {
 173+ return false;
 174+ }
 175+ }
 176+
 177+ function makeParamString( $params ) {
 178+ if ( isset( $params['physicalWidth'] ) ) {
 179+ $width = $params['physicalWidth'];
 180+ } else {
 181+ $width = $params['width'];
 182+ }
 183+ $width = intval( $width );
 184+ return "{$width}px";
 185+ }
 186+
 187+ function parseParamString( $str ) {
 188+ $m = false;
 189+ if ( preg_match( '/^(\d+)px$/', $str, $m ) ) {
 190+ return array( 'width' => $m[1] );
 191+ } else {
 192+ return false;
 193+ }
 194+ }
 195+
 196+ function getScriptParams( $params ) {
 197+ return array( 'width' => $params['width'] );
 198+ }
 199+
 200+ function normaliseParams( $image, &$params ) {
 201+ $mimeType = $image->getMimeType();
 202+
 203+ if ( !isset( $params['width'] ) ) {
 204+ return false;
 205+ }
 206+ if ( !isset( $params['page'] ) ) {
 207+ $params['page'] = 1;
 208+ }
 209+ $srcWidth = $image->getWidth( $params['page'] );
 210+ $srcHeight = $image->getHeight( $params['page'] );
 211+ if ( isset( $params['height'] ) && $params['height'] != -1 ) {
 212+ if ( $params['width'] * $srcHeight > $params['height'] * $srcWidth ) {
 213+ $params['width'] = wfFitBoxWidth( $srcWidth, $srcHeight, $params['height'] );
 214+ }
 215+ }
 216+ $params['height'] = Image::scaleHeight( $srcWidth, $srcHeight, $params['width'] );
 217+ if ( !$this->validateThumbParams( $params['width'], $params['height'], $srcWidth, $srcHeight, $mimeType ) ) {
 218+ return false;
 219+ }
 220+ return true;
 221+ }
 222+
 223+ /**
 224+ * Get a transform output object without actually doing the transform
 225+ */
 226+ function getTransform( $image, $dstPath, $dstUrl, $params ) {
 227+ return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
 228+ }
 229+
 230+ /**
 231+ * Validate thumbnail parameters and fill in the correct height
 232+ *
 233+ * @param integer &$width Specified width (input/output)
 234+ * @param integer &$height Height (output only)
 235+ * @return false to indicate that an error should be returned to the user.
 236+ */
 237+ function validateThumbParams( &$width, &$height, $srcWidth, $srcHeight, $mimeType ) {
 238+ $width = intval( $width );
 239+
 240+ # Sanity check $width
 241+ if( $width <= 0) {
 242+ wfDebug( __METHOD__.": Invalid destination width: $width\n" );
 243+ return false;
 244+ }
 245+ if ( $srcWidth <= 0 ) {
 246+ wfDebug( __METHOD__.": Invalid source width: $srcWidth\n" );
 247+ return false;
 248+ }
 249+
 250+ $height = Image::scaleHeight( $srcWidth, $srcHeight, $width );
 251+ return true;
 252+ }
 253+
 254+ function getScriptedTransform( $image, $script, $params ) {
 255+ if ( !$this->normaliseParams( $image, $params ) ) {
 256+ return false;
 257+ }
 258+ $url = $script . '&' . wfArrayToCGI( $this->getScriptParams( $params ) );
 259+ return new ThumbnailImage( $url, $params['width'], $params['height'] );
 260+ }
 261+
 262+ /**
 263+ * Check for zero-sized thumbnails. These can be generated when
 264+ * no disk space is available or some other error occurs
 265+ *
 266+ * @param $dstPath The location of the suspect file
 267+ * @param $retval Return value of some shell process, file will be deleted if this is non-zero
 268+ * @return true if removed, false otherwise
 269+ */
 270+ function removeBadFile( $dstPath, $retval = 0 ) {
 271+ $removed = false;
 272+ if( file_exists( $dstPath ) ) {
 273+ $thumbstat = stat( $dstPath );
 274+ if( $thumbstat['size'] == 0 || $retval != 0 ) {
 275+ wfDebugLog( 'thumbnail',
 276+ sprintf( 'Removing bad %d-byte thumbnail "%s"',
 277+ $thumbstat['size'], $dstPath ) );
 278+ unlink( $dstPath );
 279+ return true;
 280+ }
 281+ }
 282+ return false;
 283+ }
 284+
 285+ function getImageSize( $image, $path ) {
 286+ wfSuppressWarnings();
 287+ $gis = getimagesize( $path );
 288+ wfRestoreWarnings();
 289+ return $gis;
 290+ }
 291+}
 292+
 293+?>
Property changes on: trunk/phase3/includes/media/Generic.php
___________________________________________________________________
Added: svn:eol-style
1294 + native
Index: trunk/phase3/includes/media/SVG.php
@@ -0,0 +1,94 @@
 2+<?php
 3+
 4+class SvgHandler extends ImageHandler {
 5+ function isEnabled() {
 6+ global $wgSVGConverters, $wgSVGConverter;
 7+ if ( !isset( $wgSVGConverters[$wgSVGConverter] ) ) {
 8+ wfDebug( "\$wgSVGConverter is invalid, disabling SVG rendering.\n" );
 9+ return false;
 10+ } else {
 11+ return true;
 12+ }
 13+ }
 14+
 15+ function mustRender() {
 16+ return true;
 17+ }
 18+
 19+ function normaliseParams( $image, &$params ) {
 20+ global $wgSVGMaxSize;
 21+ if ( !parent::normaliseParams( $image, $params ) ) {
 22+ return false;
 23+ }
 24+
 25+ # Don't make an image bigger than wgMaxSVGSize
 26+ $params['physicalWidth'] = $params['width'];
 27+ $params['physicalHeight'] = $params['height'];
 28+ if ( $params['physicalWidth'] > $wgSVGMaxSize ) {
 29+ $srcWidth = $image->getWidth( $params['page'] );
 30+ $srcHeight = $image->getHeight( $params['page'] );
 31+ $params['physicalWidth'] = $wgSVGMaxSize;
 32+ $params['physicalHeight'] = Image::scaleHeight( $srcWidth, $srcHeight, $wgSVGMaxSize );
 33+ }
 34+ return true;
 35+ }
 36+
 37+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
 38+ global $wgSVGConverters, $wgSVGConverter, $wgSVGConverterPath;
 39+
 40+ if ( !$this->normaliseParams( $image, $params ) ) {
 41+ return new TransformParameterError( $params );
 42+ }
 43+ $clientWidth = $params['width'];
 44+ $clientHeight = $params['height'];
 45+ $physicalWidth = $params['physicalWidth'];
 46+ $physicalHeight = $params['physicalHeight'];
 47+ $srcWidth = $image->getWidth();
 48+ $srcHeight = $image->getHeight();
 49+ $srcPath = $image->getImagePath();
 50+
 51+ if ( $flags & self::TRANSFORM_LATER ) {
 52+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight );
 53+ }
 54+
 55+ if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
 56+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight,
 57+ wfMsg( 'thumbnail_dest_directory' ) );
 58+ }
 59+
 60+ $err = false;
 61+ if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
 62+ $cmd = str_replace(
 63+ array( '$path/', '$width', '$height', '$input', '$output' ),
 64+ array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
 65+ intval( $physicalWidth ),
 66+ intval( $physicalHeight ),
 67+ wfEscapeShellArg( $srcPath ),
 68+ wfEscapeShellArg( $dstPath ) ),
 69+ $wgSVGConverters[$wgSVGConverter] ) . " 2>&1";
 70+ wfProfileIn( 'rsvg' );
 71+ wfDebug( __METHOD__.": $cmd\n" );
 72+ $err = wfShellExec( $cmd, $retval );
 73+ wfProfileOut( 'rsvg' );
 74+ }
 75+
 76+ $removed = $this->removeBadFile( $dstPath, $retval );
 77+ if ( $retval != 0 || $removed ) {
 78+ wfDebugLog( 'thumbnail',
 79+ sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
 80+ wfHostname(), $retval, trim($err), $cmd ) );
 81+ return new MediaTransformError( 'thumbnail_error', $clientWidth, $clientHeight, $err );
 82+ } else {
 83+ return new ThumbnailImage( $dstUrl, $clientWidth, $clientHeight );
 84+ }
 85+ }
 86+
 87+ function getImageSize( $image, $path ) {
 88+ return wfGetSVGsize( $path );
 89+ }
 90+
 91+ function getThumbType( $ext, $mime ) {
 92+ return array( 'png', 'image/png' );
 93+ }
 94+}
 95+?>
Property changes on: trunk/phase3/includes/media/SVG.php
___________________________________________________________________
Added: svn:eol-style
196 + native
Index: trunk/phase3/includes/media/DjVu.php
@@ -0,0 +1,203 @@
 2+<?php
 3+
 4+class DjVuHandler extends ImageHandler {
 5+ function isEnabled() {
 6+ global $wgDjvuRenderer, $wgDjvuDump, $wgDjvuToXML;
 7+ if ( !$wgDjvuRenderer || ( !$wgDjvuDump && !$wgDjvuToXML ) ) {
 8+ wfDebug( "DjVu is disabled, please set \$wgDjvuRenderer and \$wgDjvuDump\n" );
 9+ return false;
 10+ } else {
 11+ return true;
 12+ }
 13+ }
 14+
 15+ function mustRender() { return true; }
 16+ function isMultiPage() { return true; }
 17+
 18+ function validateParam( $name, $value ) {
 19+ if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) {
 20+ if ( $value <= 0 ) {
 21+ return false;
 22+ } else {
 23+ return true;
 24+ }
 25+ } else {
 26+ return false;
 27+ }
 28+ }
 29+
 30+ function makeParamString( $params ) {
 31+ $page = isset( $params['page'] ) ? $params['page'] : 1;
 32+ if ( !isset( $params['width'] ) ) {
 33+ return false;
 34+ }
 35+ return "{$params['width']}px-page{$page}";
 36+ }
 37+
 38+ function parseParamString( $str ) {
 39+ $m = false;
 40+ if ( preg_match( '/^(\d+)px-page(\d+)$/', $str, $m ) ) {
 41+ return array( 'width' => $m[1], 'page' => $m[2] );
 42+ } else {
 43+ return false;
 44+ }
 45+ }
 46+
 47+ function getScriptParams( $params ) {
 48+ return array(
 49+ 'width' => $params['width'],
 50+ 'page' => $params['page'],
 51+ );
 52+ }
 53+
 54+ function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
 55+ global $wgDjvuRenderer, $wgDjvuPostProcessor;
 56+
 57+ // Fetch XML and check it, to give a more informative error message than the one which
 58+ // normaliseParams will inevitably give.
 59+ $xml = $image->getMetadata();
 60+ if ( !$xml ) {
 61+ return new MediaTransformError( 'thumbnail_error', @$params['width'], @$params['height'],
 62+ wfMsg( 'djvu_no_xml' ) );
 63+ }
 64+
 65+ if ( !$this->normaliseParams( $image, $params ) ) {
 66+ return new TransformParameterError( $params );
 67+ }
 68+ $width = $params['width'];
 69+ $height = $params['height'];
 70+ $srcPath = $image->getImagePath();
 71+ $page = $params['page'];
 72+ $pageCount = $this->pageCount( $image );
 73+ if ( $page > $this->pageCount( $image ) ) {
 74+ return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'djvu_page_error' ) );
 75+ }
 76+
 77+ if ( $flags & self::TRANSFORM_LATER ) {
 78+ return new ThumbnailImage( $dstUrl, $width, $height, $dstPath );
 79+ }
 80+
 81+ if ( !wfMkdirParents( dirname( $dstPath ) ) ) {
 82+ return new MediaTransformError( 'thumbnail_error', $width, $height, wfMsg( 'thumbnail_dest_directory' ) );
 83+ }
 84+
 85+ # Use a subshell (brackets) to aggregate stderr from both pipeline commands
 86+ # before redirecting it to the overall stdout. This works in both Linux and Windows XP.
 87+ $cmd = '(' . wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size={$width}x{$height} " .
 88+ wfEscapeShellArg( $srcPath );
 89+ if ( $wgDjvuPostProcessor ) {
 90+ $cmd .= " | {$wgDjvuPostProcessor}";
 91+ }
 92+ $cmd .= ' > ' . wfEscapeShellArg($dstPath) . ') 2>&1';
 93+ wfProfileIn( 'ddjvu' );
 94+ wfDebug( __METHOD__.": $cmd\n" );
 95+ $err = wfShellExec( $cmd, $retval );
 96+ wfProfileOut( 'ddjvu' );
 97+
 98+ $removed = $this->removeBadFile( $dstPath, $retval );
 99+ if ( $retval != 0 || $removed ) {
 100+ wfDebugLog( 'thumbnail',
 101+ sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
 102+ wfHostname(), $retval, trim($err), $cmd ) );
 103+ return new MediaTransformError( 'thumbnail_error', $width, $height, $err );
 104+ } else {
 105+ return new ThumbnailImage( $dstUrl, $width, $height, $dstPath );
 106+ }
 107+ }
 108+
 109+ /**
 110+ * Cache an instance of DjVuImage in an Image object, return that instance
 111+ */
 112+ function getDjVuImage( $image, $path ) {
 113+ if ( !$image ) {
 114+ $deja = new DjVuImage( $path );
 115+ } elseif ( !isset( $image->dejaImage ) ) {
 116+ $deja = $image->dejaImage = new DjVuImage( $path );
 117+ } else {
 118+ $deja = $image->dejaImage;
 119+ }
 120+ return $deja;
 121+ }
 122+
 123+ /**
 124+ * Cache a document tree for the DjVu XML metadata
 125+ */
 126+ function getMetaTree( $image ) {
 127+ if ( isset( $image->dejaMetaTree ) ) {
 128+ return $image->dejaMetaTree;
 129+ }
 130+
 131+ $metadata = $image->getMetadata();
 132+ if ( !$this->isMetadataValid( $image, $metadata ) ) {
 133+ wfDebug( "DjVu XML metadata is invalid or missing, should have been fixed in upgradeRow\n" );
 134+ return false;
 135+ }
 136+ wfProfileIn( __METHOD__ );
 137+
 138+ wfSuppressWarnings();
 139+ try {
 140+ $image->dejaMetaTree = new SimpleXMLElement( $metadata );
 141+ } catch( Exception $e ) {
 142+ wfDebug( "Bogus multipage XML metadata on '$image->name'\n" );
 143+ // Set to false rather than null to avoid further attempts
 144+ $image->dejaMetaTree = false;
 145+ }
 146+ wfRestoreWarnings();
 147+ wfProfileOut( __METHOD__ );
 148+ return $image->dejaMetaTree;
 149+ }
 150+
 151+ function getImageSize( $image, $path ) {
 152+ return $this->getDjVuImage( $image, $path )->getImageSize();
 153+ }
 154+
 155+ function getThumbType( $ext, $mime ) {
 156+ global $wgDjvuOutputExtension;
 157+ static $mime;
 158+ if ( !isset( $mime ) ) {
 159+ $magic = MimeMagic::singleton();
 160+ $mime = $magic->guessTypesForExtension( $wgDjvuOutputExtension );
 161+ }
 162+ return array( $wgDjvuOutputExtension, $mime );
 163+ }
 164+
 165+ function getMetadata( $image, $path ) {
 166+ wfDebug( "Getting DjVu metadata for $path\n" );
 167+ return $this->getDjVuImage( $image, $path )->retrieveMetaData();
 168+ }
 169+
 170+ function getMetadataType( $image ) {
 171+ return 'djvuxml';
 172+ }
 173+
 174+ function isMetadataValid( $image, $metadata ) {
 175+ return !empty( $metadata ) && $metadata != serialize(array());
 176+ }
 177+
 178+ function pageCount( $image ) {
 179+ $tree = $this->getMetaTree( $image );
 180+ if ( !$tree ) {
 181+ return false;
 182+ }
 183+ return count( $tree->xpath( '//OBJECT' ) );
 184+ }
 185+
 186+ function getPageDimensions( $image, $page ) {
 187+ $tree = $this->getMetaTree( $image );
 188+ if ( !$tree ) {
 189+ return false;
 190+ }
 191+
 192+ $o = $tree->BODY[0]->OBJECT[$page-1];
 193+ if ( $o ) {
 194+ return array(
 195+ 'width' => intval( $o['width'] ),
 196+ 'height' => intval( $o['height'] )
 197+ );
 198+ } else {
 199+ return false;
 200+ }
 201+ }
 202+}
 203+
 204+?>
Property changes on: trunk/phase3/includes/media/DjVu.php
___________________________________________________________________
Added: svn:eol-style
1205 + native
Index: trunk/phase3/includes/AutoLoader.php
@@ -8,6 +8,7 @@
99 global $wgAutoloadClasses;
1010
1111 static $localClasses = array(
 12+ # Includes
1213 'AjaxDispatcher' => 'includes/AjaxDispatcher.php',
1314 'AjaxCachePolicy' => 'includes/AjaxFunctions.php',
1415 'AjaxResponse' => 'includes/AjaxResponse.php',
@@ -115,6 +116,10 @@
116117 'MacBinary' => 'includes/MacBinary.php',
117118 'MagicWord' => 'includes/MagicWord.php',
118119 'MathRenderer' => 'includes/Math.php',
 120+ 'MediaTransformOutput' => 'includes/MediaTransformOutput.php',
 121+ 'ThumbnailImage' => 'includes/MediaTransformOutput.php',
 122+ 'MediaTransformError' => 'includes/MediaTransformOutput.php',
 123+ 'TransformParameterError' => 'includes/MediaTransformOutput.php',
119124 'MessageCache' => 'includes/MessageCache.php',
120125 'MimeMagic' => 'includes/MimeMagic.php',
121126 'Namespace' => 'includes/Namespace.php',
@@ -128,6 +133,7 @@
129134 'ParserOutput' => 'includes/ParserOutput.php',
130135 'ParserOptions' => 'includes/ParserOptions.php',
131136 'ParserCache' => 'includes/ParserCache.php',
 137+ 'PatrolLog' => 'includes/PatrolLog.php',
132138 'ProfilerSimple' => 'includes/ProfilerSimple.php',
133139 'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php',
134140 'Profiler' => 'includes/Profiler.php',
@@ -196,6 +202,7 @@
197203 'PopularPagesPage' => 'includes/SpecialPopularpages.php',
198204 'PreferencesForm' => 'includes/SpecialPreferences.php',
199205 'SpecialPrefixindex' => 'includes/SpecialPrefixindex.php',
 206+ 'PasswordResetForm' => 'includes/SpecialResetpass.php',
200207 'RevisionDeleteForm' => 'includes/SpecialRevisiondelete.php',
201208 'RevisionDeleter' => 'includes/SpecialRevisiondelete.php',
202209 'SpecialSearch' => 'includes/SpecialSearch.php',
@@ -240,15 +247,26 @@
241248 'Xml' => 'includes/Xml.php',
242249 'ZhClient' => 'includes/ZhClient.php',
243250 'memcached' => 'includes/memcached-client.php',
 251+
 252+ # Media
 253+ 'BitmapHandler' => 'includes/media/Bitmap.php',
 254+ 'DjVuHandler' => 'includes/media/DjVu.php',
 255+ 'MediaHandler' => 'includes/media/Generic.php',
 256+ 'ImageHandler' => 'includes/media/Generic.php',
 257+ 'SvgHandler' => 'includes/media/SVG.php',
 258+
 259+ # Normal
244260 'UtfNormal' => 'includes/normal/UtfNormal.php',
 261+
 262+ # Templates
245263 'UsercreateTemplate' => 'includes/templates/Userlogin.php',
246264 'UserloginTemplate' => 'includes/templates/Userlogin.php',
 265+
 266+ # Languages
247267 'Language' => 'languages/Language.php',
248 - 'PasswordResetForm' => 'includes/SpecialResetpass.php',
249 - 'PatrolLog' => 'includes/PatrolLog.php',
250268 'RandomPage' => 'includes/SpecialRandompage.php',
251269
252 - // API classes
 270+ # API
253271 'ApiBase' => 'includes/api/ApiBase.php',
254272 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php',
255273 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php',
Index: trunk/phase3/includes/Image.php
@@ -27,7 +27,8 @@
2828 const DELETED_FILE = 1;
2929 const DELETED_COMMENT = 2;
3030 const DELETED_USER = 4;
31 - const DELETED_RESTRICTED = 8;
 31+ const DELETED_RESTRICTED = 8;
 32+ const RENDER_NOW = 1;
3233
3334 /**#@+
3435 * @private
@@ -85,13 +86,12 @@
8687 }
8788 $this->title =& $title;
8889 $this->name = $title->getDBkey();
89 - $this->metadata = serialize ( array() ) ;
 90+ $this->metadata = '';
9091
9192 $n = strrpos( $this->name, '.' );
9293 $this->extension = Image::normalizeExtension( $n ?
9394 substr( $this->name, $n + 1 ) : '' );
9495 $this->historyLine = 0;
95 - $this->page = 1;
9696
9797 $this->dataLoaded = false;
9898 }
@@ -244,7 +244,6 @@
245245 $this->fileExists = file_exists( $this->imagePath );
246246 $this->fromSharedDirectory = false;
247247 $gis = array();
248 - $deja = false;
249248
250249 if (!$this->fileExists) wfDebug(__METHOD__.': '.$this->imagePath." not found locally!\n");
251250
@@ -268,18 +267,26 @@
269268
270269 $this->mime = $magic->guessMimeType($this->imagePath,true);
271270 $this->type = $magic->getMediaType($this->imagePath,$this->mime);
 271+ $handler = MediaHandler::getHandler( $this->mime );
272272
273273 # Get size in bytes
274274 $this->size = filesize( $this->imagePath );
275275
276 - # Height and width
277 - $gis = self::getImageSize( $this->imagePath, $this->mime, $deja );
 276+ # Height, width and metadata
 277+ if ( $handler ) {
 278+ $gis = $handler->getImageSize( $this, $this->imagePath );
 279+ $this->metadata = $handler->getMetadata( $this, $this->imagePath );
 280+ } else {
 281+ $gis = false;
 282+ $this->metadata = '';
 283+ }
278284
279285 wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n");
280286 }
281287 else {
282288 $this->mime = NULL;
283289 $this->type = MEDIATYPE_UNKNOWN;
 290+ $this->metadata = '';
284291 wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n");
285292 }
286293
@@ -298,13 +305,6 @@
299306 # as ther's only one thread of execution, this should be safe anyway.
300307 $this->dataLoaded = true;
301308
302 -
303 - if ( $deja ) {
304 - $this->metadata = $deja->retrieveMetaData();
305 - } else {
306 - $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) );
307 - }
308 -
309309 if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits'];
310310 else $this->bits = 0;
311311
@@ -331,9 +331,7 @@
332332 $this->loadFromRow( $row );
333333 $this->imagePath = $this->getFullPath();
334334 // Check for rows from a previous schema, quietly upgrade them
335 - if ( is_null($this->type) ) {
336 - $this->upgradeRow();
337 - }
 335+ $this->maybeUpgradeRow();
338336 } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) {
339337 # In case we're on a wgCapitalLinks=false wiki, we
340338 # capitalize the first letter of the filename before
@@ -354,9 +352,7 @@
355353 $this->loadFromRow( $row );
356354
357355 // Check for rows from a previous schema, quietly upgrade them
358 - if ( is_null($this->type) ) {
359 - $this->upgradeRow();
360 - }
 356+ $this->maybeUpgradeRow();
361357 }
362358 }
363359
@@ -368,7 +364,7 @@
369365 $this->type = 0;
370366 $this->fileExists = false;
371367 $this->fromSharedDirectory = false;
372 - $this->metadata = serialize ( array() ) ;
 368+ $this->metadata = '';
373369 $this->mime = false;
374370 }
375371
@@ -395,9 +391,7 @@
396392 if (!$minor) $minor= "unknown";
397393 $this->mime = $major.'/'.$minor;
398394 }
399 -
400395 $this->metadata = $row->img_metadata;
401 - if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ;
402396
403397 $this->dataLoaded = true;
404398 }
@@ -424,9 +418,22 @@
425419 }
426420
427421 /**
428 - * Metadata was loaded from the database, but the row had a marker indicating it needs to be
429 - * upgraded from the 1.4 schema, which had no width, height, bits or type. Upgrade the row.
 422+ * Upgrade a row if it needs it
430423 */
 424+ function maybeUpgradeRow() {
 425+ if ( is_null($this->type) || $this->mime == 'image/svg' ) {
 426+ $this->upgradeRow();
 427+ } else {
 428+ $handler = $this->getHandler();
 429+ if ( $handler && !$handler->isMetadataValid( $this, $this->metadata ) ) {
 430+ $this->upgradeRow();
 431+ }
 432+ }
 433+ }
 434+
 435+ /**
 436+ * Fix assorted version-related problems with the image row by reloading it from the file
 437+ */
431438 function upgradeRow() {
432439 global $wgDBname, $wgSharedUploadDBname;
433440 wfProfileIn( __METHOD__ );
@@ -450,7 +457,7 @@
451458
452459 list( $major, $minor ) = self::splitMime( $this->mime );
453460
454 - wfDebug(__METHOD__.': upgrading '.$this->name." to 1.5 schema\n");
 461+ wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n");
455462
456463 $dbw->update( 'image',
457464 array(
@@ -543,26 +550,52 @@
544551 /**
545552 * Return the width of the image
546553 *
547 - * Returns -1 if the file specified is not a known image type
 554+ * Returns false on error
548555 * @public
549556 */
550 - function getWidth() {
 557+ function getWidth( $page = 1 ) {
551558 $this->load();
552 - return $this->width;
 559+ if ( $this->isMultipage() ) {
 560+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
 561+ if ( $dim ) {
 562+ return $dim['width'];
 563+ } else {
 564+ return false;
 565+ }
 566+ } else {
 567+ return $this->width;
 568+ }
553569 }
554570
555571 /**
556572 * Return the height of the image
557573 *
558 - * Returns -1 if the file specified is not a known image type
 574+ * Returns false on error
559575 * @public
560576 */
561 - function getHeight() {
 577+ function getHeight( $page = 1 ) {
562578 $this->load();
563 - return $this->height;
 579+ if ( $this->isMultipage() ) {
 580+ $dim = $this->getHandler()->getPageDimensions( $this, $page );
 581+ if ( $dim ) {
 582+ return $dim['height'];
 583+ } else {
 584+ return false;
 585+ }
 586+ } else {
 587+ return $this->height;
 588+ }
564589 }
565590
566591 /**
 592+ * Get handler-specific metadata
 593+ */
 594+ function getMetadata() {
 595+ $this->load();
 596+ return $this->metadata;
 597+ }
 598+
 599+ /**
567600 * Return the size of the image file, in bytes
568601 * @public
569602 */
@@ -599,58 +632,10 @@
600633 * @todo remember the result of this check.
601634 */
602635 function canRender() {
603 - global $wgUseImageMagick, $wgDjvuRenderer;
604 -
605 - if( $this->getWidth()<=0 || $this->getHeight()<=0 ) return false;
606 -
607 - $mime= $this->getMimeType();
608 -
609 - if (!$mime || $mime==='unknown' || $mime==='unknown/unknown') return false;
610 -
611 - #if it's SVG, check if there's a converter enabled
612 - if ($mime === 'image/svg' || $mime == 'image/svg+xml' ) {
613 - global $wgSVGConverters, $wgSVGConverter;
614 -
615 - if ($wgSVGConverter && isset( $wgSVGConverters[$wgSVGConverter])) {
616 - wfDebug( "Image::canRender: SVG is ready!\n" );
617 - return true;
618 - } else {
619 - wfDebug( "Image::canRender: SVG renderer missing\n" );
620 - }
621 - }
622 -
623 - #image formats available on ALL browsers
624 - if ( $mime === 'image/gif'
625 - || $mime === 'image/png'
626 - || $mime === 'image/jpeg' ) return true;
627 -
628 - #image formats that can be converted to the above formats
629 - if ($wgUseImageMagick) {
630 - #convertable by ImageMagick (there are more...)
631 - if ( $mime === 'image/vnd.wap.wbmp'
632 - || $mime === 'image/x-xbitmap'
633 - || $mime === 'image/x-xpixmap'
634 - #|| $mime === 'image/x-icon' #file may be split into multiple parts
635 - || $mime === 'image/x-portable-anymap'
636 - || $mime === 'image/x-portable-bitmap'
637 - || $mime === 'image/x-portable-graymap'
638 - || $mime === 'image/x-portable-pixmap'
639 - #|| $mime === 'image/x-photoshop' #this takes a lot of CPU and RAM!
640 - || $mime === 'image/x-rgb'
641 - || $mime === 'image/x-bmp'
642 - || $mime === 'image/tiff' ) return true;
643 - }
644 - else {
645 - #convertable by the PHP GD image lib
646 - if ( $mime === 'image/vnd.wap.wbmp'
647 - || $mime === 'image/x-xbitmap' ) return true;
648 - }
649 - if ( $mime === 'image/vnd.djvu' && isset( $wgDjvuRenderer ) && $wgDjvuRenderer ) return true;
650 -
651 - return false;
 636+ $handler = $this->getHandler();
 637+ return $handler && $handler->canRender();
652638 }
653639
654 -
655640 /**
656641 * Return true if the file is of a type that can't be directly
657642 * rendered by typical browsers and needs to be re-rasterized.
@@ -662,13 +647,8 @@
663648 * @return bool
664649 */
665650 function mustRender() {
666 - $mime= $this->getMimeType();
667 -
668 - if ( $mime === "image/gif"
669 - || $mime === "image/png"
670 - || $mime === "image/jpeg" ) return false;
671 -
672 - return true;
 651+ $handler = $this->getHandler();
 652+ return $handler && $handler->mustRender();
673653 }
674654
675655 /**
@@ -734,15 +714,7 @@
735715 * @public
736716 */
737717 function getEscapeLocalURL( $query=false) {
738 - $this->getTitle();
739 - if ( $query === false ) {
740 - if ( $this->page != 1 ) {
741 - $query = 'page=' . $this->page;
742 - } else {
743 - $query = '';
744 - }
745 - }
746 - return $this->title->escapeLocalURL( $query );
 718+ return $this->getTitle()->escapeLocalURL( $query );
747719 }
748720
749721 /**
@@ -790,77 +762,72 @@
791763 * @todo document
792764 * @private
793765 */
794 - function thumbUrl( $width, $subdir='thumb') {
 766+ function thumbUrl( $thumbName ) {
795767 global $wgUploadPath, $wgUploadBaseUrl, $wgSharedUploadPath;
796 - global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
 768+ if($this->fromSharedDirectory) {
 769+ $base = '';
 770+ $path = $wgSharedUploadPath;
 771+ } else {
 772+ $base = $wgUploadBaseUrl;
 773+ $path = $wgUploadPath;
 774+ }
 775+ if ( Image::isHashed( $this->fromSharedDirectory ) ) {
 776+ $url = "{$base}{$path}/thumb" .
 777+ wfGetHashPath($this->name, $this->fromSharedDirectory)
 778+ . $this->name.'/'.$thumbName;
 779+ $url = wfUrlencode( $url );
 780+ } else {
 781+ $url = "{$base}{$path}/thumb/{$thumbName}";
 782+ }
 783+ return $url;
 784+ }
797785
798 - // Generate thumb.php URL if possible
799 - $script = false;
800 - $url = false;
801 -
 786+ function getTransformScript() {
 787+ global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath;
802788 if ( $this->fromSharedDirectory ) {
803 - if ( $wgSharedThumbnailScriptPath ) {
804 - $script = $wgSharedThumbnailScriptPath;
805 - }
 789+ $script = $wgSharedThumbnailScriptPath;
806790 } else {
807 - if ( $wgThumbnailScriptPath ) {
808 - $script = $wgThumbnailScriptPath;
809 - }
 791+ $script = $wgThumbnailScriptPath;
810792 }
811793 if ( $script ) {
812 - $url = $script . '?f=' . urlencode( $this->name ) . '&w=' . urlencode( $width );
813 - if( $this->mustRender() ) {
814 - $url.= '&r=1';
815 - }
 794+ return "$script?f=" . urlencode( $this->name );
816795 } else {
817 - $name = $this->thumbName( $width );
818 - if($this->fromSharedDirectory) {
819 - $base = '';
820 - $path = $wgSharedUploadPath;
821 - } else {
822 - $base = $wgUploadBaseUrl;
823 - $path = $wgUploadPath;
824 - }
825 - if ( Image::isHashed( $this->fromSharedDirectory ) ) {
826 - $url = "{$base}{$path}/{$subdir}" .
827 - wfGetHashPath($this->name, $this->fromSharedDirectory)
828 - . $this->name.'/'.$name;
829 - $url = wfUrlencode( $url );
830 - } else {
831 - $url = "{$base}{$path}/{$subdir}/{$name}";
832 - }
 796+ return false;
833797 }
834 - return array( $script !== false, $url );
835798 }
836799
837800 /**
838 - * Return the file name of a thumbnail of the specified width
 801+ * Get a ThumbnailImage which is the same size as the source
 802+ */
 803+ function getUnscaledThumb( $page = false ) {
 804+ if ( $page ) {
 805+ $params = array(
 806+ 'page' => $page,
 807+ 'width' => $this->getWidth( $page )
 808+ );
 809+ } else {
 810+ $params = array( 'width' => $this->getWidth() );
 811+ }
 812+ return $this->transform( $params );
 813+ }
 814+
 815+ /**
 816+ * Return the file name of a thumbnail with the specified parameters
839817 *
840 - * @param integer $width Width of the thumbnail image
841 - * @param boolean $shared Does the thumbnail come from the shared repository?
 818+ * @param array $params Handler-specific parameters
842819 * @private
843820 */
844 - function thumbName( $width ) {
845 - global $wgDjvuOutputExtension;
846 - $thumb = $width."px-".$this->name;
847 - if ( $this->page != 1 ) {
848 - $thumb = "page{$this->page}-$thumb";
 821+ function thumbName( $params ) {
 822+ $handler = $this->getHandler();
 823+ if ( !$handler ) {
 824+ return null;
849825 }
850 -
851 - if( $this->mustRender() ) {
852 - if( $this->canRender() ) {
853 - list( $ext, $mime ) = self::getThumbType( $this->extension, $this->mime );
854 - if ( $ext != $this->extension ) {
855 - $thumb .= ".$ext";
856 - }
857 - }
858 - else {
859 - #should we use iconThumb here to get a symbolic thumbnail?
860 - #or should we fail with an internal error?
861 - return NULL; //can't make bitmap
862 - }
 826+ list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
 827+ $thumbName = $handler->makeParamString( $params ) . '-' . $this->name;
 828+ if ( $thumbExt != $this->extension ) {
 829+ $thumbName .= ".$thumbExt";
863830 }
864 - return $thumb;
 831+ return $thumbName;
865832 }
866833
867834 /**
@@ -879,9 +846,13 @@
880847 * @param integer $height maximum height of the image (optional)
881848 * @public
882849 */
883 - function createThumb( $width, $height=-1 ) {
884 - $thumb = $this->getThumbnail( $width, $height );
885 - if( is_null( $thumb ) ) return '';
 850+ function createThumb( $width, $height = -1 ) {
 851+ $params = array( 'width' => $width );
 852+ if ( $height != -1 ) {
 853+ $params['height'] = $height;
 854+ }
 855+ $thumb = $this->transform( $params );
 856+ if( is_null( $thumb ) || $thumb->isError() ) return '';
886857 return $thumb->getUrl();
887858 }
888859
@@ -899,149 +870,89 @@
900871 *
901872 * @return ThumbnailImage or null on failure
902873 * @public
 874+ *
 875+ * @deprecated use transform()
903876 */
904877 function getThumbnail( $width, $height=-1, $render = true ) {
905 - wfProfileIn( __METHOD__ );
906 - if ($this->canRender()) {
907 - if ( $height > 0 ) {
908 - $this->load();
909 - if ( $width > $this->width * $height / $this->height ) {
910 - $width = wfFitBoxWidth( $this->width, $this->height, $height );
911 - }
912 - }
913 - if ( $render ) {
914 - $thumb = $this->renderThumb( $width );
915 - } else {
916 - // Don't render, just return the URL
917 - if ( $this->validateThumbParams( $width, $height ) ) {
918 - if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
919 - $url = $this->getURL();
920 - } else {
921 - list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $width );
922 - }
923 - $thumb = new ThumbnailImage( $url, $width, $height );
924 - } else {
925 - $thumb = null;
926 - }
927 - }
928 - } else {
929 - // not a bitmap or renderable image, don't try.
930 - $thumb = $this->iconThumb();
 878+ $params = array( 'width' => $width );
 879+ if ( $height != -1 ) {
 880+ $params['height'] = $height;
931881 }
932 - wfProfileOut( __METHOD__ );
933 - return $thumb;
 882+ $flags = $render ? self::RENDER_NOW : 0;
 883+ return $this->transform( $params, $flags );
934884 }
935 -
 885+
936886 /**
937 - * @return ThumbnailImage
 887+ * Transform a media file
 888+ *
 889+ * @param array $params An associative array of handler-specific parameters. Typical
 890+ * keys are width, height and page.
 891+ * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
 892+ * @return MediaTransformOutput
938893 */
939 - function iconThumb() {
940 - global $wgStylePath, $wgStyleDirectory;
 894+ function transform( $params, $flags = 0 ) {
 895+ global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors;
941896
942 - $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
943 - foreach( $try as $icon ) {
944 - $path = '/common/images/icons/' . $icon;
945 - $filepath = $wgStyleDirectory . $path;
946 - if( file_exists( $filepath ) ) {
947 - return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
 897+ wfProfileIn( __METHOD__ );
 898+ do {
 899+ $handler = $this->getHandler();
 900+ if ( !$handler || !$handler->canRender() ) {
 901+ // not a bitmap or renderable image, don't try.
 902+ $thumb = $this->iconThumb();
 903+ break;
948904 }
949 - }
950 - return null;
951 - }
952905
953 - /**
954 - * Validate thumbnail parameters and fill in the correct height
955 - *
956 - * @param integer &$width Specified width (input/output)
957 - * @param integer &$height Height (output only)
958 - * @return false to indicate that an error should be returned to the user.
959 - */
960 - function validateThumbParams( &$width, &$height ) {
961 - global $wgSVGMaxSize, $wgMaxImageArea;
 906+ $script = $this->getTransformScript();
 907+ if ( $script && !($flags & self::RENDER_NOW) ) {
 908+ // Use a script to transform on client request
 909+ $thumb = $handler->getScriptedTransform( $this, $script, $params );
 910+ break;
 911+ }
962912
963 - $this->load();
 913+ $handler->normaliseParams( $this, $params );
 914+ list( $thumbExt, $thumbMime ) = self::getThumbType( $this->extension, $this->mime );
 915+ $thumbName = $this->thumbName( $params );
 916+ $thumbPath = wfImageThumbDir( $this->name, $this->fromSharedDirectory ) . "/$thumbName";
 917+ $thumbUrl = $this->thumbUrl( $thumbName );
964918
965 - if ( ! $this->exists() )
966 - {
967 - # If there is no image, there will be no thumbnail
968 - return false;
969 - }
 919+ $this->migrateThumbFile( $thumbName );
970920
971 - $width = intval( $width );
 921+ if ( file_exists( $thumbPath ) ) {
 922+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 923+ break;
 924+ }
972925
973 - # Sanity check $width
974 - if( $width <= 0 || $this->width <= 0) {
975 - # BZZZT
976 - return false;
977 - }
 926+ if ( !$wgGenerateThumbnailOnParse && !($flags & self::RENDER_NOW ) ) {
 927+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 928+ break;
 929+ }
 930+ $thumb = $handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
978931
979 - # Don't thumbnail an image so big that it will fill hard drives and send servers into swap
980 - # JPEG has the handy property of allowing thumbnailing without full decompression, so we make
981 - # an exception for it.
982 - if ( $this->getMediaType() == MEDIATYPE_BITMAP &&
983 - $this->getMimeType() !== 'image/jpeg' &&
984 - $this->width * $this->height > $wgMaxImageArea )
985 - {
986 - return false;
987 - }
 932+ // Ignore errors if requested
 933+ if ( !$thumb ) {
 934+ $thumb = null;
 935+ } elseif ( $thumb->isError() ) {
 936+ $this->lastError = $thumb->toText();
 937+ if ( $wgIgnoreImageErrors ) {
 938+ $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
 939+ }
 940+ }
 941+
 942+ if ( $wgUseSquid ) {
 943+ wfPurgeSquidServers( array( $thumbUrl ) );
 944+ }
 945+ } while (false);
988946
989 - # Don't make an image bigger than the source, or wgMaxSVGSize for SVGs
990 - if ( $this->mustRender() ) {
991 - $width = min( $width, $wgSVGMaxSize );
992 - } elseif ( $width > $this->width - 1 ) {
993 - $width = $this->width;
994 - $height = $this->height;
995 - return true;
996 - }
997 -
998 - $height = self::scaleHeight( $this->width, $this->height, $width );
999 - return true;
 947+ wfProfileOut( __METHOD__ );
 948+ return $thumb;
1000949 }
1001950
1002951 /**
1003 - * Create a thumbnail of the image having the specified width.
1004 - * The thumbnail will not be created if the width is larger than the
1005 - * image's width. Let the browser do the scaling in this case.
1006 - * The thumbnail is stored on disk and is only computed if the thumbnail
1007 - * file does not exist OR if it is older than the image.
1008 - * Returns an object which can return the pathname, URL, and physical
1009 - * pixel size of the thumbnail -- or null on failure.
1010 - *
1011 - * @return ThumbnailImage or null on failure
1012 - * @private
 952+ * Fix thumbnail files from 1.4 or before, with extreme prejudice
1013953 */
1014 - function renderThumb( $width, $useScript = true ) {
1015 - global $wgUseSquid, $wgThumbnailEpoch;
1016 -
1017 - wfProfileIn( __METHOD__ );
1018 -
1019 - $this->load();
1020 - $height = -1;
1021 - if ( !$this->validateThumbParams( $width, $height ) ) {
1022 - # Validation error
1023 - wfProfileOut( __METHOD__ );
1024 - return null;
1025 - }
1026 -
1027 - if ( !$this->mustRender() && $width == $this->width && $height == $this->height ) {
1028 - # validateThumbParams (or the user) wants us to return the unscaled image
1029 - $thumb = new ThumbnailImage( $this->getURL(), $width, $height );
1030 - wfProfileOut( __METHOD__ );
1031 - return $thumb;
1032 - }
1033 -
1034 - list( $isScriptUrl, $url ) = $this->thumbUrl( $width );
1035 - if ( $isScriptUrl && $useScript ) {
1036 - // Use thumb.php to render the image
1037 - $thumb = new ThumbnailImage( $url, $width, $height );
1038 - wfProfileOut( __METHOD__ );
1039 - return $thumb;
1040 - }
1041 -
1042 - $thumbName = $this->thumbName( $width, $this->fromSharedDirectory );
 954+ function migrateThumbFile( $thumbName ) {
1043955 $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory );
1044 - $thumbPath = $thumbDir.'/'.$thumbName;
1045 -
 956+ $thumbPath = "$thumbDir/$thumbName";
1046957 if ( is_dir( $thumbPath ) ) {
1047958 // Directory where file should be
1048959 // This happened occasionally due to broken migration code in 1.5
@@ -1054,247 +965,50 @@
1055966 break;
1056967 }
1057968 }
1058 - // Code below will ask if it exists, and the answer is now no
 969+ // Doesn't exist anymore
1059970 clearstatcache();
1060971 }
1061 -
1062 - $done = true;
1063 - if ( !file_exists( $thumbPath ) ||
1064 - filemtime( $thumbPath ) < wfTimestamp( TS_UNIX, $wgThumbnailEpoch ) )
1065 - {
1066 - // Create the directory if it doesn't exist
1067 - if ( is_file( $thumbDir ) ) {
1068 - // File where thumb directory should be, destroy if possible
1069 - @unlink( $thumbDir );
1070 - }
1071 - wfMkdirParents( $thumbDir );
1072 -
1073 - $oldThumbPath = wfDeprecatedThumbDir( $thumbName, 'thumb', $this->fromSharedDirectory ).
1074 - '/'.$thumbName;
1075 - $done = false;
1076 -
1077 - // Migration from old directory structure
1078 - if ( is_file( $oldThumbPath ) ) {
1079 - if ( filemtime($oldThumbPath) >= filemtime($this->imagePath) ) {
1080 - if ( file_exists( $thumbPath ) ) {
1081 - if ( !is_dir( $thumbPath ) ) {
1082 - // Old image in the way of rename
1083 - unlink( $thumbPath );
1084 - } else {
1085 - // This should have been dealt with already
1086 - throw new MWException( "Directory where image should be: $thumbPath" );
1087 - }
1088 - }
1089 - // Rename the old image into the new location
1090 - rename( $oldThumbPath, $thumbPath );
1091 - $done = true;
1092 - } else {
1093 - unlink( $oldThumbPath );
1094 - }
1095 - }
1096 - if ( !$done ) {
1097 - $this->lastError = self::reallyRenderThumb( $this->imagePath, $thumbPath, $this->mime,
1098 - $width, $height, $this->page );
1099 - if ( $this->lastError === true ) {
1100 - $done = true;
1101 - } elseif( $GLOBALS['wgIgnoreImageErrors'] ) {
1102 - // Log the error but output anyway.
1103 - // With luck it's a transitory error...
1104 - $done = true;
1105 - }
1106 -
1107 - # Purge squid
1108 - # This has to be done after the image is updated and present for all machines on NFS,
1109 - # or else the old version might be stored into the squid again
1110 - if ( $wgUseSquid ) {
1111 - $urlArr = array( $url );
1112 - wfPurgeSquidServers($urlArr);
1113 - }
1114 - }
 972+ if ( is_file( $thumbDir ) ) {
 973+ // File where directory should be
 974+ unlink( $thumbDir );
 975+ // Doesn't exist anymore
 976+ clearstatcache();
1115977 }
 978+ }
1116979
1117 - if ( $done ) {
1118 - $thumb = new ThumbnailImage( $url, $width, $height, $thumbPath );
1119 - } else {
1120 - $thumb = null;
1121 - }
1122 - wfProfileOut( __METHOD__ );
1123 - return $thumb;
1124 - } // END OF function renderThumb
 980+ /**
 981+ * Get a MediaHandler instance for this image
 982+ */
 983+ function getHandler() {
 984+ return MediaHandler::getHandler( $this->getMimeType() );
 985+ }
1125986
1126987 /**
1127 - * Really render a thumbnail
1128 - * Call this only for images for which canRender() returns true.
1129 - *
1130 - * @param string $source Source filename
1131 - * @param string $destination Destination filename
1132 - * @param string $mime MIME type of source
1133 - * @param integer $width Destination width in pixels
1134 - * @param integer $height Destination height in pixels
1135 - * @param integer $page Which page of a multi-page document to display. Ignored
1136 - * for source MIME types which do not support multiple pages.
 988+ * Get a ThumbnailImage representing a file type icon
 989+ * @return ThumbnailImage
1137990 */
1138 - static function reallyRenderThumb( $source, $destination, $mime, $width, $height, $page = false ) {
1139 - global $wgSVGConverters, $wgSVGConverter;
1140 - global $wgUseImageMagick, $wgImageMagickConvertCommand;
1141 - global $wgCustomConvertCommand;
1142 - global $wgDjvuRenderer, $wgDjvuPostProcessor;
 991+ function iconThumb() {
 992+ global $wgStylePath, $wgStyleDirectory;
1143993
1144 - $err = false;
1145 - $cmd = "";
1146 - $retval = 0;
1147 -
1148 - if( $mime == "image/svg" || $mime == 'image/svg+xml' ) {
1149 - #Right now we have only SVG
1150 -
1151 - global $wgSVGConverters, $wgSVGConverter;
1152 - if( isset( $wgSVGConverters[$wgSVGConverter] ) ) {
1153 - global $wgSVGConverterPath;
1154 - $cmd = str_replace(
1155 - array( '$path/', '$width', '$height', '$input', '$output' ),
1156 - array( $wgSVGConverterPath ? wfEscapeShellArg( "$wgSVGConverterPath/" ) : "",
1157 - intval( $width ),
1158 - intval( $height ),
1159 - wfEscapeShellArg( $source ),
1160 - wfEscapeShellArg( $destination ) ),
1161 - $wgSVGConverters[$wgSVGConverter] ) . " 2>&1";
1162 - wfProfileIn( 'rsvg' );
1163 - wfDebug( "reallyRenderThumb SVG: $cmd\n" );
1164 - $err = wfShellExec( $cmd, $retval );
1165 - wfProfileOut( 'rsvg' );
 994+ $try = array( 'fileicon-' . $this->extension . '.png', 'fileicon.png' );
 995+ foreach( $try as $icon ) {
 996+ $path = '/common/images/icons/' . $icon;
 997+ $filepath = $wgStyleDirectory . $path;
 998+ if( file_exists( $filepath ) ) {
 999+ return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
11661000 }
1167 - } elseif ( $mime === "image/vnd.djvu" && $wgDjvuRenderer ) {
1168 - // DJVU image
1169 - // The file contains several images. First, extract the
1170 - // page in hi-res, if it doesn't yet exist. Then, thumbnail
1171 - // it.
1172 -
1173 - $cmd = wfEscapeShellArg( $wgDjvuRenderer ) . " -format=ppm -page={$page} -size=${width}x${height} " .
1174 - wfEscapeShellArg( $source );
1175 - if ( $wgDjvuPostProcessor ) {
1176 - $cmd .= " | {$wgDjvuPostProcessor}";
1177 - }
1178 - $cmd .= ' > ' . wfEscapeShellArg($destination);
1179 - wfProfileIn( 'ddjvu' );
1180 - wfDebug( "reallyRenderThumb DJVU: $cmd\n" );
1181 - $err = wfShellExec( $cmd, $retval );
1182 - wfProfileOut( 'ddjvu' );
1183 -
1184 - } elseif ( $wgUseImageMagick ) {
1185 - # use ImageMagick
1186 -
1187 - if ( $mime == 'image/jpeg' ) {
1188 - $quality = "-quality 80"; // 80%
1189 - } elseif ( $mime == 'image/png' ) {
1190 - $quality = "-quality 95"; // zlib 9, adaptive filtering
1191 - } else {
1192 - $quality = ''; // default
1193 - }
1194 -
1195 - # Specify white background color, will be used for transparent images
1196 - # in Internet Explorer/Windows instead of default black.
1197 -
1198 - # Note, we specify "-size {$width}" and NOT "-size {$width}x{$height}".
1199 - # It seems that ImageMagick has a bug wherein it produces thumbnails of
1200 - # the wrong size in the second case.
1201 -
1202 - $cmd = wfEscapeShellArg($wgImageMagickConvertCommand) .
1203 - " {$quality} -background white -size {$width} ".
1204 - wfEscapeShellArg($source) .
1205 - // Coalesce is needed to scale animated GIFs properly (bug 1017).
1206 - ' -coalesce ' .
1207 - // For the -resize option a "!" is needed to force exact size,
1208 - // or ImageMagick may decide your ratio is wrong and slice off
1209 - // a pixel.
1210 - " -thumbnail " . wfEscapeShellArg( "{$width}x{$height}!" ) .
1211 - " -depth 8 " .
1212 - wfEscapeShellArg($destination) . " 2>&1";
1213 - wfDebug("reallyRenderThumb: running ImageMagick: $cmd\n");
1214 - wfProfileIn( 'convert' );
1215 - $err = wfShellExec( $cmd, $retval );
1216 - wfProfileOut( 'convert' );
1217 - } elseif( $wgCustomConvertCommand ) {
1218 - # Use a custom convert command
1219 - # Variables: %s %d %w %h
1220 - $src = wfEscapeShellArg( $source );
1221 - $dst = wfEscapeShellArg( $destination );
1222 - $cmd = $wgCustomConvertCommand;
1223 - $cmd = str_replace( '%s', $src, str_replace( '%d', $dst, $cmd ) ); # Filenames
1224 - $cmd = str_replace( '%h', $height, str_replace( '%w', $width, $cmd ) ); # Size
1225 - wfDebug( "reallyRenderThumb: Running custom convert command $cmd\n" );
1226 - wfProfileIn( 'convert' );
1227 - $err = wfShellExec( $cmd, $retval );
1228 - wfProfileOut( 'convert' );
1229 - } else {
1230 - # Use PHP's builtin GD library functions.
1231 - #
1232 - # First find out what kind of file this is, and select the correct
1233 - # input routine for this.
1234 -
1235 - $typemap = array(
1236 - 'image/gif' => array( 'imagecreatefromgif', 'palette', 'imagegif' ),
1237 - 'image/jpeg' => array( 'imagecreatefromjpeg', 'truecolor', array( __CLASS__, 'imageJpegWrapper' ) ),
1238 - 'image/png' => array( 'imagecreatefrompng', 'bits', 'imagepng' ),
1239 - 'image/vnd.wap.wmbp' => array( 'imagecreatefromwbmp', 'palette', 'imagewbmp' ),
1240 - 'image/xbm' => array( 'imagecreatefromxbm', 'palette', 'imagexbm' ),
1241 - );
1242 - if( !isset( $typemap[$mime] ) ) {
1243 - $err = 'Image type not supported';
1244 - wfDebug( "$err\n" );
1245 - return $err;
1246 - }
1247 - list( $loader, $colorStyle, $saveType ) = $typemap[$mime];
1248 -
1249 - if( !function_exists( $loader ) ) {
1250 - $err = "Incomplete GD library configuration: missing function $loader";
1251 - wfDebug( "$err\n" );
1252 - return $err;
1253 - }
1254 -
1255 - $src_image = call_user_func( $loader, $source );
1256 - $dst_image = imagecreatetruecolor( $width, $height );
1257 - imagecopyresampled( $dst_image, $src_image,
1258 - 0,0,0,0,
1259 - $width, $height, imagesx( $src_image ), imagesy( $src_image ) );
1260 - call_user_func( $saveType, $dst_image, $destination );
1261 - imagedestroy( $dst_image );
1262 - imagedestroy( $src_image );
12631001 }
1264 -
1265 - #
1266 - # Check for zero-sized thumbnails. Those can be generated when
1267 - # no disk space is available or some other error occurs
1268 - #
1269 - $removed = false;
1270 - if( file_exists( $destination ) ) {
1271 - $thumbstat = stat( $destination );
1272 - if( $thumbstat['size'] == 0 || $retval != 0 ) {
1273 - wfDebugLog( 'thumbnail',
1274 - sprintf( 'Removing bad %d-byte thumbnail "%s"',
1275 - $thumbstat['size'], $destination ) );
1276 - unlink( $destination );
1277 - $removed = true;
1278 - }
1279 - }
1280 - if ( $retval != 0 || $removed ) {
1281 - wfDebugLog( 'thumbnail',
1282 - sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"',
1283 - wfHostname(), $retval, trim($err), $cmd ) );
1284 - return wfMsg( 'thumbnail_error', $err );
1285 - } else {
1286 - return true;
1287 - }
 1002+ return null;
12881003 }
12891004
 1005+ /**
 1006+ * Get last thumbnailing error.
 1007+ * Largely obsolete.
 1008+ */
12901009 function getLastError() {
12911010 return $this->lastError;
12921011 }
12931012
1294 - static function imageJpegWrapper( $dst_image, $thumbPath ) {
1295 - imageinterlace( $dst_image );
1296 - imagejpeg( $dst_image, $thumbPath, 95 );
1297 - }
1298 -
12991013 /**
13001014 * Get all thumbnail names previously generated for this image
13011015 */
@@ -1328,7 +1042,7 @@
13291043 */
13301044 function purgeMetadataCache() {
13311045 clearstatcache();
1332 - $this->loadFromFile();
 1046+ $this->upgradeRow();
13331047 $this->saveToCache();
13341048 }
13351049
@@ -1348,7 +1062,7 @@
13491063 foreach ( $files as $file ) {
13501064 $m = array();
13511065 if ( preg_match( '/^(\d+)px/', $file, $m ) ) {
1352 - list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] );
 1066+ $url = $this->thumbUrl( $m[1] );
13531067 $urls[] = $url;
13541068 @unlink( "$dir/$file" );
13551069 }
@@ -1391,6 +1105,9 @@
13921106 $update->doUpdate();
13931107 }
13941108
 1109+ /**
 1110+ * Check the image table schema on the given connection for subtle problems
 1111+ */
13951112 function checkDBSchema(&$db) {
13961113 static $checkDone = false;
13971114 global $wgCheckDBSchema;
@@ -1711,76 +1428,24 @@
17121429 return $retVal;
17131430 }
17141431
1715 - /**
1716 - * Retrive Exif data from the file and prune unrecognized tags
1717 - * and/or tags with invalid contents
1718 - *
1719 - * @param $filename
1720 - * @return array
1721 - */
1722 - private function retrieveExifData( $filename ) {
1723 - global $wgShowEXIF;
1724 -
1725 - /*
1726 - if ( $this->getMimeType() !== "image/jpeg" )
1727 - return array();
1728 - */
1729 -
1730 - if( $wgShowEXIF && file_exists( $filename ) ) {
1731 - $exif = new Exif( $filename );
1732 - return $exif->getFilteredData();
1733 - }
1734 -
1735 - return array();
1736 - }
1737 -
17381432 function getExifData() {
17391433 global $wgRequest;
1740 - if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' )
 1434+ $handler = $this->getHandler();
 1435+ if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) {
17411436 return array();
1742 -
1743 - $purge = $wgRequest->getVal( 'action' ) == 'purge';
1744 - $ret = unserialize( $this->metadata );
1745 -
1746 - $oldver = isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) ? $ret['MEDIAWIKI_EXIF_VERSION'] : 0;
1747 - $newver = Exif::version();
1748 -
1749 - if ( !count( $ret ) || $purge || $oldver != $newver ) {
1750 - $this->purgeMetadataCache();
1751 - $this->updateExifData( $newver );
17521437 }
1753 - if ( isset( $ret['MEDIAWIKI_EXIF_VERSION'] ) )
1754 - unset( $ret['MEDIAWIKI_EXIF_VERSION'] );
1755 - $format = new FormatExif( $ret );
 1438+ if ( !$this->metadata ) {
 1439+ return array();
 1440+ }
 1441+ $exif = unserialize( $this->metadata );
 1442+ if ( !$exif ) {
 1443+ return array();
 1444+ }
 1445+ $format = new FormatExif( $exif );
17561446
17571447 return $format->getFormattedData();
17581448 }
17591449
1760 - function updateExifData( $version ) {
1761 - if ( $this->getImagePath() === false ) # Not a local image
1762 - return;
1763 -
1764 - # Get EXIF data from image
1765 - $exif = $this->retrieveExifData( $this->imagePath );
1766 - if ( count( $exif ) ) {
1767 - $exif['MEDIAWIKI_EXIF_VERSION'] = $version;
1768 - $this->metadata = serialize( $exif );
1769 - } else {
1770 - $this->metadata = '0';
1771 - }
1772 -
1773 - # Update EXIF data in database
1774 - $dbw = wfGetDB( DB_MASTER );
1775 -
1776 - $this->checkDBSchema($dbw);
1777 -
1778 - $dbw->update( 'image',
1779 - array( 'img_metadata' => $this->metadata ),
1780 - array( 'img_name' => $this->name ),
1781 - __METHOD__
1782 - );
1783 - }
1784 -
17851450 /**
17861451 * Returns true if the image does not come from the shared
17871452 * image repository.
@@ -2145,12 +1810,17 @@
21461811 // an archived file revision.
21471812 if( is_null( $row->fa_metadata ) ) {
21481813 $tempFile = $store->filePath( $row->fa_storage_key );
2149 - $metadata = serialize( $this->retrieveExifData( $tempFile ) );
21501814
21511815 $magic = MimeMagic::singleton();
21521816 $mime = $magic->guessMimeType( $tempFile, true );
21531817 $media_type = $magic->getMediaType( $tempFile, $mime );
21541818 list( $major_mime, $minor_mime ) = self::splitMime( $mime );
 1819+ $handler = MediaHandler::getHandler( $mime );
 1820+ if ( $handler ) {
 1821+ $metadata = $handler->getMetadata( $image, $tempFile );
 1822+ } else {
 1823+ $metadata = '';
 1824+ }
21551825 } else {
21561826 $metadata = $row->fa_metadata;
21571827 $major_mime = $row->fa_major_mime;
@@ -2258,71 +1928,14 @@
22591929 }
22601930
22611931 /**
2262 - * Select a page from a multipage document. Determines the page used for
2263 - * rendering thumbnails.
2264 - *
2265 - * @param $page Integer: page number, starting with 1
2266 - */
2267 - function selectPage( $page ) {
2268 - if( $this->initializeMultiPageXML() ) {
2269 - wfDebug( __METHOD__." selecting page $page \n" );
2270 - $this->page = $page;
2271 - $o = $this->multiPageXML->BODY[0]->OBJECT[$page-1];
2272 - $this->height = intval( $o['height'] );
2273 - $this->width = intval( $o['width'] );
2274 - } else {
2275 - wfDebug( __METHOD__." selectPage($page) for bogus multipage xml on '$this->name'\n" );
2276 - return;
2277 - }
2278 - }
2279 -
2280 - /**
2281 - * Lazy-initialize multipage XML metadata for DjVu files.
2282 - * @return bool true if $this->multiPageXML is set up and ready;
2283 - * false if corrupt or otherwise failing
2284 - */
2285 - function initializeMultiPageXML() {
2286 - $this->load();
2287 - if ( isset( $this->multiPageXML ) ) {
2288 - return true;
2289 - }
2290 -
2291 - #
2292 - # Check for files uploaded prior to DJVU support activation,
2293 - # or damaged.
2294 - #
2295 - if( empty( $this->metadata ) || $this->metadata == serialize( array() ) ) {
2296 - $deja = new DjVuImage( $this->imagePath );
2297 - $this->metadata = $deja->retrieveMetaData();
2298 - $this->purgeMetadataCache();
2299 -
2300 - # Update metadata in the database
2301 - $dbw = wfGetDB( DB_MASTER );
2302 - $dbw->update( 'image',
2303 - array( 'img_metadata' => $this->metadata ),
2304 - array( 'img_name' => $this->name ),
2305 - __METHOD__
2306 - );
2307 - }
2308 - wfSuppressWarnings();
2309 - try {
2310 - $this->multiPageXML = new SimpleXMLElement( $this->metadata );
2311 - } catch( Exception $e ) {
2312 - wfDebug( "Bogus multipage XML metadata on '$this->name'\n" );
2313 - $this->multiPageXML = null;
2314 - }
2315 - wfRestoreWarnings();
2316 - return isset( $this->multiPageXML );
2317 - }
2318 -
2319 - /**
23201932 * Returns 'true' if this image is a multipage document, e.g. a DJVU
23211933 * document.
23221934 *
23231935 * @return Bool
23241936 */
23251937 function isMultipage() {
2326 - return ( $this->mime == 'image/vnd.djvu' );
 1938+ $handler = $this->getHandler();
 1939+ return $handler && $handler->isMultiPage();
23271940 }
23281941
23291942 /**
@@ -2330,13 +1943,10 @@
23311944 * documents which aren't multipage documents
23321945 */
23331946 function pageCount() {
2334 - if ( ! $this->isMultipage() ) {
2335 - return null;
2336 - }
2337 - if( $this->initializeMultiPageXML() ) {
2338 - return count( $this->multiPageXML->xpath( '//OBJECT' ) );
 1947+ $handler = $this->getHandler();
 1948+ if ( $handler && $handler->isMultiPage() ) {
 1949+ return $handler->pageCount( $this );
23391950 } else {
2340 - wfDebug( "Requested pageCount() for bogus multi-page metadata for '$this->name'\n" );
23411951 return null;
23421952 }
23431953 }
@@ -2358,7 +1968,11 @@
23591969 */
23601970 static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
23611971 // Exact integer multiply followed by division
2362 - return round( $srcHeight * $dstWidth / $srcWidth );
 1972+ if ( $srcWidth == 0 ) {
 1973+ return 0;
 1974+ } else {
 1975+ return round( $srcHeight * $dstWidth / $srcWidth );
 1976+ }
23631977 }
23641978
23651979 /**
@@ -2366,28 +1980,11 @@
23671981 * can't be determined.
23681982 *
23691983 * @param string $fileName The filename
2370 - * @param string $mimeType The MIME type of the file
2371 - * @param object $deja Filled with a DjVu object if the mime type is image/vnd.djvu
23721984 * @return array
23731985 */
2374 - static function getImageSize( $fileName, $mimeType, &$deja ) {
2375 - $magic =& MimeMagic::singleton();
2376 - if( $mimeType == 'image/svg' || $mimeType == 'image/svg+xml' ) {
2377 - $gis = wfGetSVGsize( $fileName );
2378 - } elseif( $mimeType == 'image/vnd.djvu' ) {
2379 - wfSuppressWarnings();
2380 - $deja = new DjVuImage( $fileName );
2381 - $gis = $deja->getImageSize();
2382 - wfRestoreWarnings();
2383 - } elseif ( !$magic->isPHPImageType( $mimeType ) ) {
2384 - # Don't try to get the width and height of sound and video files, that's bad for performance
2385 - $gis = false;
2386 - } else {
2387 - wfSuppressWarnings();
2388 - $gis = getimagesize( $fileName );
2389 - wfRestoreWarnings();
2390 - }
2391 - return $gis;
 1986+ function getImageSize( $fileName ) {
 1987+ $handler = $this->getHandler();
 1988+ return $handler->getImageSize( $this, $fileName );
23921989 }
23931990
23941991 /**
@@ -2395,22 +1992,14 @@
23961993 * @return array thumbnail extension and MIME type
23971994 */
23981995 static function getThumbType( $ext, $mime ) {
2399 - switch ( $mime ) {
2400 - case 'image/svg':
2401 - case 'image/svg+xml':
2402 - $ext = 'png';
2403 - $mime = 'image/png';
2404 - break;
2405 - case 'image/vnd.djvu':
2406 - $ext = $GLOBALS['wgDjvuOutputExtension'];
2407 - $magic = MimeMagic::singleton();
2408 - $mime = $magic->guessTypesForExtension( $ext );
2409 - break;
 1996+ $handler = MediaHandler::getHandler( $mime );
 1997+ if ( $handler ) {
 1998+ return $handler->getThumbType( $ext, $mime );
 1999+ } else {
 2000+ return array( $ext, $mime );
24102001 }
2411 - return array( $ext, $mime );
24122002 }
24132003
2414 -
24152004 } //class
24162005
24172006 class ArchivedFile
@@ -2520,60 +2109,6 @@
25212110 }
25222111
25232112 /**
2524 - * Wrapper class for thumbnail images
2525 - */
2526 -class ThumbnailImage {
2527 - /**
2528 - * @param string $path Filesystem path to the thumb
2529 - * @param string $url URL path to the thumb
2530 - * @private
2531 - */
2532 - function ThumbnailImage( $url, $width, $height, $path = false ) {
2533 - $this->url = $url;
2534 - $this->width = round( $width );
2535 - $this->height = round( $height );
2536 - # These should be integers when they get here.
2537 - # If not, there's a bug somewhere. But let's at
2538 - # least produce valid HTML code regardless.
2539 - $this->path = $path;
2540 - }
2541 -
2542 - /**
2543 - * @return string The thumbnail URL
2544 - */
2545 - function getUrl() {
2546 - return $this->url;
2547 - }
2548 -
2549 - /**
2550 - * Return HTML <img ... /> tag for the thumbnail, will include
2551 - * width and height attributes and a blank alt text (as required).
2552 - *
2553 - * You can set or override additional attributes by passing an
2554 - * associative array of name => data pairs. The data will be escaped
2555 - * for HTML output, so should be in plaintext.
2556 - *
2557 - * @param array $attribs
2558 - * @return string
2559 - * @public
2560 - */
2561 - function toHtml( $attribs = array() ) {
2562 - $attribs['src'] = $this->url;
2563 - $attribs['width'] = $this->width;
2564 - $attribs['height'] = $this->height;
2565 - if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
2566 -
2567 - $html = '<img ';
2568 - foreach( $attribs as $name => $data ) {
2569 - $html .= $name . '="' . htmlspecialchars( $data ) . '" ';
2570 - }
2571 - $html .= '/>';
2572 - return $html;
2573 - }
2574 -
2575 -}
2576 -
2577 -/**
25782113 * Aliases for backwards compatibility with 1.6
25792114 */
25802115 define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE );
Index: trunk/phase3/includes/DefaultSettings.php
@@ -1443,9 +1443,19 @@
14441444 # Images settings
14451445 #
14461446
1447 -/** dynamic server side image resizing ("Thumbnails") */
1448 -$wgUseImageResize = false;
 1447+/**
 1448+ * Plugins for media file type handling.
 1449+ * Each entry in the array maps a MIME type to a class name
 1450+ */
 1451+$wgMediaHandlers = array(
 1452+ 'image/jpeg' => 'BitmapHandler',
 1453+ 'image/png' => 'BitmapHandler',
 1454+ 'image/gif' => 'BitmapHandler',
 1455+ 'image/svg+xml' => 'SvgHandler',
 1456+ 'image/vnd.djvu' => 'DjVuHandler',
 1457+);
14491458
 1459+
14501460 /**
14511461 * Resizing can be done using PHP's internal image libraries or using
14521462 * ImageMagick or another third-party converter, e.g. GraphicMagick.
@@ -1458,6 +1468,12 @@
14591469 /** The convert command shipped with ImageMagick */
14601470 $wgImageMagickConvertCommand = '/usr/bin/convert';
14611471
 1472+/** Sharpening parameter to ImageMagick */
 1473+$wgSharpenParameter = '0x0.4';
 1474+
 1475+/** Reduction in linear dimensions below which sharpening will be enabled */
 1476+$wgSharpenReductionThreshold = 0.85;
 1477+
14621478 /**
14631479 * Use another resizing converter, e.g. GraphicMagick
14641480 * %s will be replaced with the source path, %d with the destination
@@ -1523,6 +1539,10 @@
15241540 */
15251541 $wgGenerateThumbnailOnParse = true;
15261542
 1543+/** Obsolete, always true, kept for compatibility with extensions */
 1544+$wgUseImageResize = true;
 1545+
 1546+
15271547 /** Set $wgCommandLineMode if it's not set already, to avoid notices */
15281548 if( !isset( $wgCommandLineMode ) ) {
15291549 $wgCommandLineMode = false;
@@ -2277,7 +2297,7 @@
22782298 MEDIATYPE_BITMAP, //all bitmap formats
22792299 MEDIATYPE_AUDIO, //all audio formats
22802300 MEDIATYPE_VIDEO, //all plain video formats
2281 - "image/svg", //svg (only needed if inline rendering of svg is not supported)
 2301+ "image/svg+xml", //svg (only needed if inline rendering of svg is not supported)
22822302 "application/pdf", //PDF files
22832303 #"application/x-shockwave-flash", //flash/shockwave movie
22842304 );
@@ -2380,7 +2400,7 @@
23812401 * MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't
23822402 * perform basic stuff like MIME detection and which are vulnerable to further idiots uploading
23832403 * crap files as images. When this directive is on, <title> will be allowed in files with
2384 - * an "image/svg" MIME type. You should leave this disabled if your web server is misconfigured
 2404+ * an "image/svg+xml" MIME type. You should leave this disabled if your web server is misconfigured
23852405 * and doesn't send appropriate MIME types for SVG images.
23862406 */
23872407 $wgAllowTitlesInSVG = false;
@@ -2406,20 +2426,33 @@
24072427
24082428 /**
24092429 * DJVU settings
2410 - * Path of the djvutoxml executable
 2430+ * Path of the djvudump executable
24112431 * Enable this and $wgDjvuRenderer to enable djvu rendering
24122432 */
2413 -# $wgDjvuToXML = 'djvutoxml';
2414 -$wgDjvuToXML = null;
 2433+# $wgDjvuDump = 'djvudump';
 2434+$wgDjvuDump = null;
24152435
24162436 /**
24172437 * Path of the ddjvu DJVU renderer
2418 - * Enable this and $wgDjvuToXML to enable djvu rendering
 2438+ * Enable this and $wgDjvuDump to enable djvu rendering
24192439 */
24202440 # $wgDjvuRenderer = 'ddjvu';
24212441 $wgDjvuRenderer = null;
24222442
24232443 /**
 2444+ * Path of the djvutoxml executable
 2445+ * This works like djvudump except much, much slower as of version 3.5.
 2446+ *
 2447+ * For now I recommend you use djvudump instead. The djvuxml output is
 2448+ * probably more stable, so we'll switch back to it as soon as they fix
 2449+ * the efficiency problem.
 2450+ * http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
 2451+ */
 2452+# $wgDjvuToXML = 'djvutoxml';
 2453+$wgDjvuToXML = null;
 2454+
 2455+
 2456+/**
24242457 * Shell command for the DJVU post processor
24252458 * Default: pnmtopng, since ddjvu generates ppm output
24262459 * Set this to false to output the ppm file directly.
Index: trunk/phase3/includes/mime.info
@@ -19,7 +19,7 @@
2020 image/x-bmp image/bmp application/x-bmp application/bmp [BITMAP]
2121 image/x-photoshop image/psd image/x-psd image/photoshop [BITMAP]
2222
23 -image/svg image/svg+xml application/svg+xml application/svg [DRAWING]
 23+image/svg+xml application/svg+xml application/svg image/svg [DRAWING]
2424 application/postscript [DRAWING]
2525 application/x-latex [DRAWING]
2626 application/x-tex [DRAWING]
Index: trunk/phase3/includes/MediaTransformOutput.php
@@ -0,0 +1,158 @@
 2+<?php
 3+
 4+/**
 5+ * Base class for the output of MediaHandler::doTransform() and Image::transform().
 6+ */
 7+abstract class MediaTransformOutput {
 8+ /**
 9+ * Get the width of the output box
 10+ */
 11+ function getWidth() {
 12+ return $this->width;
 13+ }
 14+
 15+ /**
 16+ * Get the height of the output box
 17+ */
 18+ function getHeight() {
 19+ return $this->height;
 20+ }
 21+
 22+ /**
 23+ * @return string The thumbnail URL
 24+ */
 25+ function getUrl() {
 26+ return $this->url;
 27+ }
 28+
 29+ /**
 30+ * @return string Destination file path (local filesystem)
 31+ */
 32+ function getPath() {
 33+ return $this->path;
 34+ }
 35+
 36+ /**
 37+ * Fetch HTML for this transform output
 38+ * @param array $attribs Advisory associative array of HTML attributes supplied
 39+ * by the linker. These can be incorporated into the output in any way.
 40+ * @param array $linkAttribs Attributes of a suggested enclosing <a> tag.
 41+ * May be ignored.
 42+ */
 43+ abstract function toHtml( $attribs = array() , $linkAttribs = false );
 44+
 45+ /**
 46+ * This will be overridden to return true in error classes
 47+ */
 48+ function isError() {
 49+ return false;
 50+ }
 51+
 52+ /**
 53+ * Wrap some XHTML text in an anchor tag with the given attributes
 54+ */
 55+ protected function linkWrap( $linkAttribs, $contents ) {
 56+ if ( $linkAttribs ) {
 57+ return Xml::tags( 'a', $linkAttribs, $contents );
 58+ } else {
 59+ return $contents;
 60+ }
 61+ }
 62+}
 63+
 64+
 65+/**
 66+ * Media transform output for images
 67+ */
 68+class ThumbnailImage extends MediaTransformOutput {
 69+ /**
 70+ * @param string $path Filesystem path to the thumb
 71+ * @param string $url URL path to the thumb
 72+ * @private
 73+ */
 74+ function ThumbnailImage( $url, $width, $height, $path = false ) {
 75+ $this->url = $url;
 76+ # These should be integers when they get here.
 77+ # If not, there's a bug somewhere. But let's at
 78+ # least produce valid HTML code regardless.
 79+ $this->width = round( $width );
 80+ $this->height = round( $height );
 81+ $this->path = $path;
 82+ }
 83+
 84+ /**
 85+ * Return HTML <img ... /> tag for the thumbnail, will include
 86+ * width and height attributes and a blank alt text (as required).
 87+ *
 88+ * You can set or override additional attributes by passing an
 89+ * associative array of name => data pairs. The data will be escaped
 90+ * for HTML output, so should be in plaintext.
 91+ *
 92+ * If $linkAttribs is given, the image will be enclosed in an <a> tag.
 93+ *
 94+ * @param array $attribs
 95+ * @param array $linkAttribs
 96+ * @return string
 97+ * @public
 98+ */
 99+ function toHtml( $attribs = array(), $linkAttribs = false ) {
 100+ $attribs['src'] = $this->url;
 101+ $attribs['width'] = $this->width;
 102+ $attribs['height'] = $this->height;
 103+ if( !isset( $attribs['alt'] ) ) $attribs['alt'] = '';
 104+ return $this->linkWrap( $linkAttribs, Xml::element( 'img', $attribs ) );
 105+ }
 106+
 107+}
 108+
 109+/**
 110+ * Basic media transform error class
 111+ */
 112+class MediaTransformError extends MediaTransformOutput {
 113+ var $htmlMsg, $textMsg, $width, $height, $url, $path;
 114+
 115+ function __construct( $msg, $width, $height /*, ... */ ) {
 116+ $args = array_slice( func_get_args(), 3 );
 117+ $htmlArgs = array_map( 'htmlspecialchars', $args );
 118+ $htmlArgs = array_map( 'nl2br', $htmlArgs );
 119+
 120+ $this->htmlMsg = wfMsgReplaceArgs( htmlspecialchars( wfMsgGetKey( $msg, true ) ), $htmlArgs );
 121+ $this->textMsg = wfMsgReal( $msg, $args );
 122+ $this->width = intval( $width );
 123+ $this->height = intval( $height );
 124+ $this->url = false;
 125+ $this->path = false;
 126+ }
 127+
 128+ function toHtml( $attribs = array(), $linkAttribs = false ) {
 129+ return "<table class=\"MediaTransformError\" style=\"" .
 130+ "width: {$this->width}px; height: {$this->height}px;\"><tr><td>" .
 131+ $this->htmlMsg .
 132+ "</td></tr></table>";
 133+ }
 134+
 135+ function toText() {
 136+ return $this->textMsg;
 137+ }
 138+
 139+ function getHtmlMsg() {
 140+ return $this->htmlMsg;
 141+ }
 142+
 143+ function isError() {
 144+ return true;
 145+ }
 146+}
 147+
 148+/**
 149+ * Shortcut class for parameter validation errors
 150+ */
 151+class TransformParameterError extends MediaTransformError {
 152+ function __construct( $params ) {
 153+ parent::__construct( 'thumbnail_error',
 154+ max( @$params['width'], 180 ), max( @$params['height'], 180 ),
 155+ wfMsg( 'thumbnail_invalid_params' ) );
 156+ }
 157+}
 158+
 159+?>
Property changes on: trunk/phase3/includes/MediaTransformOutput.php
___________________________________________________________________
Added: svn:eol-style
1160 + native
Index: trunk/phase3/includes/ImageGallery.php
@@ -183,7 +183,7 @@
184184 *
185185 */
186186 function toHTML() {
187 - global $wgLang, $wgGenerateThumbnailOnParse;
 187+ global $wgLang;
188188
189189 $sk = $this->getSkin();
190190
@@ -191,6 +191,7 @@
192192 if( $this->mCaption )
193193 $s .= "\n\t<caption>{$this->mCaption}</caption>";
194194
 195+ $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights );
195196 $i = 0;
196197 foreach ( $this->mImages as $pair ) {
197198 $img =& $pair[0];
@@ -206,7 +207,7 @@
207208 # The image is blacklisted, just show it as a text link.
208209 $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
209210 . $sk->makeKnownLinkObj( $nt, htmlspecialchars( $nt->getText() ) ) . '</div>';
210 - } elseif( !( $thumb = $img->getThumbnail( $this->mWidths, $this->mHeights, $wgGenerateThumbnailOnParse ) ) ) {
 211+ } elseif( !( $thumb = $img->transform( $params ) ) ) {
211212 # Error generating thumbnail.
212213 $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">'
213214 . htmlspecialchars( $img->getLastError() ) . '</div>';
Index: trunk/phase3/includes/DjVuImage.php
@@ -220,17 +220,121 @@
221221 * @return string
222222 */
223223 function retrieveMetaData() {
224 - global $wgDjvuToXML;
225 - if ( isset( $wgDjvuToXML ) ) {
 224+ global $wgDjvuToXML, $wgDjvuDump;
 225+ if ( isset( $wgDjvuDump ) ) {
 226+ # djvudump is faster as of version 3.5
 227+ # http://sourceforge.net/tracker/index.php?func=detail&aid=1704049&group_id=32953&atid=406583
 228+ wfProfileIn( 'djvudump' );
 229+ $cmd = wfEscapeShellArg( $wgDjvuDump ) . ' ' . wfEscapeShellArg( $this->mFilename );
 230+ $dump = wfShellExec( $cmd );
 231+ $xml = $this->convertDumpToXML( $dump );
 232+ wfProfileOut( 'djvudump' );
 233+ } elseif ( isset( $wgDjvuToXML ) ) {
 234+ wfProfileIn( 'djvutoxml' );
226235 $cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' .
227236 wfEscapeShellArg( $this->mFilename );
228237 $xml = wfShellExec( $cmd );
 238+ wfProfileOut( 'djvutoxml' );
229239 } else {
230240 $xml = null;
231241 }
232242 return $xml;
233243 }
234 -
 244+
 245+ /**
 246+ * Hack to temporarily work around djvutoxml bug
 247+ */
 248+ function convertDumpToXML( $dump ) {
 249+ if ( strval( $dump ) == '' ) {
 250+ return false;
 251+ }
 252+
 253+ $xml = <<<EOT
 254+<?xml version="1.0" ?>
 255+<!DOCTYPE DjVuXML PUBLIC "-//W3C//DTD DjVuXML 1.1//EN" "pubtext/DjVuXML-s.dtd">
 256+<DjVuXML>
 257+<HEAD></HEAD>
 258+<BODY>
 259+EOT;
 260+
 261+ $dump = str_replace( "\r", '', $dump );
 262+ $line = strtok( $dump, "\n" );
 263+ $m = false;
 264+ $good = false;
 265+ if ( preg_match( '/^( *)FORM:DJVU/', $line, $m ) ) {
 266+ # Single-page
 267+ if ( $this->parseFormDjvu( $line, $xml ) ) {
 268+ $good = true;
 269+ } else {
 270+ return false;
 271+ }
 272+ } elseif ( preg_match( '/^( *)FORM:DJVM/', $line, $m ) ) {
 273+ # Multi-page
 274+ $parentLevel = strlen( $m[1] );
 275+ # Find DIRM
 276+ $line = strtok( "\n" );
 277+ while ( $line !== false ) {
 278+ $childLevel = strspn( $line, ' ' );
 279+ if ( $childLevel <= $parentLevel ) {
 280+ # End of chunk
 281+ break;
 282+ }
 283+
 284+ if ( preg_match( '/^ *DIRM.*indirect/', $line ) ) {
 285+ wfDebug( "Indirect multi-page DjVu document, bad for server!\n" );
 286+ return false;
 287+ }
 288+ if ( preg_match( '/^ *FORM:DJVU/', $line ) ) {
 289+ # Found page
 290+ if ( $this->parseFormDjvu( $line, $xml ) ) {
 291+ $good = true;
 292+ } else {
 293+ return false;
 294+ }
 295+ }
 296+ $line = strtok( "\n" );
 297+ }
 298+ }
 299+ if ( !$good ) {
 300+ return false;
 301+ }
 302+
 303+ $xml .= "</BODY>\n</DjVuXML>\n";
 304+ return $xml;
 305+ }
 306+
 307+ function parseFormDjvu( $line, &$xml ) {
 308+ $parentLevel = strspn( $line, ' ' );
 309+ $line = strtok( "\n" );
 310+
 311+ # Find INFO
 312+ while ( $line !== false ) {
 313+ $childLevel = strspn( $line, ' ' );
 314+ if ( $childLevel <= $parentLevel ) {
 315+ # End of chunk
 316+ break;
 317+ }
 318+
 319+ if ( preg_match( '/^ *INFO *\[\d*\] *DjVu *(\d+)x(\d+), *\w*, *(\d+) *dpi, *gamma=([0-9.-]+)/', $line, $m ) ) {
 320+ $xml .= Xml::tags( 'OBJECT',
 321+ array(
 322+ #'data' => '',
 323+ #'type' => 'image/x.djvu',
 324+ 'height' => $m[2],
 325+ 'width' => $m[1],
 326+ #'usemap' => '',
 327+ ),
 328+ "\n" .
 329+ Xml::element( 'PARAM', array( 'name' => 'DPI', 'value' => $m[3] ) ) . "\n" .
 330+ Xml::element( 'PARAM', array( 'name' => 'GAMMA', 'value' => $m[4] ) ) . "\n"
 331+ ) . "\n";
 332+ return true;
 333+ }
 334+ $line = strtok( "\n" );
 335+ }
 336+ # Not found
 337+ return false;
 338+ }
235339 }
236340
237341
Index: trunk/phase3/config/index.php
@@ -483,8 +483,6 @@
484484 }
485485 }
486486
487 -$conf->UseImageResize = $conf->HaveGD || $conf->ImageMagick;
488 -
489487 $conf->IP = dirname( dirname( __FILE__ ) );
490488 print "<li>Installation directory: <tt>" . htmlspecialchars( $conf->IP ) . "</tt></li>\n";
491489
@@ -1302,7 +1300,6 @@
13031301 }
13041302
13051303 function writeLocalSettings( $conf ) {
1306 - $conf->UseImageResize = $conf->UseImageResize ? 'true' : 'false';
13071304 $conf->PasswordSender = $conf->EmergencyContact;
13081305 $magic = ($conf->ImageMagick ? "" : "# ");
13091306 $convert = ($conf->ImageMagick ? $conf->ImageMagick : "/usr/bin/convert" );
@@ -1448,7 +1445,6 @@
14491446 ## To enable image uploads, make sure the 'images' directory
14501447 ## is writable, then set this to true:
14511448 \$wgEnableUploads = false;
1452 -\$wgUseImageResize = {$conf->UseImageResize};
14531449 {$magic}\$wgUseImageMagick = true;
14541450 {$magic}\$wgImageMagickConvertCommand = \"{$convert}\";
14551451
Index: trunk/phase3/thumb.php
@@ -9,46 +9,51 @@
1010 require_once( './includes/WebStart.php' );
1111 wfProfileIn( 'thumb.php' );
1212 wfProfileIn( 'thumb.php-start' );
13 -require_once( './includes/GlobalFunctions.php' );
14 -require_once( './includes/ImageFunctions.php' );
 13+require_once( "$IP/includes/GlobalFunctions.php" );
 14+require_once( "$IP/includes/ImageFunctions.php" );
1515
1616 $wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png.
1717
18 -require_once( './includes/Image.php' );
19 -require_once( './includes/StreamFile.php' );
 18+require_once( "$IP/includes/StreamFile.php" );
 19+require_once( "$IP/includes/AutoLoader.php" );
2020
2121 // Get input parameters
22 -$fileName = isset( $_REQUEST['f'] ) ? $_REQUEST['f'] : '';
23 -$width = isset( $_REQUEST['w'] ) ? intval( $_REQUEST['w'] ) : 0;
24 -$page = isset( $_REQUEST['p'] ) ? intval( $_REQUEST['p'] ) : null;
25 -
2622 if ( get_magic_quotes_gpc() ) {
27 - $fileName = stripslashes( $fileName );
 23+ $params = array_map( 'stripslashes', $_REQUEST );
 24+} else {
 25+ $params = $_REQUEST;
2826 }
2927
30 -$pre_render= isset($_REQUEST['r']) && $_REQUEST['r']!="0";
 28+$fileName = isset( $params['f'] ) ? $params['f'] : '';
 29+unset( $params['f'] );
3130
 31+// Backwards compatibility parameters
 32+if ( isset( $params['w'] ) ) {
 33+ $params['width'] = $params['w'];
 34+ unset( $params['w'] );
 35+}
 36+if ( isset( $params['p'] ) ) {
 37+ $params['page'] = $params['p'];
 38+}
 39+unset( $params['r'] );
 40+
3241 // Some basic input validation
3342 $fileName = strtr( $fileName, '\\/', '__' );
3443
3544 // Work out paths, carefully avoiding constructing an Image object because that won't work yet
 45+$handler = thumbGetHandler( $fileName );
 46+if ( $handler ) {
 47+ $imagePath = wfImageDir( $fileName ) . '/' . $fileName;
 48+ $thumbName = $handler->makeParamString( $params ) . "-$fileName";
 49+ $thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
3650
37 -$imagePath = wfImageDir( $fileName ) . '/' . $fileName;
38 -$thumbName = "{$width}px-$fileName";
39 -if ( ! is_null( $page ) ) {
40 - $thumbName = 'page' . $page . '-' . $thumbName;
 51+ if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
 52+ wfStreamFile( $thumbPath );
 53+ // Can't log profiling data with no Setup.php
 54+ exit;
 55+ }
4156 }
42 -if ( $pre_render ) {
43 - $thumbName .= '.png';
44 -}
45 -$thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName;
4657
47 -if ( is_file( $thumbPath ) && filemtime( $thumbPath ) >= filemtime( $imagePath ) ) {
48 - wfStreamFile( $thumbPath );
49 - // Can't log profiling data with no Setup.php
50 - exit;
51 -}
52 -
5358 // OK, no valid thumbnail, time to get out the heavy machinery
5459 wfProfileOut( 'thumb.php-start' );
5560 require_once( './includes/Setup.php' );
@@ -57,10 +62,7 @@
5863 $img = Image::newFromName( $fileName );
5964 try {
6065 if ( $img ) {
61 - if ( ! is_null( $page ) ) {
62 - $img->selectPage( $page );
63 - }
64 - $thumb = $img->renderThumb( $width, false );
 66+ $thumb = $img->transform( $params, Image::RENDER_NOW );
6567 } else {
6668 $thumb = false;
6769 }
@@ -69,13 +71,33 @@
7072 $thumb = false;
7173 }
7274
73 -if ( $thumb && $thumb->path ) {
74 - wfStreamFile( $thumb->path );
 75+if ( $thumb && $thumb->getPath() ) {
 76+ wfStreamFile( $thumb->getPath() );
 77+} elseif ( $img ) {
 78+ header( 'Cache-Control: no-cache' );
 79+ header( 'Content-Type: text/html; charset=utf-8' );
 80+ header( 'HTTP/1.1 500 Internal server error' );
 81+ if ( !$thumb ) {
 82+ $msg = wfMsgHtml( 'thumbnail_error', 'Image::transform() returned false' );
 83+ } elseif ( $thumb->isError() ) {
 84+ $msg = $thumb->toHtml();
 85+ } else {
 86+ $msg = wfMsgHtml( 'thumbnail_error', 'No path supplied in thumbnail object' );
 87+ }
 88+ echo <<<EOT
 89+<html><head><title>Error generating thumbnail</title></head>
 90+<body>
 91+$msg
 92+</body>
 93+</html>
 94+
 95+EOT;
7596 } else {
7697 $badtitle = wfMsg( 'badtitle' );
7798 $badtitletext = wfMsg( 'badtitletext' );
7899 header( 'Cache-Control: no-cache' );
79100 header( 'Content-Type: text/html; charset=utf-8' );
 101+ header( 'HTTP/1.1 500 Internal server error' );
80102 echo "<html><head>
81103 <title>$badtitle</title>
82104 <body>
@@ -89,4 +111,17 @@
90112 wfProfileOut( 'thumb.php' );
91113 wfLogProfilingData();
92114
 115+//--------------------------------------------------------------------------
 116+
 117+function thumbGetHandler( $fileName ) {
 118+ // Determine type
 119+ $magic = MimeMagic::singleton();
 120+ $extPos = strrpos( $fileName, '.' );
 121+ if ( $extPos === false ) {
 122+ return false;
 123+ }
 124+ $mime = $magic->guessTypesForExtension( substr( $fileName, $extPos + 1 ) );
 125+ return MediaHandler::getHandler( $mime );
 126+}
 127+
93128 ?>
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -2139,6 +2139,10 @@
21402140 'missingimage' => '<b>Missing image</b><br /><i>$1</i>',
21412141 'filemissing' => 'File missing',
21422142 'thumbnail_error' => 'Error creating thumbnail: $1',
 2143+'djvu_page_error' => 'DjVu page out of range',
 2144+'djvu_no_xml' => 'Unable to fetch XML for DjVu file',
 2145+'thumbnail_invalid_params' => 'Invalid thumbnail parameters',
 2146+'thumbnail_dest_directory' => 'Unable to create destination directory',
21432147
21442148 # Special:Import
21452149 'import' => 'Import pages',
@@ -2816,8 +2820,8 @@
28172821 * Nederlands|nl",
28182822
28192823 # Multipage image navigation
2820 -'imgmultipageprev' => '&larr; previous page',
2821 -'imgmultipagenext' => 'next page &rarr;',
 2824+'imgmultipageprev' => '← previous page',
 2825+'imgmultipagenext' => 'next page →',
28222826 'imgmultigo' => 'Go!',
28232827 'imgmultigotopre' => 'Go to page',
28242828 'imgmultigotopost' => '',
Index: trunk/phase3/RELEASE-NOTES
@@ -31,6 +31,7 @@
3232 * Added rate limiter for Special:Emailuser
3333 * Private logs can now be created using $wgLogRestrictions
3434 * (Bug 8590) limited HTML is now always enabled ($wgUserHtml = true).
 35+* Deprecated $wgUseImageResize, thumbnailing will be enabled unconditionally.
3536
3637 == New features since 1.9 ==
3738
@@ -117,6 +118,7 @@
118119 * Introduce 'SearchUpdate' hook; see docs/hooks.txt for more information
119120 * Introduce 'mywatchlist' message; used on personal menu to link to watchlist page
120121 * Introduce magic word {{NUMBEROFEDITS}}
 122+* Introduced media handlers for file-type specific operations.
121123
122124 == Bugfixes since 1.9 ==
123125
@@ -327,6 +329,8 @@
328330 a random page, and will give an error message if none really can be found
329331 instead of sending the user to the main page like they used to
330332 * Fix object variable used for displaying "not-patrolled" CSS class on list
 333+* Fixed interaction of page parameter to ImagePage with the HTML file cache
 334+*
331335
332336 == Maintenance ==
333337
Index: trunk/extensions/WebStore/WebStoreClient.php
@@ -66,20 +66,29 @@
6767 /**
6868 * Publish a file, return "archived" if the file existed already and was archived,
6969 * "new" if the file didn't exist, and "failure" if there was a problem. Details
70 - * of the problem are put in $this->lastError
 70+ * of the problem are put in $this->lastError.
 71+ *
 72+ * The source zone may be one of "local", "temp", "public" or "deleted". The
 73+ * file is copied if it is "public", and moved otherwise.
7174 */
72 - function publish( $srcRepo, $srcRel, $dstRel, $archiveRel ) {
 75+ function publish( $srcZone, $srcRel, $dstRel, $archiveRel ) {
7376 global $wgServer, $wgScriptPath;
7477
75 - if ( $srcRepo == 'local' ) {
76 - // Need to store the file first.
77 - $srcRel = $this->store( $srcRel );
78 - $srcRepo = 'temp';
 78+ if ( $srcZone == 'local' ) {
 79+ // Local pseudo-zone, need to store the file first.
 80+ $sharedSrcRel = $this->store( $srcRel );
 81+ if ( !$sharedSrcRel ) {
 82+ return 'failure';
 83+ }
 84+ // Delete temporary source file, to simulate rename()
 85+ unlink( $srcRel );
 86+ $srcRel = $sharedSrcRel;
 87+ $srcZone = 'temp';
7988 }
8089
8190 $content = $this->post( $this->getURL( 'publish.php' ),
8291 array(
83 - 'srcRepo' => $srcRepo,
 92+ 'srcZone' => $srcZone,
8493 'src' => $srcRel,
8594 'dst' => $dstRel,
8695 'archive' => $archiveRel
@@ -113,10 +122,10 @@
114123 return isset( $response->status ) && $response->status == 'success';
115124 }
116125
117 - function metadata( $repository, $path ) {
 126+ function metadata( $zone, $path ) {
118127 $content = $this->post( $this->getURL( 'metadata.php' ),
119128 array(
120 - 'repository' => $repository,
 129+ 'zone' => $zone,
121130 'path' => $path,
122131 ));
123132 $response = $this->parseResponse( $content );
Index: trunk/extensions/WebStore/WebStore.php
@@ -64,6 +64,7 @@
6565
6666 $wgAutoloadClasses['WebStoreClient'] = 'extensions/WebStore/WebStoreClient.php';
6767 $wgAutoloadClasses['WebStoreCommon'] = 'extensions/WebStore/WebStoreCommon.php';
 68+$wgAutoloadClasses['WebStoreLocalImage'] = 'extensions/WebStore/WebStoreCommon.php';
6869 $wgAutoloadClasses['WebStorePostFile'] = 'extensions/WebStore/WebStorePostFile.php';
6970 $wgHooks['LoadAllMessages'][] = 'WebStoreCommon::initialiseMessages';
7071
Index: trunk/extensions/WebStore/metadata.php
@@ -6,18 +6,6 @@
77
88 require( dirname( __FILE__ ) . '/WebStoreStart.php' );
99
10 -class WebStoreImage extends Image {
11 - function __construct( $path ) {
12 - $title = Title::makeTitle( NS_IMAGE, 'Metadata.php dummy image');
13 - $this->imagePath = $path;
14 - parent::__construct( $title );
15 - }
16 -
17 - function getFullPath() {
18 - return $this->imagePath;
19 - }
20 -}
21 -
2210 class WebStoreMetadata extends WebStoreCommon {
2311 function execute() {
2412 global $wgRequest;
@@ -29,7 +17,7 @@
3018 <head><title>metadata.php Test interface</title>
3119 <body>
3220 <form method="post" action="metadata.php">
33 -<p>Repository: <select name="repository" value="public">
 21+<p>Zone: <select name="zone" value="public">
3422 <option>public</option>
3523 <option>temp</option>
3624 <option>deleted</option>
@@ -43,10 +31,10 @@
4432 return true;
4533 }
4634
47 - $repository = $wgRequest->getVal( 'repository' );
48 - $root = $this->getRepositoryRoot( $repository );
 35+ $zone = $wgRequest->getVal( 'zone' );
 36+ $root = $this->getZoneRoot( $zone );
4937 if ( strval( $root ) == '' ) {
50 - $this->error( 400, 'webstore_invalid_repository', $repository );
 38+ $this->error( 400, 'webstore_invalid_zone', $zone );
5139 return false;
5240 }
5341
@@ -58,21 +46,42 @@
5947
6048 $fullPath = $root . '/' . $rel;
6149
62 - if ( !file_exists( $fullPath ) ) {
 50+ $name = basename( $fullPath );
 51+ $i = strrpos( $name, '.' );
 52+ $ext = Image::normalizeExtension( $i ? substr( $name, $i + 1 ) : '' );
 53+ $magic = MimeMagic::singleton();
 54+ $mime = $magic->guessTypesForExtension( $ext );
 55+ $type = $magic->getMediaType( $fullPath, $mime);
 56+
 57+ $stat = stat( $fullPath );
 58+ if ( !$stat ) {
6359 $this->error( 400, 'webstore_metadata_not_found', $fullPath );
6460 return false;
6561 }
6662
67 - $image = new WebStoreImage( $fullPath );
68 - $image->loadFromFile();
 63+ $image = new WebStoreLocalImage( $fullPath, $mime );
 64+ if ( !$image->getHandler() ) {
 65+ $this->error( 400, 'webstore_no_handler' );
 66+ return false;
 67+ }
 68+ $gis = $image->getImageSize();
 69+ $handlerMeta = $image->getMetadata();
 70+ $stat = stat( $fullPath );
6971
70 - $fields = array( 'width', 'height', 'bits', 'type', 'mime', 'metadata', 'size' );
 72+ $metadata = array(
 73+ 'width' => $gis[0],
 74+ 'height' => $gis[1],
 75+ 'bits' => isset( $gis['bits'] ) ? $gis['bits'] : '',
 76+ 'type' => $type,
 77+ 'mime' => $mime,
 78+ 'metadata' => $handlerMeta,
 79+ 'size' => $stat['size'],
 80+ );
7181
7282 header( 'Content-Type: text/xml' );
7383 echo "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<response><status>success</status><metadata>\n";
74 - foreach ( $fields as $field ) {
75 - $value = $image->$field;
76 - if ( is_bool( $image->$field ) ) {
 84+ foreach ( $metadata as $field => $value ) {
 85+ if ( is_bool( $value ) ) {
7786 $value = $value ? 1 : 0;
7887 }
7988 echo "<item name=\"$field\">" . htmlspecialchars( $value ) . "</item>\n";
Index: trunk/extensions/WebStore/404-handler.php
@@ -39,19 +39,46 @@
4040 return false;
4141 }
4242
43 - if ( !preg_match( '!^(\w)/(\w\w)/([^/]*)/(page(\d*)-)?(\d*)px-([^/]*)$!', $rel, $parts ) ) {
 43+ if ( !preg_match( '!^(\w)/(\w\w)/([^/]*)/([^/]*)$!', $rel, $parts ) ) {
4444 header( 'X-Debug: regex mismatch' );
4545 $this->real404();
4646 return false;
4747 }
4848
49 - list( $all, $hash1, $hash2, $filename, $pagefull, $pagenum, $size, $fn2 ) = $parts;
50 - if( $filename != $fn2 && "$filename.png" != $fn2 ) {
 49+ list( $all, $hash1, $hash2, $filename, $thumbName ) = $parts;
 50+ $srcNamePos = strrpos( $thumbName, $filename );
 51+ if ( $srcNamePos === false ) {
5152 header( 'X-Debug: filename/fn2 mismatch' );
5253 $this->real404();
5354 return false;
5455 }
 56+ $extraExt = substr( $thumbName, $srcNamePos + strlen( $filename ) );
 57+ if ( $extraExt != '' && $extraExt[0] != '.' ) {
 58+ header( "X-Debug: invalid trailing characters in filename: $extraExt" );
 59+ $this->real404();
 60+ return false;
 61+ }
 62+ // Determine MIME type
 63+ $extPos = strrpos( $filename, '.' );
 64+ $srcExt = $extPos === false ? '' : substr( $filename, $extPos + 1 );
 65+ $magic = MimeMagic::singleton();
 66+ $mime = $magic->guessTypesForExtension( $srcExt );
 67+ $handler = MediaHandler::getHandler( $mime );
 68+ if ( !$handler ) {
 69+ header( 'X-Debug: no handler' );
 70+ $this->real404();
 71+ return false;
 72+ }
5573
 74+ // Parse parameter string
 75+ $paramString = substr( $thumbName, 0, $srcNamePos - 1 );
 76+ $params = $handler->parseParamString( $paramString );
 77+ if ( !$params ) {
 78+ header( "X-Debug: handler for $mime says param string is invalid" );
 79+ $this->real404();
 80+ return false;
 81+ }
 82+
5683 // Open the destination temporary file
5784 $dstPath = "{$this->publicDir}/thumb/$rel";
5885 $tmpPath = "$dstPath.temp.MW_WebStore";
@@ -114,7 +141,7 @@
115142
116143 $post = WebStorePostFile::post( $scalerUrl, 'data',
117144 "{$this->publicDir}/$hash1/$hash2/$filename",
118 - array( 'width' => $size, 'page' => $pagenum ),
 145+ $params,
119146 $server, $tmpFile, $this->httpConnectTimeout, $this->httpOverallTimeout );
120147
121148 // Try next server unless that one was successful
@@ -135,6 +162,12 @@
136163 break;
137164 }
138165
 166+ fseek( $tmpFile, 0, SEEK_END );
 167+ if ( ftell( $tmpFile ) == 0 ) {
 168+ $this->htmlError( 500, 'webstore_scaler_empty_response' );
 169+ break;
 170+ }
 171+
139172 // Report PHP errors
140173 if ( count( $this->phpErrors ) ) {
141174 $errors = '<ul>';
Index: trunk/extensions/WebStore/inplace-scaler.php
@@ -7,7 +7,7 @@
88 global $wgRequest, $wgContLanguageCode;
99
1010 if ( !$this->scalerAccessRanges ) {
11 - $this->error( 403, 'inplace_access_disabled' );
 11+ $this->htmlError( 403, 'inplace_access_disabled' );
1212 return false;
1313 }
1414
@@ -24,7 +24,7 @@
2525 }
2626
2727 if ( !$allowed ) {
28 - $this->error( 403, 'inplace_access_denied' );
 28+ $this->htmlError( 403, 'inplace_access_denied' );
2929 return false;
3030 }
3131
@@ -49,20 +49,18 @@
5050 $tempDir = $this->tmpDir . '/' . gmdate( self::$tempDirFormat );
5151 if ( !is_dir( $tempDir ) ) {
5252 if ( !wfMkdirParents( $tempDir ) ) {
53 - $this->error( 500, 'inplace_scaler_no_temp' );
 53+ $this->htmlError( 500, 'inplace_scaler_no_temp' );
5454 return false;
5555 }
5656 }
5757
5858 $name = $wgRequest->getFileName( 'data' );
5959 $srcTemp = $wgRequest->getFileTempname( 'data' );
60 - $page = $wgRequest->getInt( 'page', 1 );
61 - $dstWidth = $wgRequest->getInt( 'width', 0 );
6260
63 - # Check that the parameters are present
64 - if ( is_null( $name ) || !$dstWidth ) {
65 - $this->error( 400, 'inplace_scaler_not_enough_params' );
66 - return false;
 61+ $params = $_REQUEST;
 62+ unset( $params['file'] );
 63+ if ( get_magic_quotes_gpc() ) {
 64+ $params = array_map( 'stripslashes', $params );
6765 }
6866
6967 $i = strrpos( $name, '.' );
@@ -70,16 +68,26 @@
7169
7270 $magic = MimeMagic::singleton();
7371 $mime = $magic->guessTypesForExtension( $ext );
74 - $deja = false;
75 - $size = Image::getImageSize( $srcTemp, $mime, $deja );
76 - if ( !$size ) {
77 - $this->error( 400, 'inplace_scaler_invalid_image' );
 72+
 73+ $image = new WebStoreLocalImage( $srcTemp, $mime );
 74+
 75+ $handler = $image->getHandler();
 76+ if ( !$handler ) {
 77+ $this->htmlError( 400, 'inplace_scaler_no_handler' );
7878 return false;
7979 }
8080
81 - $dstHeight = Image::scaleHeight( $size[0], $size[1], $dstWidth );
 81+ if ( !isset( $params['page'] ) ) {
 82+ $params['page'] = 1;
 83+ }
 84+ $srcWidth = $image->getWidth( $params['page'] );
 85+ $srcHeight = $image->getHeight( $params['page'] );
 86+ if ( $srcWidth <= 0 || $srcHeight <= 0 ) {
 87+ $this->htmlError( 400, 'inplace_scaler_invalid_image' );
 88+ return false;
 89+ }
8290
83 - list( $dstExt, $dstMime ) = Image::getThumbType( $ext, $mime );
 91+ list( $dstExt, $dstMime ) = $handler->getThumbType( $ext, $mime );
8492 if ( preg_match( '/[ \\n;=]/', $name ) ) {
8593 $dstName = "thumb.$ext";
8694 } else {
@@ -91,13 +99,26 @@
92100
93101 $dstTemp = tempnam( $tempDir, 'mwimg' );
94102
95 - $error = Image::reallyRenderThumb( $srcTemp, $dstTemp, $mime, $dstWidth, $dstHeight, $page );
96 - if ( $error !== true ) {
97 - $this->error( 500, 'inplace_scaler_failed', $error );
98 - @unlink( $dstTemp );
 103+ $thumb = $handler->doTransform( $image, $dstTemp, false, $params );
 104+ if ( !$thumb || $thumb->isError() ) {
 105+ $error = $thumb ? $thumb->getHtmlMsg() : '';
 106+ $this->htmlErrorReal( 500, 'inplace_scaler_failed', array(''), $error );
 107+ unlink( $dstTemp );
99108 return false;
100109 }
 110+ $stat = stat( $dstTemp );
 111+ if ( !$stat ) {
 112+ $this->htmlError( 500, 'inplace_scaler_no_output' );
 113+ return false;
 114+ }
101115
 116+ if ( $stat['size'] == 0 ) {
 117+ $this->htmlError( 500, 'inplace_scaler_no_output' );
 118+ unlink( $dstTemp );
 119+ return false;
 120+ }
 121+
 122+ wfDebug( __METHOD__.": transformation completed successfully, streaming output...\n" );
102123 header( "Content-Type: $dstMime" );
103124 header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( $dstName ) );
104125 readfile( $dstTemp );
@@ -105,7 +126,10 @@
106127 }
107128 }
108129
109 -
 130+// Fatal errors can cause PHP to spew out some HTML and exit with a 200 response,
 131+// which would leave a corrupt image file permanently on disk. Prevent this from
 132+// happening.
 133+ini_set( 'display_errors', false );
110134 $s = new InplaceScaler;
111135 $s->execute();
112136
Index: trunk/extensions/WebStore/WebStoreCommon.php
@@ -79,20 +79,32 @@
8080 }
8181
8282 function htmlError( $code, $msgName /*, ... */ ) {
83 - $params = array_slice( func_get_args(), 1 );
84 - $msgText = htmlspecialchars( call_user_func_array( 'wfMsg', $params ) );
85 - $encMsgName = htmlspecialchars( $msgName );
86 - $info = self::$httpErrors[$code];
 83+ $params = array_slice( func_get_args(), 1 );
 84+ $this->htmlErrorReal( $code, $msgName, $params );
 85+ }
 86+
 87+ function htmlErrorReal( $code, $msgName, $msgParams = array(), $extra = '' ) {
 88+ global $wgLogo;
 89+ $msgText = htmlspecialchars( wfMsgReal( $msgName, $msgParams ) );
 90+ $encMsgName = htmlspecialchars( $msgName );
 91+ $info = self::$httpErrors[$code];
 92+ $logo = htmlspecialchars( $wgLogo );
8793 header( "HTTP/1.1 $code $info" );
8894 echo $this->dtd();
8995 echo <<<EOT
90 -<html><head><title>$info</title></head>
91 -<body><h1>$info</h1><p>
 96+<html>
 97+<head>
 98+<title>$info</title></head>
 99+<body>
 100+<h1><img src="$logo" style='float:left;margin-right:1em' alt=''>$info</h1><p>
92101 $encMsgName: $msgText
93 -</p></body></html>
 102+</p>
 103+$extra
 104+</body>
 105+</html>
94106
95107 EOT;
96 - }
 108+ }
97109
98110 function executeCommon() {
99111 if ( !$this->checkAccess() ) {
@@ -378,10 +390,10 @@
379391 }
380392
381393 /**
382 - * Get the root directory for a given repository: public, temp or deleted
 394+ * Get the root directory for a given zone: public, temp or deleted
383395 */
384 - function getRepositoryRoot( $repository ) {
385 - switch ( $repository ) {
 396+ function getZoneRoot( $zone ) {
 397+ switch ( $zone ) {
386398 case 'public':
387399 return $this->publicDir;
388400 case 'temp':
@@ -481,4 +493,58 @@
482494 }
483495 }
484496
 497+class WebStoreLocalImage {
 498+ function __construct( $path, $mime ) {
 499+ $this->imagePath = $path;
 500+ $this->mime = $mime;
 501+ $this->handler = MediaHandler::getHandler( $mime );
 502+ $this->dims = array();
 503+ }
 504+
 505+ function getPageDimensions( $page = 1 ) {
 506+ if ( !isset( $this->dims[$page] ) ) {
 507+ $this->dims[$page] = $this->handler->getPageDimensions( $this, $page );
 508+ }
 509+ return $this->dims[$page];
 510+ }
 511+
 512+ function getWidth( $page = 1 ) {
 513+ $dim = $this->getPageDimensions( $page );
 514+ return $dim['width'];
 515+ }
 516+
 517+ function getHeight( $page = 1 ) {
 518+ $dim = $this->getPageDimensions( $page );
 519+ return $dim['height'];
 520+ }
 521+
 522+ function getMimeType() {
 523+ return $this->mime;
 524+ }
 525+
 526+ function getImagePath() {
 527+ return $this->imagePath;
 528+ }
 529+
 530+ function getHandler() {
 531+ return $this->handler;
 532+ }
 533+
 534+ function getImageSize() {
 535+ return $this->handler->getImageSize( $this, $this->getImagePath() );
 536+ }
 537+
 538+ function getMetadata() {
 539+ if ( !isset( $this->metadata ) ) {
 540+ $this->metadata = $this->handler->getMetadata( $this, $this->getImagePath() );
 541+ }
 542+ return $this->metadata;
 543+ }
 544+
 545+ function getURL() {
 546+ return false;
 547+ }
 548+}
 549+
 550+
485551 ?>
Index: trunk/extensions/WebStore/publish.php
@@ -25,7 +25,7 @@
2626 <head><title>publish.php Test Interface</title></head>
2727 <body>
2828 <form method="post" action="publish.php">
29 -<p>Source repository: <select name="srcRepo" value="public">
 29+<p>Source zone: <select name="srcZone" value="public">
3030 <option>public</option>
3131 <option>temp</option>
3232 <option>deleted</option>
@@ -41,12 +41,12 @@
4242 return true;
4343 }
4444
45 - $srcRepo = $wgRequest->getVal( 'srcRepo' );
46 - if ( !$srcRepo ) {
47 - $srcRepo = 'temp';
 45+ $srcZone = $wgRequest->getVal( 'srcZone' );
 46+ if ( !$srcZone ) {
 47+ $srcZone = 'temp';
4848 }
49 - // Delete the source file if the source repo is not the public one
50 - $deleteSource = ( $srcRepo != 'public' );
 49+ // Delete the source file if the source zone is not the public one
 50+ $deleteSource = ( $srcZone != 'public' );
5151
5252 $srcRel = $wgRequest->getVal( 'src' );
5353 $dstRel = $wgRequest->getVal( 'dst' );
@@ -61,7 +61,7 @@
6262 return false;
6363 }
6464
65 - // Don't publish into odd subdirectories of the public repository.
 65+ // Don't publish into odd subdirectories of the public zone.
6666 // Some directories may be temporary caches with a potential for
6767 // data loss.
6868 if ( !preg_match( '!^archive|[a-zA-Z0-9]/!', $dstRel ) ) {
@@ -77,9 +77,9 @@
7878 return false;
7979 }
8080
81 - $srcRoot = $this->getRepositoryRoot( $srcRepo );
 81+ $srcRoot = $this->getZoneRoot( $srcZone );
8282 if ( strval( $srcRoot ) == '' ) {
83 - $this->error( 400, 'webstore_invalid_repository', $srcRepo );
 83+ $this->error( 400, 'webstore_invalid_zone', $srcZone );
8484 return false;
8585 }
8686
@@ -88,7 +88,10 @@
8989 $archivePath = $this->publicDir . '/archive/' . $archiveRel;
9090
9191 if ( file_exists( $dstPath ) ) {
92 - if ( $this->publishAndArchive( $srcPath, $dstPath, $archivePath, $deleteSource ) ) {
 92+ if ( $dstRel == '' ) {
 93+ $this->errors[] = new WebStoreError( 'webstore_no_archive' );
 94+ $status = 'failure';
 95+ } elseif ( $this->publishAndArchive( $srcPath, $dstPath, $archivePath, $deleteSource ) ) {
9396 $status = 'archived';
9497 } else {
9598 $status = 'failure';
Index: trunk/extensions/WebStore/WebStore.i18n.php
@@ -8,6 +8,9 @@
99 'inplace_scaler_not_enough_params' => 'Not enough parameters.',
1010 'inplace_scaler_invalid_image' => 'Invalid image, could not determine size.',
1111 'inplace_scaler_failed' => 'An error was encountered during image scaling: $1',
 12+ 'inplace_scaler_no_handler' => 'No handler for transforming this MIME type',
 13+ 'inplace_scaler_no_output' => 'No transformation output file was produced.',
 14+ 'inplace_scaler_zero_size' => 'Transformation produced a zero-sized output file.',
1215
1316 'webstore_access' => 'This service is restricted by client IP.',
1417 'webstore_path_invalid' => 'The filename was invalid.',
@@ -28,11 +31,12 @@
2932 'webstore_temp_copy' => 'Error copying temporary file "$1" to destination file "$2".',
3033 'webstore_temp_close' => 'Error closing temporary file "$1".',
3134 'webstore_temp_lock' => 'Error locking temporary file "$1".',
 35+ 'webstore_no_archive' => 'Destination file exists and no archive was given.',
3236
3337 'webstore_no_file' => 'No file was uploaded.',
3438 'webstore_move_uploaded' => 'Error moving uploaded file "$1" to temporary location "$2".',
3539
36 - 'webstore_invalid_repository' => 'Invalid repository "$1".',
 40+ 'webstore_invalid_zone' => 'Invalid zone "$1".',
3741
3842 'webstore_no_deleted' => 'No archive directory for deleted files is defined.',
3943 'webstore_curl' => 'Error from cURL: $1',
@@ -40,11 +44,14 @@
4145 'webstore_php_warning' => 'PHP Warning: $1',
4246 'webstore_metadata_not_found' => 'File not found: $1',
4347 'webstore_postfile_not_found' => 'File to post not found.',
 48+ 'webstore_scaler_empty_response' => 'The image scaler gave an empty response with a 200 ' .
 49+ 'response code. This could be due to a PHP fatal error in the scaler.',
4450
4551 'webstore_invalid_response' => "Invalid response from server:\n\n$1\n",
4652 'webstore_no_response' => 'No response from server',
4753 'webstore_backend_error' => "Error from storage server:\n\n$1\n",
4854 'webstore_php_error' => 'PHP errors were encountered:',
 55+ 'webstore_no_handler' => 'No handler for transforming this MIME type',
4956 ),
5057 );
5158

Follow-up revisions

RevisionCommit summaryAuthorDate
r21650Restored previous interface for thumbUrl(), for backwards compatibility. Fixe...tstarling09:17, 27 April 2007
r21673Fix regression, broken since r21411:...raymond22:04, 27 April 2007

Comments

#Comment by Tbleher (talk | contribs)   17:08, 19 December 2010

The change to MimeMagic.php leads to a wrong values for svg images in the mMimeToExt member variable. After this change, MimeMagic::getExtensionsForType("image/svg+xml") returns, among others, "image/svg". See bug 26367.