| Index: trunk/phase3/skins/monobook/main.css |
| — | — | @@ -1626,3 +1626,13 @@ |
| 1627 | 1627 | .texvc { direction: ltr; unicode-bidi: embed; } |
| 1628 | 1628 | /* Stop floats from intruding into edit area in previews */ |
| 1629 | 1629 | #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 @@ |
| 481 | 481 | #file img, .gallerybox .thumb img { |
| 482 | 482 | background: url(images/Checker-16x16.png) repeat; |
| 483 | 483 | } |
| 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 @@ |
| 2461 | 2461 | $diff = $wgRequest->getVal( 'diff' ); |
| 2462 | 2462 | $redirect = $wgRequest->getVal( 'redirect' ); |
| 2463 | 2463 | $printable = $wgRequest->getVal( 'printable' ); |
| | 2464 | + $page = $wgRequest->getVal( 'page' ); |
| 2464 | 2465 | |
| 2465 | 2466 | return $wgUseFileCache |
| 2466 | 2467 | and (!$wgShowIPinHeader) |
| — | — | @@ -2472,6 +2473,7 @@ |
| 2473 | 2474 | and (!isset($diff)) |
| 2474 | 2475 | and (!isset($redirect)) |
| 2475 | 2476 | and (!isset($printable)) |
| | 2477 | + and !isset($page) |
| 2476 | 2478 | and (!$this->mRedirectedFrom); |
| 2477 | 2479 | } |
| 2478 | 2480 | |
| Index: trunk/phase3/includes/MimeMagic.php |
| — | — | @@ -22,7 +22,7 @@ |
| 23 | 23 | image/gif gif |
| 24 | 24 | image/jpeg jpeg jpg jpe |
| 25 | 25 | image/png png |
| 26 | | -image/svg+xml svg |
| | 26 | +image/svg+xml image/svg svg |
| 27 | 27 | image/tiff tiff tif |
| 28 | 28 | image/vnd.djvu djvu |
| 29 | 29 | image/x-portable-pixmap ppm |
| — | — | @@ -51,7 +51,7 @@ |
| 52 | 52 | image/gif [BITMAP] |
| 53 | 53 | image/jpeg [BITMAP] |
| 54 | 54 | image/png [BITMAP] |
| 55 | | -image/svg image/svg+xml [DRAWING] |
| | 55 | +image/svg+xml [DRAWING] |
| 56 | 56 | image/tiff [BITMAP] |
| 57 | 57 | image/vnd.djvu [BITMAP] |
| 58 | 58 | image/x-portable-pixmap [BITMAP] |
| Index: trunk/phase3/includes/ImagePage.php |
| — | — | @@ -166,11 +166,9 @@ |
| 167 | 167 | |
| 168 | 168 | function openShowImage() { |
| 169 | 169 | global $wgOut, $wgUser, $wgImageLimits, $wgRequest, $wgLang; |
| 170 | | - global $wgUseImageResize, $wgGenerateThumbnailOnParse; |
| 171 | 170 | |
| 172 | 171 | $full_url = $this->img->getURL(); |
| 173 | | - $anchoropen = ''; |
| 174 | | - $anchorclose = ''; |
| | 172 | + $linkAttribs = false; |
| 175 | 173 | $sizeSel = intval( $wgUser->getOption( 'imagesize') ); |
| 176 | 174 | if( !isset( $wgImageLimits[$sizeSel] ) ) { |
| 177 | 175 | $sizeSel = User::getDefaultOption( 'imagesize' ); |
| — | — | @@ -190,10 +188,11 @@ |
| 191 | 189 | if ( $this->img->exists() ) { |
| 192 | 190 | # image |
| 193 | 191 | $page = $wgRequest->getIntOrNull( 'page' ); |
| 194 | | - if ( ! is_null( $page ) ) { |
| 195 | | - $this->img->selectPage( $page ); |
| 196 | | - } else { |
| | 192 | + if ( is_null( $page ) ) { |
| | 193 | + $params = array(); |
| 197 | 194 | $page = 1; |
| | 195 | + } else { |
| | 196 | + $params = array( 'page' => $page ); |
| 198 | 197 | } |
| 199 | 198 | $width_orig = $this->img->getWidth(); |
| 200 | 199 | $width = $width_orig; |
| — | — | @@ -201,6 +200,7 @@ |
| 202 | 201 | $height = $height_orig; |
| 203 | 202 | $mime = $this->img->getMimeType(); |
| 204 | 203 | $showLink = false; |
| | 204 | + $linkAttribs = array( 'href' => $full_url ); |
| 205 | 205 | |
| 206 | 206 | if ( $this->img->allowInlineDisplay() and $width and $height) { |
| 207 | 207 | # image |
| — | — | @@ -223,57 +223,51 @@ |
| 224 | 224 | # Note that $height <= $maxHeight now, but might not be identical |
| 225 | 225 | # because of rounding. |
| 226 | 226 | } |
| | 227 | + } |
| | 228 | + $params['width'] = $width; |
| | 229 | + $thumbnail = $this->img->transform( $params ); |
| 227 | 230 | |
| 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() ) { |
| 250 | 233 | $showLink = true; |
| | 234 | + } else { |
| | 235 | + $anchorclose .= |
| | 236 | + wfMsg('show-big-image-thumb', $width, $height ) . |
| | 237 | + '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $msgsize; |
| 251 | 238 | } |
| 252 | 239 | |
| 253 | 240 | if ( $this->img->isMultipage() ) { |
| 254 | 241 | $wgOut->addHTML( '<table class="multipageimage"><tr><td>' ); |
| 255 | 242 | } |
| 256 | 243 | |
| 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 | + ); |
| 260 | 248 | |
| | 249 | + if ( $thumbnail ) { |
| | 250 | + $wgOut->addHTML( '<div class="fullImageLink" id="file">' . |
| | 251 | + $thumbnail->toHtml( $imgAttribs, $linkAttribs ) . |
| | 252 | + $anchorclose . '</div>' ); |
| | 253 | + } |
| | 254 | + |
| 261 | 255 | if ( $this->img->isMultipage() ) { |
| 262 | 256 | $count = $this->img->pageCount(); |
| 263 | 257 | |
| 264 | 258 | if ( $page > 1 ) { |
| 265 | 259 | $label = $wgOut->parse( wfMsg( 'imgmultipageprev' ), false ); |
| 266 | 260 | $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 ) ); |
| 269 | 263 | } else { |
| 270 | 264 | $thumb1 = ''; |
| 271 | 265 | } |
| 272 | 266 | |
| 273 | 267 | if ( $page < $count ) { |
| 274 | 268 | $label = wfMsg( 'imgmultipagenext' ); |
| 275 | | - $this->img->selectPage( $page + 1 ); |
| 276 | 269 | $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 ) ); |
| 278 | 272 | } else { |
| 279 | 273 | $thumb2 = ''; |
| 280 | 274 | } |
| — | — | @@ -294,7 +288,7 @@ |
| 295 | 289 | htmlspecialchars( wfMsg( 'imgmultigo' ) ) . '"></form>'; |
| 296 | 290 | |
| 297 | 291 | $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>" ); |
| 299 | 293 | } |
| 300 | 294 | } else { |
| 301 | 295 | #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 @@ |
| 430 | 430 | } |
| 431 | 431 | |
| 432 | 432 | /** @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 = '' ) |
| 435 | 435 | { |
| 436 | | - global $wgContLang, $wgUser, $wgThumbLimits, $wgGenerateThumbnailOnParse; |
| | 436 | + global $wgContLang, $wgUser, $wgThumbLimits; |
| 437 | 437 | |
| 438 | 438 | $img = new Image( $nt ); |
| 439 | 439 | |
| 440 | | - if ( ! is_null( $page ) ) { |
| 441 | | - $img->selectPage( $page ); |
| 442 | | - } |
| 443 | | - |
| 444 | 440 | if ( !$img->allowInlineDisplay() && $img->exists() ) { |
| 445 | 441 | return $this->makeKnownLinkObj( $nt ); |
| 446 | 442 | } |
| 447 | 443 | |
| 448 | | - $url = $img->getViewURL(); |
| 449 | 444 | $error = $prefix = $postfix = ''; |
| | 445 | + $page = isset( $params['page'] ) ? $params['page'] : false; |
| 450 | 446 | |
| 451 | | - wfDebug( "makeImageLinkObj: '$width'x'$height', \"$label\"\n" ); |
| 452 | | - |
| 453 | 447 | if ( 'center' == $align ) |
| 454 | 448 | { |
| 455 | 449 | $prefix = '<div class="center">'; |
| — | — | @@ -456,6 +450,16 @@ |
| 457 | 451 | $align = 'none'; |
| 458 | 452 | } |
| 459 | 453 | |
| | 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 | + |
| 460 | 464 | if ( $thumb || $framed ) { |
| 461 | 465 | |
| 462 | 466 | # Create a thumbnail. Alignment depends on language |
| — | — | @@ -468,73 +472,39 @@ |
| 469 | 473 | if ( $align == '' ) { |
| 470 | 474 | $align = $wgContLang->isRTL() ? 'left' : 'right'; |
| 471 | 475 | } |
| | 476 | + return $prefix.$this->makeThumbLinkObj( $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix; |
| | 477 | + } |
| 472 | 478 | |
| 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; |
| 485 | 484 | } |
| 486 | 485 | |
| 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 ); |
| 517 | 488 | } else { |
| 518 | | - $width = $img->width; |
| 519 | | - $height = $img->height; |
| | 489 | + $query = ''; |
| 520 | 490 | } |
| | 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 | + ); |
| 521 | 504 | |
| 522 | | - wfDebug( "makeImageLinkObj2: '$width'x'$height'\n" ); |
| 523 | | - $u = $nt->escapeLocalURL(); |
| 524 | | - if ( $error ) { |
| 525 | | - $s = $error; |
| 526 | | - } elseif ( $url == '' ) { |
| | 505 | + if ( !$thumb ) { |
| 527 | 506 | $s = $this->makeBrokenImageLinkObj( $img->getTitle() ); |
| 528 | | - //$s .= "<br />{$alt}<br />{$url}<br />\n"; |
| 529 | 507 | } 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 ); |
| 539 | 509 | } |
| 540 | 510 | if ( '' != $align ) { |
| 541 | 511 | $s = "<div class=\"float{$align}\"><span>{$s}</span></div>"; |
| — | — | @@ -546,86 +516,64 @@ |
| 547 | 517 | * Make HTML for a thumbnail including image, border and caption |
| 548 | 518 | * $img is an Image object |
| 549 | 519 | */ |
| 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; |
| 552 | 522 | $thumbUrl = ''; |
| 553 | 523 | $error = ''; |
| 554 | 524 | |
| 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; |
| 559 | 529 | } |
| 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 ) { |
| 567 | 539 | // Use image dimensions, don't scale |
| 568 | | - $boxwidth = $width; |
| 569 | | - $boxheight = $height; |
| 570 | | - $thumbUrl = $img->getViewURL(); |
| | 540 | + $thumb = $img->getUnscaledThumb( $page ); |
| 571 | 541 | } 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 ); |
| 584 | 543 | } |
| 585 | | - $oboxwidth = $boxwidth + 2; |
| 586 | 544 | |
| 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; |
| 602 | 549 | } |
| 603 | 550 | |
| 604 | | - $u = $img->getEscapeLocalURL(); |
| | 551 | + $query = $page ? 'page=' . urlencode( $page ) : ''; |
| | 552 | + $u = $img->getTitle()->getLocalURL( $query ); |
| 605 | 553 | |
| 606 | 554 | $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) ); |
| 607 | 555 | $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right'; |
| 608 | 556 | $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : ''; |
| 609 | 557 | |
| 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', '' ) ); |
| 621 | 561 | $zoomicon = ''; |
| 622 | 562 | } elseif( !$img->exists() ) { |
| 623 | 563 | $s .= $this->makeBrokenImageLinkObj( $img->getTitle() ); |
| 624 | 564 | $zoomicon = ''; |
| 625 | 565 | } 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 ); |
| 630 | 578 | if ( $framed ) { |
| 631 | 579 | $zoomicon=""; |
| 632 | 580 | } else { |
| Index: trunk/phase3/includes/Exif.php |
| — | — | @@ -93,9 +93,9 @@ |
| 94 | 94 | var $basename; |
| 95 | 95 | |
| 96 | 96 | /** |
| 97 | | - * The private log to log to |
| | 97 | + * The private log to log to, e.g. 'exif' |
| 98 | 98 | */ |
| 99 | | - var $log = 'exif'; |
| | 99 | + var $log = false; |
| 100 | 100 | |
| 101 | 101 | //@} |
| 102 | 102 | |
| — | — | @@ -561,7 +561,10 @@ |
| 562 | 562 | * @param $fname String: |
| 563 | 563 | * @param $action Mixed: , default NULL. |
| 564 | 564 | */ |
| 565 | | - function debug( $in, $fname, $action = NULL ) { |
| | 565 | + function debug( $in, $fname, $action = NULL ) { |
| | 566 | + if ( !$this->log ) { |
| | 567 | + return; |
| | 568 | + } |
| 566 | 569 | $type = gettype( $in ); |
| 567 | 570 | $class = ucfirst( __CLASS__ ); |
| 568 | 571 | if ( $type === 'array' ) |
| — | — | @@ -586,6 +589,9 @@ |
| 587 | 590 | * @param $io Boolean: Specify whether we're beginning or ending |
| 588 | 591 | */ |
| 589 | 592 | function debugFile( $fname, $io ) { |
| | 593 | + if ( !$this->log ) { |
| | 594 | + return; |
| | 595 | + } |
| 590 | 596 | $class = ucfirst( __CLASS__ ); |
| 591 | 597 | if ( $io ) { |
| 592 | 598 | wfDebugLog( $this->log, "$class::$fname: begin processing: '{$this->basename}'\n" ); |
| Index: trunk/phase3/includes/Parser.php |
| — | — | @@ -4387,8 +4387,8 @@ |
| 4388 | 4388 | * Parse image options text and use it to make an image |
| 4389 | 4389 | */ |
| 4390 | 4390 | function makeImage( $nt, $options ) { |
| 4391 | | - global $wgUseImageResize, $wgDjvuRenderer; |
| 4392 | | - |
| | 4391 | + # @TODO: let the MediaHandler specify its transform parameters |
| | 4392 | + # |
| 4393 | 4393 | # Check if the options text is of the form "options|alt text" |
| 4394 | 4394 | # Options are: |
| 4395 | 4395 | # * thumbnail make a thumbnail with enlarge-icon and caption, alignment depends on lang |
| — | — | @@ -4408,6 +4408,7 @@ |
| 4409 | 4409 | # * bottom |
| 4410 | 4410 | # * text-bottom |
| 4411 | 4411 | |
| | 4412 | + |
| 4412 | 4413 | $part = array_map( 'trim', explode( '|', $options) ); |
| 4413 | 4414 | |
| 4414 | 4415 | $mwAlign = array(); |
| — | — | @@ -4422,13 +4423,14 @@ |
| 4423 | 4424 | $mwPage =& MagicWord::get( 'img_page' ); |
| 4424 | 4425 | $caption = ''; |
| 4425 | 4426 | |
| 4426 | | - $width = $height = $framed = $thumb = false; |
| 4427 | | - $page = null; |
| | 4427 | + $params = array(); |
| | 4428 | + $framed = $thumb = false; |
| 4428 | 4429 | $manual_thumb = '' ; |
| 4429 | 4430 | $align = $valign = ''; |
| | 4431 | + $sk = $this->mOptions->getSkin(); |
| 4430 | 4432 | |
| 4431 | 4433 | foreach( $part as $val ) { |
| 4432 | | - if ( $wgUseImageResize && ! is_null( $mwThumb->matchVariableStartToEnd($val) ) ) { |
| | 4434 | + if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) { |
| 4433 | 4435 | $thumb=true; |
| 4434 | 4436 | } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) { |
| 4435 | 4437 | # use manually specified thumbnail |
| — | — | @@ -4446,19 +4448,18 @@ |
| 4447 | 4449 | continue 2; |
| 4448 | 4450 | } |
| 4449 | 4451 | } |
| 4450 | | - if ( isset( $wgDjvuRenderer ) && $wgDjvuRenderer |
| 4451 | | - && ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) { |
| | 4452 | + if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) { |
| 4452 | 4453 | # 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) ) ) { |
| 4455 | 4456 | wfDebug( "img_width match: $match\n" ); |
| 4456 | 4457 | # $match is the image width in pixels |
| 4457 | 4458 | $m = array(); |
| 4458 | 4459 | 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] ); |
| 4461 | 4462 | } else { |
| 4462 | | - $width = intval($match); |
| | 4463 | + $params['width'] = intval($match); |
| 4463 | 4464 | } |
| 4464 | 4465 | } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) { |
| 4465 | 4466 | $framed=true; |
| — | — | @@ -4477,8 +4478,7 @@ |
| 4478 | 4479 | $alt = Sanitizer::stripAllTags( $alt ); |
| 4479 | 4480 | |
| 4480 | 4481 | # 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 ); |
| 4483 | 4483 | } |
| 4484 | 4484 | |
| 4485 | 4485 | /** |
| 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 |
| 1 | 228 | + 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 |
| 1 | 294 | + 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 |
| 1 | 96 | + 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 |
| 1 | 205 | + native |
| Index: trunk/phase3/includes/AutoLoader.php |
| — | — | @@ -8,6 +8,7 @@ |
| 9 | 9 | global $wgAutoloadClasses; |
| 10 | 10 | |
| 11 | 11 | static $localClasses = array( |
| | 12 | + # Includes |
| 12 | 13 | 'AjaxDispatcher' => 'includes/AjaxDispatcher.php', |
| 13 | 14 | 'AjaxCachePolicy' => 'includes/AjaxFunctions.php', |
| 14 | 15 | 'AjaxResponse' => 'includes/AjaxResponse.php', |
| — | — | @@ -115,6 +116,10 @@ |
| 116 | 117 | 'MacBinary' => 'includes/MacBinary.php', |
| 117 | 118 | 'MagicWord' => 'includes/MagicWord.php', |
| 118 | 119 | 'MathRenderer' => 'includes/Math.php', |
| | 120 | + 'MediaTransformOutput' => 'includes/MediaTransformOutput.php', |
| | 121 | + 'ThumbnailImage' => 'includes/MediaTransformOutput.php', |
| | 122 | + 'MediaTransformError' => 'includes/MediaTransformOutput.php', |
| | 123 | + 'TransformParameterError' => 'includes/MediaTransformOutput.php', |
| 119 | 124 | 'MessageCache' => 'includes/MessageCache.php', |
| 120 | 125 | 'MimeMagic' => 'includes/MimeMagic.php', |
| 121 | 126 | 'Namespace' => 'includes/Namespace.php', |
| — | — | @@ -128,6 +133,7 @@ |
| 129 | 134 | 'ParserOutput' => 'includes/ParserOutput.php', |
| 130 | 135 | 'ParserOptions' => 'includes/ParserOptions.php', |
| 131 | 136 | 'ParserCache' => 'includes/ParserCache.php', |
| | 137 | + 'PatrolLog' => 'includes/PatrolLog.php', |
| 132 | 138 | 'ProfilerSimple' => 'includes/ProfilerSimple.php', |
| 133 | 139 | 'ProfilerSimpleUDP' => 'includes/ProfilerSimpleUDP.php', |
| 134 | 140 | 'Profiler' => 'includes/Profiler.php', |
| — | — | @@ -196,6 +202,7 @@ |
| 197 | 203 | 'PopularPagesPage' => 'includes/SpecialPopularpages.php', |
| 198 | 204 | 'PreferencesForm' => 'includes/SpecialPreferences.php', |
| 199 | 205 | 'SpecialPrefixindex' => 'includes/SpecialPrefixindex.php', |
| | 206 | + 'PasswordResetForm' => 'includes/SpecialResetpass.php', |
| 200 | 207 | 'RevisionDeleteForm' => 'includes/SpecialRevisiondelete.php', |
| 201 | 208 | 'RevisionDeleter' => 'includes/SpecialRevisiondelete.php', |
| 202 | 209 | 'SpecialSearch' => 'includes/SpecialSearch.php', |
| — | — | @@ -240,15 +247,26 @@ |
| 241 | 248 | 'Xml' => 'includes/Xml.php', |
| 242 | 249 | 'ZhClient' => 'includes/ZhClient.php', |
| 243 | 250 | '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 |
| 244 | 260 | 'UtfNormal' => 'includes/normal/UtfNormal.php', |
| | 261 | + |
| | 262 | + # Templates |
| 245 | 263 | 'UsercreateTemplate' => 'includes/templates/Userlogin.php', |
| 246 | 264 | 'UserloginTemplate' => 'includes/templates/Userlogin.php', |
| | 265 | + |
| | 266 | + # Languages |
| 247 | 267 | 'Language' => 'languages/Language.php', |
| 248 | | - 'PasswordResetForm' => 'includes/SpecialResetpass.php', |
| 249 | | - 'PatrolLog' => 'includes/PatrolLog.php', |
| 250 | 268 | 'RandomPage' => 'includes/SpecialRandompage.php', |
| 251 | 269 | |
| 252 | | - // API classes |
| | 270 | + # API |
| 253 | 271 | 'ApiBase' => 'includes/api/ApiBase.php', |
| 254 | 272 | 'ApiFormatFeedWrapper' => 'includes/api/ApiFormatBase.php', |
| 255 | 273 | 'ApiFeedWatchlist' => 'includes/api/ApiFeedWatchlist.php', |
| Index: trunk/phase3/includes/Image.php |
| — | — | @@ -27,7 +27,8 @@ |
| 28 | 28 | const DELETED_FILE = 1; |
| 29 | 29 | const DELETED_COMMENT = 2; |
| 30 | 30 | const DELETED_USER = 4; |
| 31 | | - const DELETED_RESTRICTED = 8; |
| | 31 | + const DELETED_RESTRICTED = 8; |
| | 32 | + const RENDER_NOW = 1; |
| 32 | 33 | |
| 33 | 34 | /**#@+ |
| 34 | 35 | * @private |
| — | — | @@ -85,13 +86,12 @@ |
| 86 | 87 | } |
| 87 | 88 | $this->title =& $title; |
| 88 | 89 | $this->name = $title->getDBkey(); |
| 89 | | - $this->metadata = serialize ( array() ) ; |
| | 90 | + $this->metadata = ''; |
| 90 | 91 | |
| 91 | 92 | $n = strrpos( $this->name, '.' ); |
| 92 | 93 | $this->extension = Image::normalizeExtension( $n ? |
| 93 | 94 | substr( $this->name, $n + 1 ) : '' ); |
| 94 | 95 | $this->historyLine = 0; |
| 95 | | - $this->page = 1; |
| 96 | 96 | |
| 97 | 97 | $this->dataLoaded = false; |
| 98 | 98 | } |
| — | — | @@ -244,7 +244,6 @@ |
| 245 | 245 | $this->fileExists = file_exists( $this->imagePath ); |
| 246 | 246 | $this->fromSharedDirectory = false; |
| 247 | 247 | $gis = array(); |
| 248 | | - $deja = false; |
| 249 | 248 | |
| 250 | 249 | if (!$this->fileExists) wfDebug(__METHOD__.': '.$this->imagePath." not found locally!\n"); |
| 251 | 250 | |
| — | — | @@ -268,18 +267,26 @@ |
| 269 | 268 | |
| 270 | 269 | $this->mime = $magic->guessMimeType($this->imagePath,true); |
| 271 | 270 | $this->type = $magic->getMediaType($this->imagePath,$this->mime); |
| | 271 | + $handler = MediaHandler::getHandler( $this->mime ); |
| 272 | 272 | |
| 273 | 273 | # Get size in bytes |
| 274 | 274 | $this->size = filesize( $this->imagePath ); |
| 275 | 275 | |
| 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 | + } |
| 278 | 284 | |
| 279 | 285 | wfDebug(__METHOD__.': '.$this->imagePath." loaded, ".$this->size." bytes, ".$this->mime.".\n"); |
| 280 | 286 | } |
| 281 | 287 | else { |
| 282 | 288 | $this->mime = NULL; |
| 283 | 289 | $this->type = MEDIATYPE_UNKNOWN; |
| | 290 | + $this->metadata = ''; |
| 284 | 291 | wfDebug(__METHOD__.': '.$this->imagePath." NOT FOUND!\n"); |
| 285 | 292 | } |
| 286 | 293 | |
| — | — | @@ -298,13 +305,6 @@ |
| 299 | 306 | # as ther's only one thread of execution, this should be safe anyway. |
| 300 | 307 | $this->dataLoaded = true; |
| 301 | 308 | |
| 302 | | - |
| 303 | | - if ( $deja ) { |
| 304 | | - $this->metadata = $deja->retrieveMetaData(); |
| 305 | | - } else { |
| 306 | | - $this->metadata = serialize( $this->retrieveExifData( $this->imagePath ) ); |
| 307 | | - } |
| 308 | | - |
| 309 | 309 | if ( isset( $gis['bits'] ) ) $this->bits = $gis['bits']; |
| 310 | 310 | else $this->bits = 0; |
| 311 | 311 | |
| — | — | @@ -331,9 +331,7 @@ |
| 332 | 332 | $this->loadFromRow( $row ); |
| 333 | 333 | $this->imagePath = $this->getFullPath(); |
| 334 | 334 | // Check for rows from a previous schema, quietly upgrade them |
| 335 | | - if ( is_null($this->type) ) { |
| 336 | | - $this->upgradeRow(); |
| 337 | | - } |
| | 335 | + $this->maybeUpgradeRow(); |
| 338 | 336 | } elseif ( $wgUseSharedUploads && $wgSharedUploadDBname ) { |
| 339 | 337 | # In case we're on a wgCapitalLinks=false wiki, we |
| 340 | 338 | # capitalize the first letter of the filename before |
| — | — | @@ -354,9 +352,7 @@ |
| 355 | 353 | $this->loadFromRow( $row ); |
| 356 | 354 | |
| 357 | 355 | // Check for rows from a previous schema, quietly upgrade them |
| 358 | | - if ( is_null($this->type) ) { |
| 359 | | - $this->upgradeRow(); |
| 360 | | - } |
| | 356 | + $this->maybeUpgradeRow(); |
| 361 | 357 | } |
| 362 | 358 | } |
| 363 | 359 | |
| — | — | @@ -368,7 +364,7 @@ |
| 369 | 365 | $this->type = 0; |
| 370 | 366 | $this->fileExists = false; |
| 371 | 367 | $this->fromSharedDirectory = false; |
| 372 | | - $this->metadata = serialize ( array() ) ; |
| | 368 | + $this->metadata = ''; |
| 373 | 369 | $this->mime = false; |
| 374 | 370 | } |
| 375 | 371 | |
| — | — | @@ -395,9 +391,7 @@ |
| 396 | 392 | if (!$minor) $minor= "unknown"; |
| 397 | 393 | $this->mime = $major.'/'.$minor; |
| 398 | 394 | } |
| 399 | | - |
| 400 | 395 | $this->metadata = $row->img_metadata; |
| 401 | | - if ( $this->metadata == "" ) $this->metadata = serialize ( array() ) ; |
| 402 | 396 | |
| 403 | 397 | $this->dataLoaded = true; |
| 404 | 398 | } |
| — | — | @@ -424,9 +418,22 @@ |
| 425 | 419 | } |
| 426 | 420 | |
| 427 | 421 | /** |
| 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 |
| 430 | 423 | */ |
| | 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 | + */ |
| 431 | 438 | function upgradeRow() { |
| 432 | 439 | global $wgDBname, $wgSharedUploadDBname; |
| 433 | 440 | wfProfileIn( __METHOD__ ); |
| — | — | @@ -450,7 +457,7 @@ |
| 451 | 458 | |
| 452 | 459 | list( $major, $minor ) = self::splitMime( $this->mime ); |
| 453 | 460 | |
| 454 | | - wfDebug(__METHOD__.': upgrading '.$this->name." to 1.5 schema\n"); |
| | 461 | + wfDebug(__METHOD__.': upgrading '.$this->name." to the current schema\n"); |
| 455 | 462 | |
| 456 | 463 | $dbw->update( 'image', |
| 457 | 464 | array( |
| — | — | @@ -543,26 +550,52 @@ |
| 544 | 551 | /** |
| 545 | 552 | * Return the width of the image |
| 546 | 553 | * |
| 547 | | - * Returns -1 if the file specified is not a known image type |
| | 554 | + * Returns false on error |
| 548 | 555 | * @public |
| 549 | 556 | */ |
| 550 | | - function getWidth() { |
| | 557 | + function getWidth( $page = 1 ) { |
| 551 | 558 | $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 | + } |
| 553 | 569 | } |
| 554 | 570 | |
| 555 | 571 | /** |
| 556 | 572 | * Return the height of the image |
| 557 | 573 | * |
| 558 | | - * Returns -1 if the file specified is not a known image type |
| | 574 | + * Returns false on error |
| 559 | 575 | * @public |
| 560 | 576 | */ |
| 561 | | - function getHeight() { |
| | 577 | + function getHeight( $page = 1 ) { |
| 562 | 578 | $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 | + } |
| 564 | 589 | } |
| 565 | 590 | |
| 566 | 591 | /** |
| | 592 | + * Get handler-specific metadata |
| | 593 | + */ |
| | 594 | + function getMetadata() { |
| | 595 | + $this->load(); |
| | 596 | + return $this->metadata; |
| | 597 | + } |
| | 598 | + |
| | 599 | + /** |
| 567 | 600 | * Return the size of the image file, in bytes |
| 568 | 601 | * @public |
| 569 | 602 | */ |
| — | — | @@ -599,58 +632,10 @@ |
| 600 | 633 | * @todo remember the result of this check. |
| 601 | 634 | */ |
| 602 | 635 | 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(); |
| 652 | 638 | } |
| 653 | 639 | |
| 654 | | - |
| 655 | 640 | /** |
| 656 | 641 | * Return true if the file is of a type that can't be directly |
| 657 | 642 | * rendered by typical browsers and needs to be re-rasterized. |
| — | — | @@ -662,13 +647,8 @@ |
| 663 | 648 | * @return bool |
| 664 | 649 | */ |
| 665 | 650 | 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(); |
| 673 | 653 | } |
| 674 | 654 | |
| 675 | 655 | /** |
| — | — | @@ -734,15 +714,7 @@ |
| 735 | 715 | * @public |
| 736 | 716 | */ |
| 737 | 717 | 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 ); |
| 747 | 719 | } |
| 748 | 720 | |
| 749 | 721 | /** |
| — | — | @@ -790,77 +762,72 @@ |
| 791 | 763 | * @todo document |
| 792 | 764 | * @private |
| 793 | 765 | */ |
| 794 | | - function thumbUrl( $width, $subdir='thumb') { |
| | 766 | + function thumbUrl( $thumbName ) { |
| 795 | 767 | 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 | + } |
| 797 | 785 | |
| 798 | | - // Generate thumb.php URL if possible |
| 799 | | - $script = false; |
| 800 | | - $url = false; |
| 801 | | - |
| | 786 | + function getTransformScript() { |
| | 787 | + global $wgSharedThumbnailScriptPath, $wgThumbnailScriptPath; |
| 802 | 788 | if ( $this->fromSharedDirectory ) { |
| 803 | | - if ( $wgSharedThumbnailScriptPath ) { |
| 804 | | - $script = $wgSharedThumbnailScriptPath; |
| 805 | | - } |
| | 789 | + $script = $wgSharedThumbnailScriptPath; |
| 806 | 790 | } else { |
| 807 | | - if ( $wgThumbnailScriptPath ) { |
| 808 | | - $script = $wgThumbnailScriptPath; |
| 809 | | - } |
| | 791 | + $script = $wgThumbnailScriptPath; |
| 810 | 792 | } |
| 811 | 793 | 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 ); |
| 816 | 795 | } 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; |
| 833 | 797 | } |
| 834 | | - return array( $script !== false, $url ); |
| 835 | 798 | } |
| 836 | 799 | |
| 837 | 800 | /** |
| 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 |
| 839 | 817 | * |
| 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 |
| 842 | 819 | * @private |
| 843 | 820 | */ |
| 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; |
| 849 | 825 | } |
| 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"; |
| 863 | 830 | } |
| 864 | | - return $thumb; |
| | 831 | + return $thumbName; |
| 865 | 832 | } |
| 866 | 833 | |
| 867 | 834 | /** |
| — | — | @@ -879,9 +846,13 @@ |
| 880 | 847 | * @param integer $height maximum height of the image (optional) |
| 881 | 848 | * @public |
| 882 | 849 | */ |
| 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 ''; |
| 886 | 857 | return $thumb->getUrl(); |
| 887 | 858 | } |
| 888 | 859 | |
| — | — | @@ -899,149 +870,89 @@ |
| 900 | 871 | * |
| 901 | 872 | * @return ThumbnailImage or null on failure |
| 902 | 873 | * @public |
| | 874 | + * |
| | 875 | + * @deprecated use transform() |
| 903 | 876 | */ |
| 904 | 877 | 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; |
| 931 | 881 | } |
| 932 | | - wfProfileOut( __METHOD__ ); |
| 933 | | - return $thumb; |
| | 882 | + $flags = $render ? self::RENDER_NOW : 0; |
| | 883 | + return $this->transform( $params, $flags ); |
| 934 | 884 | } |
| 935 | | - |
| | 885 | + |
| 936 | 886 | /** |
| 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 |
| 938 | 893 | */ |
| 939 | | - function iconThumb() { |
| 940 | | - global $wgStylePath, $wgStyleDirectory; |
| | 894 | + function transform( $params, $flags = 0 ) { |
| | 895 | + global $wgGenerateThumbnailOnParse, $wgUseSquid, $wgIgnoreImageErrors; |
| 941 | 896 | |
| 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; |
| 948 | 904 | } |
| 949 | | - } |
| 950 | | - return null; |
| 951 | | - } |
| 952 | 905 | |
| 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 | + } |
| 962 | 912 | |
| 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 ); |
| 964 | 918 | |
| 965 | | - if ( ! $this->exists() ) |
| 966 | | - { |
| 967 | | - # If there is no image, there will be no thumbnail |
| 968 | | - return false; |
| 969 | | - } |
| | 919 | + $this->migrateThumbFile( $thumbName ); |
| 970 | 920 | |
| 971 | | - $width = intval( $width ); |
| | 921 | + if ( file_exists( $thumbPath ) ) { |
| | 922 | + $thumb = $handler->getTransform( $this, $thumbPath, $thumbUrl, $params ); |
| | 923 | + break; |
| | 924 | + } |
| 972 | 925 | |
| 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 ); |
| 978 | 931 | |
| 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); |
| 988 | 946 | |
| 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; |
| 1000 | 949 | } |
| 1001 | 950 | |
| 1002 | 951 | /** |
| 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 |
| 1013 | 953 | */ |
| 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 ) { |
| 1043 | 955 | $thumbDir = wfImageThumbDir( $this->name, $this->fromSharedDirectory ); |
| 1044 | | - $thumbPath = $thumbDir.'/'.$thumbName; |
| 1045 | | - |
| | 956 | + $thumbPath = "$thumbDir/$thumbName"; |
| 1046 | 957 | if ( is_dir( $thumbPath ) ) { |
| 1047 | 958 | // Directory where file should be |
| 1048 | 959 | // This happened occasionally due to broken migration code in 1.5 |
| — | — | @@ -1054,247 +965,50 @@ |
| 1055 | 966 | break; |
| 1056 | 967 | } |
| 1057 | 968 | } |
| 1058 | | - // Code below will ask if it exists, and the answer is now no |
| | 969 | + // Doesn't exist anymore |
| 1059 | 970 | clearstatcache(); |
| 1060 | 971 | } |
| 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(); |
| 1115 | 977 | } |
| | 978 | + } |
| 1116 | 979 | |
| 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 | + } |
| 1125 | 986 | |
| 1126 | 987 | /** |
| 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 |
| 1137 | 990 | */ |
| 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; |
| 1143 | 993 | |
| 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 ); |
| 1166 | 1000 | } |
| 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 ); |
| 1263 | 1001 | } |
| 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; |
| 1288 | 1003 | } |
| 1289 | 1004 | |
| | 1005 | + /** |
| | 1006 | + * Get last thumbnailing error. |
| | 1007 | + * Largely obsolete. |
| | 1008 | + */ |
| 1290 | 1009 | function getLastError() { |
| 1291 | 1010 | return $this->lastError; |
| 1292 | 1011 | } |
| 1293 | 1012 | |
| 1294 | | - static function imageJpegWrapper( $dst_image, $thumbPath ) { |
| 1295 | | - imageinterlace( $dst_image ); |
| 1296 | | - imagejpeg( $dst_image, $thumbPath, 95 ); |
| 1297 | | - } |
| 1298 | | - |
| 1299 | 1013 | /** |
| 1300 | 1014 | * Get all thumbnail names previously generated for this image |
| 1301 | 1015 | */ |
| — | — | @@ -1328,7 +1042,7 @@ |
| 1329 | 1043 | */ |
| 1330 | 1044 | function purgeMetadataCache() { |
| 1331 | 1045 | clearstatcache(); |
| 1332 | | - $this->loadFromFile(); |
| | 1046 | + $this->upgradeRow(); |
| 1333 | 1047 | $this->saveToCache(); |
| 1334 | 1048 | } |
| 1335 | 1049 | |
| — | — | @@ -1348,7 +1062,7 @@ |
| 1349 | 1063 | foreach ( $files as $file ) { |
| 1350 | 1064 | $m = array(); |
| 1351 | 1065 | if ( preg_match( '/^(\d+)px/', $file, $m ) ) { |
| 1352 | | - list( /* $isScriptUrl */, $url ) = $this->thumbUrl( $m[1] ); |
| | 1066 | + $url = $this->thumbUrl( $m[1] ); |
| 1353 | 1067 | $urls[] = $url; |
| 1354 | 1068 | @unlink( "$dir/$file" ); |
| 1355 | 1069 | } |
| — | — | @@ -1391,6 +1105,9 @@ |
| 1392 | 1106 | $update->doUpdate(); |
| 1393 | 1107 | } |
| 1394 | 1108 | |
| | 1109 | + /** |
| | 1110 | + * Check the image table schema on the given connection for subtle problems |
| | 1111 | + */ |
| 1395 | 1112 | function checkDBSchema(&$db) { |
| 1396 | 1113 | static $checkDone = false; |
| 1397 | 1114 | global $wgCheckDBSchema; |
| — | — | @@ -1711,76 +1428,24 @@ |
| 1712 | 1429 | return $retVal; |
| 1713 | 1430 | } |
| 1714 | 1431 | |
| 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 | | - |
| 1738 | 1432 | function getExifData() { |
| 1739 | 1433 | global $wgRequest; |
| 1740 | | - if ( $this->metadata === '0' || $this->mime == 'image/vnd.djvu' ) |
| | 1434 | + $handler = $this->getHandler(); |
| | 1435 | + if ( !$handler || $handler->getMetadataType( $this ) != 'exif' ) { |
| 1741 | 1436 | 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 ); |
| 1752 | 1437 | } |
| 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 ); |
| 1756 | 1446 | |
| 1757 | 1447 | return $format->getFormattedData(); |
| 1758 | 1448 | } |
| 1759 | 1449 | |
| 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 | | - |
| 1785 | 1450 | /** |
| 1786 | 1451 | * Returns true if the image does not come from the shared |
| 1787 | 1452 | * image repository. |
| — | — | @@ -2145,12 +1810,17 @@ |
| 2146 | 1811 | // an archived file revision. |
| 2147 | 1812 | if( is_null( $row->fa_metadata ) ) { |
| 2148 | 1813 | $tempFile = $store->filePath( $row->fa_storage_key ); |
| 2149 | | - $metadata = serialize( $this->retrieveExifData( $tempFile ) ); |
| 2150 | 1814 | |
| 2151 | 1815 | $magic = MimeMagic::singleton(); |
| 2152 | 1816 | $mime = $magic->guessMimeType( $tempFile, true ); |
| 2153 | 1817 | $media_type = $magic->getMediaType( $tempFile, $mime ); |
| 2154 | 1818 | 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 | + } |
| 2155 | 1825 | } else { |
| 2156 | 1826 | $metadata = $row->fa_metadata; |
| 2157 | 1827 | $major_mime = $row->fa_major_mime; |
| — | — | @@ -2258,71 +1928,14 @@ |
| 2259 | 1929 | } |
| 2260 | 1930 | |
| 2261 | 1931 | /** |
| 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 | | - /** |
| 2320 | 1932 | * Returns 'true' if this image is a multipage document, e.g. a DJVU |
| 2321 | 1933 | * document. |
| 2322 | 1934 | * |
| 2323 | 1935 | * @return Bool |
| 2324 | 1936 | */ |
| 2325 | 1937 | function isMultipage() { |
| 2326 | | - return ( $this->mime == 'image/vnd.djvu' ); |
| | 1938 | + $handler = $this->getHandler(); |
| | 1939 | + return $handler && $handler->isMultiPage(); |
| 2327 | 1940 | } |
| 2328 | 1941 | |
| 2329 | 1942 | /** |
| — | — | @@ -2330,13 +1943,10 @@ |
| 2331 | 1944 | * documents which aren't multipage documents |
| 2332 | 1945 | */ |
| 2333 | 1946 | 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 ); |
| 2339 | 1950 | } else { |
| 2340 | | - wfDebug( "Requested pageCount() for bogus multi-page metadata for '$this->name'\n" ); |
| 2341 | 1951 | return null; |
| 2342 | 1952 | } |
| 2343 | 1953 | } |
| — | — | @@ -2358,7 +1968,11 @@ |
| 2359 | 1969 | */ |
| 2360 | 1970 | static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) { |
| 2361 | 1971 | // 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 | + } |
| 2363 | 1977 | } |
| 2364 | 1978 | |
| 2365 | 1979 | /** |
| — | — | @@ -2366,28 +1980,11 @@ |
| 2367 | 1981 | * can't be determined. |
| 2368 | 1982 | * |
| 2369 | 1983 | * @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 |
| 2372 | 1984 | * @return array |
| 2373 | 1985 | */ |
| 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 ); |
| 2392 | 1989 | } |
| 2393 | 1990 | |
| 2394 | 1991 | /** |
| — | — | @@ -2395,22 +1992,14 @@ |
| 2396 | 1993 | * @return array thumbnail extension and MIME type |
| 2397 | 1994 | */ |
| 2398 | 1995 | 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 ); |
| 2410 | 2001 | } |
| 2411 | | - return array( $ext, $mime ); |
| 2412 | 2002 | } |
| 2413 | 2003 | |
| 2414 | | - |
| 2415 | 2004 | } //class |
| 2416 | 2005 | |
| 2417 | 2006 | class ArchivedFile |
| — | — | @@ -2520,60 +2109,6 @@ |
| 2521 | 2110 | } |
| 2522 | 2111 | |
| 2523 | 2112 | /** |
| 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 | | -/** |
| 2578 | 2113 | * Aliases for backwards compatibility with 1.6 |
| 2579 | 2114 | */ |
| 2580 | 2115 | define( 'MW_IMG_DELETED_FILE', Image::DELETED_FILE ); |
| Index: trunk/phase3/includes/DefaultSettings.php |
| — | — | @@ -1443,9 +1443,19 @@ |
| 1444 | 1444 | # Images settings |
| 1445 | 1445 | # |
| 1446 | 1446 | |
| 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 | +); |
| 1449 | 1458 | |
| | 1459 | + |
| 1450 | 1460 | /** |
| 1451 | 1461 | * Resizing can be done using PHP's internal image libraries or using |
| 1452 | 1462 | * ImageMagick or another third-party converter, e.g. GraphicMagick. |
| — | — | @@ -1458,6 +1468,12 @@ |
| 1459 | 1469 | /** The convert command shipped with ImageMagick */ |
| 1460 | 1470 | $wgImageMagickConvertCommand = '/usr/bin/convert'; |
| 1461 | 1471 | |
| | 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 | + |
| 1462 | 1478 | /** |
| 1463 | 1479 | * Use another resizing converter, e.g. GraphicMagick |
| 1464 | 1480 | * %s will be replaced with the source path, %d with the destination |
| — | — | @@ -1523,6 +1539,10 @@ |
| 1524 | 1540 | */ |
| 1525 | 1541 | $wgGenerateThumbnailOnParse = true; |
| 1526 | 1542 | |
| | 1543 | +/** Obsolete, always true, kept for compatibility with extensions */ |
| | 1544 | +$wgUseImageResize = true; |
| | 1545 | + |
| | 1546 | + |
| 1527 | 1547 | /** Set $wgCommandLineMode if it's not set already, to avoid notices */ |
| 1528 | 1548 | if( !isset( $wgCommandLineMode ) ) { |
| 1529 | 1549 | $wgCommandLineMode = false; |
| — | — | @@ -2277,7 +2297,7 @@ |
| 2278 | 2298 | MEDIATYPE_BITMAP, //all bitmap formats |
| 2279 | 2299 | MEDIATYPE_AUDIO, //all audio formats |
| 2280 | 2300 | 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) |
| 2282 | 2302 | "application/pdf", //PDF files |
| 2283 | 2303 | #"application/x-shockwave-flash", //flash/shockwave movie |
| 2284 | 2304 | ); |
| — | — | @@ -2380,7 +2400,7 @@ |
| 2381 | 2401 | * MediaWiki will reject HTMLesque tags in uploaded files due to idiotic browsers which can't |
| 2382 | 2402 | * perform basic stuff like MIME detection and which are vulnerable to further idiots uploading |
| 2383 | 2403 | * 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 |
| 2385 | 2405 | * and doesn't send appropriate MIME types for SVG images. |
| 2386 | 2406 | */ |
| 2387 | 2407 | $wgAllowTitlesInSVG = false; |
| — | — | @@ -2406,20 +2426,33 @@ |
| 2407 | 2427 | |
| 2408 | 2428 | /** |
| 2409 | 2429 | * DJVU settings |
| 2410 | | - * Path of the djvutoxml executable |
| | 2430 | + * Path of the djvudump executable |
| 2411 | 2431 | * Enable this and $wgDjvuRenderer to enable djvu rendering |
| 2412 | 2432 | */ |
| 2413 | | -# $wgDjvuToXML = 'djvutoxml'; |
| 2414 | | -$wgDjvuToXML = null; |
| | 2433 | +# $wgDjvuDump = 'djvudump'; |
| | 2434 | +$wgDjvuDump = null; |
| 2415 | 2435 | |
| 2416 | 2436 | /** |
| 2417 | 2437 | * Path of the ddjvu DJVU renderer |
| 2418 | | - * Enable this and $wgDjvuToXML to enable djvu rendering |
| | 2438 | + * Enable this and $wgDjvuDump to enable djvu rendering |
| 2419 | 2439 | */ |
| 2420 | 2440 | # $wgDjvuRenderer = 'ddjvu'; |
| 2421 | 2441 | $wgDjvuRenderer = null; |
| 2422 | 2442 | |
| 2423 | 2443 | /** |
| | 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 | +/** |
| 2424 | 2457 | * Shell command for the DJVU post processor |
| 2425 | 2458 | * Default: pnmtopng, since ddjvu generates ppm output |
| 2426 | 2459 | * Set this to false to output the ppm file directly. |
| Index: trunk/phase3/includes/mime.info |
| — | — | @@ -19,7 +19,7 @@ |
| 20 | 20 | image/x-bmp image/bmp application/x-bmp application/bmp [BITMAP] |
| 21 | 21 | image/x-photoshop image/psd image/x-psd image/photoshop [BITMAP] |
| 22 | 22 | |
| 23 | | -image/svg image/svg+xml application/svg+xml application/svg [DRAWING] |
| | 23 | +image/svg+xml application/svg+xml application/svg image/svg [DRAWING] |
| 24 | 24 | application/postscript [DRAWING] |
| 25 | 25 | application/x-latex [DRAWING] |
| 26 | 26 | 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 |
| 1 | 160 | + native |
| Index: trunk/phase3/includes/ImageGallery.php |
| — | — | @@ -183,7 +183,7 @@ |
| 184 | 184 | * |
| 185 | 185 | */ |
| 186 | 186 | function toHTML() { |
| 187 | | - global $wgLang, $wgGenerateThumbnailOnParse; |
| | 187 | + global $wgLang; |
| 188 | 188 | |
| 189 | 189 | $sk = $this->getSkin(); |
| 190 | 190 | |
| — | — | @@ -191,6 +191,7 @@ |
| 192 | 192 | if( $this->mCaption ) |
| 193 | 193 | $s .= "\n\t<caption>{$this->mCaption}</caption>"; |
| 194 | 194 | |
| | 195 | + $params = array( 'width' => $this->mWidths, 'height' => $this->mHeights ); |
| 195 | 196 | $i = 0; |
| 196 | 197 | foreach ( $this->mImages as $pair ) { |
| 197 | 198 | $img =& $pair[0]; |
| — | — | @@ -206,7 +207,7 @@ |
| 207 | 208 | # The image is blacklisted, just show it as a text link. |
| 208 | 209 | $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">' |
| 209 | 210 | . $sk->makeKnownLinkObj( $nt, htmlspecialchars( $nt->getText() ) ) . '</div>'; |
| 210 | | - } elseif( !( $thumb = $img->getThumbnail( $this->mWidths, $this->mHeights, $wgGenerateThumbnailOnParse ) ) ) { |
| | 211 | + } elseif( !( $thumb = $img->transform( $params ) ) ) { |
| 211 | 212 | # Error generating thumbnail. |
| 212 | 213 | $thumbhtml = "\n\t\t\t".'<div style="height: '.($this->mHeights*1.25+2).'px;">' |
| 213 | 214 | . htmlspecialchars( $img->getLastError() ) . '</div>'; |
| Index: trunk/phase3/includes/DjVuImage.php |
| — | — | @@ -220,17 +220,121 @@ |
| 221 | 221 | * @return string |
| 222 | 222 | */ |
| 223 | 223 | 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' ); |
| 226 | 235 | $cmd = wfEscapeShellArg( $wgDjvuToXML ) . ' --without-anno --without-text ' . |
| 227 | 236 | wfEscapeShellArg( $this->mFilename ); |
| 228 | 237 | $xml = wfShellExec( $cmd ); |
| | 238 | + wfProfileOut( 'djvutoxml' ); |
| 229 | 239 | } else { |
| 230 | 240 | $xml = null; |
| 231 | 241 | } |
| 232 | 242 | return $xml; |
| 233 | 243 | } |
| 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 | + } |
| 235 | 339 | } |
| 236 | 340 | |
| 237 | 341 | |
| Index: trunk/phase3/config/index.php |
| — | — | @@ -483,8 +483,6 @@ |
| 484 | 484 | } |
| 485 | 485 | } |
| 486 | 486 | |
| 487 | | -$conf->UseImageResize = $conf->HaveGD || $conf->ImageMagick; |
| 488 | | - |
| 489 | 487 | $conf->IP = dirname( dirname( __FILE__ ) ); |
| 490 | 488 | print "<li>Installation directory: <tt>" . htmlspecialchars( $conf->IP ) . "</tt></li>\n"; |
| 491 | 489 | |
| — | — | @@ -1302,7 +1300,6 @@ |
| 1303 | 1301 | } |
| 1304 | 1302 | |
| 1305 | 1303 | function writeLocalSettings( $conf ) { |
| 1306 | | - $conf->UseImageResize = $conf->UseImageResize ? 'true' : 'false'; |
| 1307 | 1304 | $conf->PasswordSender = $conf->EmergencyContact; |
| 1308 | 1305 | $magic = ($conf->ImageMagick ? "" : "# "); |
| 1309 | 1306 | $convert = ($conf->ImageMagick ? $conf->ImageMagick : "/usr/bin/convert" ); |
| — | — | @@ -1448,7 +1445,6 @@ |
| 1449 | 1446 | ## To enable image uploads, make sure the 'images' directory |
| 1450 | 1447 | ## is writable, then set this to true: |
| 1451 | 1448 | \$wgEnableUploads = false; |
| 1452 | | -\$wgUseImageResize = {$conf->UseImageResize}; |
| 1453 | 1449 | {$magic}\$wgUseImageMagick = true; |
| 1454 | 1450 | {$magic}\$wgImageMagickConvertCommand = \"{$convert}\"; |
| 1455 | 1451 | |
| Index: trunk/phase3/thumb.php |
| — | — | @@ -9,46 +9,51 @@ |
| 10 | 10 | require_once( './includes/WebStart.php' ); |
| 11 | 11 | wfProfileIn( 'thumb.php' ); |
| 12 | 12 | 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" ); |
| 15 | 15 | |
| 16 | 16 | $wgTrivialMimeDetection = true; //don't use fancy mime detection, just check the file extension for jpg/gif/png. |
| 17 | 17 | |
| 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" ); |
| 20 | 20 | |
| 21 | 21 | // 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 | | - |
| 26 | 22 | if ( get_magic_quotes_gpc() ) { |
| 27 | | - $fileName = stripslashes( $fileName ); |
| | 23 | + $params = array_map( 'stripslashes', $_REQUEST ); |
| | 24 | +} else { |
| | 25 | + $params = $_REQUEST; |
| 28 | 26 | } |
| 29 | 27 | |
| 30 | | -$pre_render= isset($_REQUEST['r']) && $_REQUEST['r']!="0"; |
| | 28 | +$fileName = isset( $params['f'] ) ? $params['f'] : ''; |
| | 29 | +unset( $params['f'] ); |
| 31 | 30 | |
| | 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 | + |
| 32 | 41 | // Some basic input validation |
| 33 | 42 | $fileName = strtr( $fileName, '\\/', '__' ); |
| 34 | 43 | |
| 35 | 44 | // 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; |
| 36 | 50 | |
| 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 | + } |
| 41 | 56 | } |
| 42 | | -if ( $pre_render ) { |
| 43 | | - $thumbName .= '.png'; |
| 44 | | -} |
| 45 | | -$thumbPath = wfImageThumbDir( $fileName ) . '/' . $thumbName; |
| 46 | 57 | |
| 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 | | - |
| 53 | 58 | // OK, no valid thumbnail, time to get out the heavy machinery |
| 54 | 59 | wfProfileOut( 'thumb.php-start' ); |
| 55 | 60 | require_once( './includes/Setup.php' ); |
| — | — | @@ -57,10 +62,7 @@ |
| 58 | 63 | $img = Image::newFromName( $fileName ); |
| 59 | 64 | try { |
| 60 | 65 | 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 ); |
| 65 | 67 | } else { |
| 66 | 68 | $thumb = false; |
| 67 | 69 | } |
| — | — | @@ -69,13 +71,33 @@ |
| 70 | 72 | $thumb = false; |
| 71 | 73 | } |
| 72 | 74 | |
| 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; |
| 75 | 96 | } else { |
| 76 | 97 | $badtitle = wfMsg( 'badtitle' ); |
| 77 | 98 | $badtitletext = wfMsg( 'badtitletext' ); |
| 78 | 99 | header( 'Cache-Control: no-cache' ); |
| 79 | 100 | header( 'Content-Type: text/html; charset=utf-8' ); |
| | 101 | + header( 'HTTP/1.1 500 Internal server error' ); |
| 80 | 102 | echo "<html><head> |
| 81 | 103 | <title>$badtitle</title> |
| 82 | 104 | <body> |
| — | — | @@ -89,4 +111,17 @@ |
| 90 | 112 | wfProfileOut( 'thumb.php' ); |
| 91 | 113 | wfLogProfilingData(); |
| 92 | 114 | |
| | 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 | + |
| 93 | 128 | ?> |
| Index: trunk/phase3/languages/messages/MessagesEn.php |
| — | — | @@ -2139,6 +2139,10 @@ |
| 2140 | 2140 | 'missingimage' => '<b>Missing image</b><br /><i>$1</i>', |
| 2141 | 2141 | 'filemissing' => 'File missing', |
| 2142 | 2142 | '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', |
| 2143 | 2147 | |
| 2144 | 2148 | # Special:Import |
| 2145 | 2149 | 'import' => 'Import pages', |
| — | — | @@ -2816,8 +2820,8 @@ |
| 2817 | 2821 | * Nederlands|nl", |
| 2818 | 2822 | |
| 2819 | 2823 | # Multipage image navigation |
| 2820 | | -'imgmultipageprev' => '← previous page', |
| 2821 | | -'imgmultipagenext' => 'next page →', |
| | 2824 | +'imgmultipageprev' => '← previous page', |
| | 2825 | +'imgmultipagenext' => 'next page →', |
| 2822 | 2826 | 'imgmultigo' => 'Go!', |
| 2823 | 2827 | 'imgmultigotopre' => 'Go to page', |
| 2824 | 2828 | 'imgmultigotopost' => '', |
| Index: trunk/phase3/RELEASE-NOTES |
| — | — | @@ -31,6 +31,7 @@ |
| 32 | 32 | * Added rate limiter for Special:Emailuser |
| 33 | 33 | * Private logs can now be created using $wgLogRestrictions |
| 34 | 34 | * (Bug 8590) limited HTML is now always enabled ($wgUserHtml = true). |
| | 35 | +* Deprecated $wgUseImageResize, thumbnailing will be enabled unconditionally. |
| 35 | 36 | |
| 36 | 37 | == New features since 1.9 == |
| 37 | 38 | |
| — | — | @@ -117,6 +118,7 @@ |
| 118 | 119 | * Introduce 'SearchUpdate' hook; see docs/hooks.txt for more information |
| 119 | 120 | * Introduce 'mywatchlist' message; used on personal menu to link to watchlist page |
| 120 | 121 | * Introduce magic word {{NUMBEROFEDITS}} |
| | 122 | +* Introduced media handlers for file-type specific operations. |
| 121 | 123 | |
| 122 | 124 | == Bugfixes since 1.9 == |
| 123 | 125 | |
| — | — | @@ -327,6 +329,8 @@ |
| 328 | 330 | a random page, and will give an error message if none really can be found |
| 329 | 331 | instead of sending the user to the main page like they used to |
| 330 | 332 | * 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 | +* |
| 331 | 335 | |
| 332 | 336 | == Maintenance == |
| 333 | 337 | |
| Index: trunk/extensions/WebStore/WebStoreClient.php |
| — | — | @@ -66,20 +66,29 @@ |
| 67 | 67 | /** |
| 68 | 68 | * Publish a file, return "archived" if the file existed already and was archived, |
| 69 | 69 | * "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. |
| 71 | 74 | */ |
| 72 | | - function publish( $srcRepo, $srcRel, $dstRel, $archiveRel ) { |
| | 75 | + function publish( $srcZone, $srcRel, $dstRel, $archiveRel ) { |
| 73 | 76 | global $wgServer, $wgScriptPath; |
| 74 | 77 | |
| 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'; |
| 79 | 88 | } |
| 80 | 89 | |
| 81 | 90 | $content = $this->post( $this->getURL( 'publish.php' ), |
| 82 | 91 | array( |
| 83 | | - 'srcRepo' => $srcRepo, |
| | 92 | + 'srcZone' => $srcZone, |
| 84 | 93 | 'src' => $srcRel, |
| 85 | 94 | 'dst' => $dstRel, |
| 86 | 95 | 'archive' => $archiveRel |
| — | — | @@ -113,10 +122,10 @@ |
| 114 | 123 | return isset( $response->status ) && $response->status == 'success'; |
| 115 | 124 | } |
| 116 | 125 | |
| 117 | | - function metadata( $repository, $path ) { |
| | 126 | + function metadata( $zone, $path ) { |
| 118 | 127 | $content = $this->post( $this->getURL( 'metadata.php' ), |
| 119 | 128 | array( |
| 120 | | - 'repository' => $repository, |
| | 129 | + 'zone' => $zone, |
| 121 | 130 | 'path' => $path, |
| 122 | 131 | )); |
| 123 | 132 | $response = $this->parseResponse( $content ); |
| Index: trunk/extensions/WebStore/WebStore.php |
| — | — | @@ -64,6 +64,7 @@ |
| 65 | 65 | |
| 66 | 66 | $wgAutoloadClasses['WebStoreClient'] = 'extensions/WebStore/WebStoreClient.php'; |
| 67 | 67 | $wgAutoloadClasses['WebStoreCommon'] = 'extensions/WebStore/WebStoreCommon.php'; |
| | 68 | +$wgAutoloadClasses['WebStoreLocalImage'] = 'extensions/WebStore/WebStoreCommon.php'; |
| 68 | 69 | $wgAutoloadClasses['WebStorePostFile'] = 'extensions/WebStore/WebStorePostFile.php'; |
| 69 | 70 | $wgHooks['LoadAllMessages'][] = 'WebStoreCommon::initialiseMessages'; |
| 70 | 71 | |
| Index: trunk/extensions/WebStore/metadata.php |
| — | — | @@ -6,18 +6,6 @@ |
| 7 | 7 | |
| 8 | 8 | require( dirname( __FILE__ ) . '/WebStoreStart.php' ); |
| 9 | 9 | |
| 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 | | - |
| 22 | 10 | class WebStoreMetadata extends WebStoreCommon { |
| 23 | 11 | function execute() { |
| 24 | 12 | global $wgRequest; |
| — | — | @@ -29,7 +17,7 @@ |
| 30 | 18 | <head><title>metadata.php Test interface</title> |
| 31 | 19 | <body> |
| 32 | 20 | <form method="post" action="metadata.php"> |
| 33 | | -<p>Repository: <select name="repository" value="public"> |
| | 21 | +<p>Zone: <select name="zone" value="public"> |
| 34 | 22 | <option>public</option> |
| 35 | 23 | <option>temp</option> |
| 36 | 24 | <option>deleted</option> |
| — | — | @@ -43,10 +31,10 @@ |
| 44 | 32 | return true; |
| 45 | 33 | } |
| 46 | 34 | |
| 47 | | - $repository = $wgRequest->getVal( 'repository' ); |
| 48 | | - $root = $this->getRepositoryRoot( $repository ); |
| | 35 | + $zone = $wgRequest->getVal( 'zone' ); |
| | 36 | + $root = $this->getZoneRoot( $zone ); |
| 49 | 37 | if ( strval( $root ) == '' ) { |
| 50 | | - $this->error( 400, 'webstore_invalid_repository', $repository ); |
| | 38 | + $this->error( 400, 'webstore_invalid_zone', $zone ); |
| 51 | 39 | return false; |
| 52 | 40 | } |
| 53 | 41 | |
| — | — | @@ -58,21 +46,42 @@ |
| 59 | 47 | |
| 60 | 48 | $fullPath = $root . '/' . $rel; |
| 61 | 49 | |
| 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 ) { |
| 63 | 59 | $this->error( 400, 'webstore_metadata_not_found', $fullPath ); |
| 64 | 60 | return false; |
| 65 | 61 | } |
| 66 | 62 | |
| 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 ); |
| 69 | 71 | |
| 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 | + ); |
| 71 | 81 | |
| 72 | 82 | header( 'Content-Type: text/xml' ); |
| 73 | 83 | 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 ) ) { |
| 77 | 86 | $value = $value ? 1 : 0; |
| 78 | 87 | } |
| 79 | 88 | echo "<item name=\"$field\">" . htmlspecialchars( $value ) . "</item>\n"; |
| Index: trunk/extensions/WebStore/404-handler.php |
| — | — | @@ -39,19 +39,46 @@ |
| 40 | 40 | return false; |
| 41 | 41 | } |
| 42 | 42 | |
| 43 | | - if ( !preg_match( '!^(\w)/(\w\w)/([^/]*)/(page(\d*)-)?(\d*)px-([^/]*)$!', $rel, $parts ) ) { |
| | 43 | + if ( !preg_match( '!^(\w)/(\w\w)/([^/]*)/([^/]*)$!', $rel, $parts ) ) { |
| 44 | 44 | header( 'X-Debug: regex mismatch' ); |
| 45 | 45 | $this->real404(); |
| 46 | 46 | return false; |
| 47 | 47 | } |
| 48 | 48 | |
| 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 ) { |
| 51 | 52 | header( 'X-Debug: filename/fn2 mismatch' ); |
| 52 | 53 | $this->real404(); |
| 53 | 54 | return false; |
| 54 | 55 | } |
| | 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 | + } |
| 55 | 73 | |
| | 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 | + |
| 56 | 83 | // Open the destination temporary file |
| 57 | 84 | $dstPath = "{$this->publicDir}/thumb/$rel"; |
| 58 | 85 | $tmpPath = "$dstPath.temp.MW_WebStore"; |
| — | — | @@ -114,7 +141,7 @@ |
| 115 | 142 | |
| 116 | 143 | $post = WebStorePostFile::post( $scalerUrl, 'data', |
| 117 | 144 | "{$this->publicDir}/$hash1/$hash2/$filename", |
| 118 | | - array( 'width' => $size, 'page' => $pagenum ), |
| | 145 | + $params, |
| 119 | 146 | $server, $tmpFile, $this->httpConnectTimeout, $this->httpOverallTimeout ); |
| 120 | 147 | |
| 121 | 148 | // Try next server unless that one was successful |
| — | — | @@ -135,6 +162,12 @@ |
| 136 | 163 | break; |
| 137 | 164 | } |
| 138 | 165 | |
| | 166 | + fseek( $tmpFile, 0, SEEK_END ); |
| | 167 | + if ( ftell( $tmpFile ) == 0 ) { |
| | 168 | + $this->htmlError( 500, 'webstore_scaler_empty_response' ); |
| | 169 | + break; |
| | 170 | + } |
| | 171 | + |
| 139 | 172 | // Report PHP errors |
| 140 | 173 | if ( count( $this->phpErrors ) ) { |
| 141 | 174 | $errors = '<ul>'; |
| Index: trunk/extensions/WebStore/inplace-scaler.php |
| — | — | @@ -7,7 +7,7 @@ |
| 8 | 8 | global $wgRequest, $wgContLanguageCode; |
| 9 | 9 | |
| 10 | 10 | if ( !$this->scalerAccessRanges ) { |
| 11 | | - $this->error( 403, 'inplace_access_disabled' ); |
| | 11 | + $this->htmlError( 403, 'inplace_access_disabled' ); |
| 12 | 12 | return false; |
| 13 | 13 | } |
| 14 | 14 | |
| — | — | @@ -24,7 +24,7 @@ |
| 25 | 25 | } |
| 26 | 26 | |
| 27 | 27 | if ( !$allowed ) { |
| 28 | | - $this->error( 403, 'inplace_access_denied' ); |
| | 28 | + $this->htmlError( 403, 'inplace_access_denied' ); |
| 29 | 29 | return false; |
| 30 | 30 | } |
| 31 | 31 | |
| — | — | @@ -49,20 +49,18 @@ |
| 50 | 50 | $tempDir = $this->tmpDir . '/' . gmdate( self::$tempDirFormat ); |
| 51 | 51 | if ( !is_dir( $tempDir ) ) { |
| 52 | 52 | if ( !wfMkdirParents( $tempDir ) ) { |
| 53 | | - $this->error( 500, 'inplace_scaler_no_temp' ); |
| | 53 | + $this->htmlError( 500, 'inplace_scaler_no_temp' ); |
| 54 | 54 | return false; |
| 55 | 55 | } |
| 56 | 56 | } |
| 57 | 57 | |
| 58 | 58 | $name = $wgRequest->getFileName( 'data' ); |
| 59 | 59 | $srcTemp = $wgRequest->getFileTempname( 'data' ); |
| 60 | | - $page = $wgRequest->getInt( 'page', 1 ); |
| 61 | | - $dstWidth = $wgRequest->getInt( 'width', 0 ); |
| 62 | 60 | |
| 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 ); |
| 67 | 65 | } |
| 68 | 66 | |
| 69 | 67 | $i = strrpos( $name, '.' ); |
| — | — | @@ -70,16 +68,26 @@ |
| 71 | 69 | |
| 72 | 70 | $magic = MimeMagic::singleton(); |
| 73 | 71 | $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' ); |
| 78 | 78 | return false; |
| 79 | 79 | } |
| 80 | 80 | |
| 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 | + } |
| 82 | 90 | |
| 83 | | - list( $dstExt, $dstMime ) = Image::getThumbType( $ext, $mime ); |
| | 91 | + list( $dstExt, $dstMime ) = $handler->getThumbType( $ext, $mime ); |
| 84 | 92 | if ( preg_match( '/[ \\n;=]/', $name ) ) { |
| 85 | 93 | $dstName = "thumb.$ext"; |
| 86 | 94 | } else { |
| — | — | @@ -91,13 +99,26 @@ |
| 92 | 100 | |
| 93 | 101 | $dstTemp = tempnam( $tempDir, 'mwimg' ); |
| 94 | 102 | |
| 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 ); |
| 99 | 108 | return false; |
| 100 | 109 | } |
| | 110 | + $stat = stat( $dstTemp ); |
| | 111 | + if ( !$stat ) { |
| | 112 | + $this->htmlError( 500, 'inplace_scaler_no_output' ); |
| | 113 | + return false; |
| | 114 | + } |
| 101 | 115 | |
| | 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" ); |
| 102 | 123 | header( "Content-Type: $dstMime" ); |
| 103 | 124 | header( "Content-Disposition: inline;filename*=utf-8'$wgContLanguageCode'" . urlencode( $dstName ) ); |
| 104 | 125 | readfile( $dstTemp ); |
| — | — | @@ -105,7 +126,10 @@ |
| 106 | 127 | } |
| 107 | 128 | } |
| 108 | 129 | |
| 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 ); |
| 110 | 134 | $s = new InplaceScaler; |
| 111 | 135 | $s->execute(); |
| 112 | 136 | |
| Index: trunk/extensions/WebStore/WebStoreCommon.php |
| — | — | @@ -79,20 +79,32 @@ |
| 80 | 80 | } |
| 81 | 81 | |
| 82 | 82 | 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 ); |
| 87 | 93 | header( "HTTP/1.1 $code $info" ); |
| 88 | 94 | echo $this->dtd(); |
| 89 | 95 | 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> |
| 92 | 101 | $encMsgName: $msgText |
| 93 | | -</p></body></html> |
| | 102 | +</p> |
| | 103 | +$extra |
| | 104 | +</body> |
| | 105 | +</html> |
| 94 | 106 | |
| 95 | 107 | EOT; |
| 96 | | - } |
| | 108 | + } |
| 97 | 109 | |
| 98 | 110 | function executeCommon() { |
| 99 | 111 | if ( !$this->checkAccess() ) { |
| — | — | @@ -378,10 +390,10 @@ |
| 379 | 391 | } |
| 380 | 392 | |
| 381 | 393 | /** |
| 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 |
| 383 | 395 | */ |
| 384 | | - function getRepositoryRoot( $repository ) { |
| 385 | | - switch ( $repository ) { |
| | 396 | + function getZoneRoot( $zone ) { |
| | 397 | + switch ( $zone ) { |
| 386 | 398 | case 'public': |
| 387 | 399 | return $this->publicDir; |
| 388 | 400 | case 'temp': |
| — | — | @@ -481,4 +493,58 @@ |
| 482 | 494 | } |
| 483 | 495 | } |
| 484 | 496 | |
| | 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 | + |
| 485 | 551 | ?> |
| Index: trunk/extensions/WebStore/publish.php |
| — | — | @@ -25,7 +25,7 @@ |
| 26 | 26 | <head><title>publish.php Test Interface</title></head> |
| 27 | 27 | <body> |
| 28 | 28 | <form method="post" action="publish.php"> |
| 29 | | -<p>Source repository: <select name="srcRepo" value="public"> |
| | 29 | +<p>Source zone: <select name="srcZone" value="public"> |
| 30 | 30 | <option>public</option> |
| 31 | 31 | <option>temp</option> |
| 32 | 32 | <option>deleted</option> |
| — | — | @@ -41,12 +41,12 @@ |
| 42 | 42 | return true; |
| 43 | 43 | } |
| 44 | 44 | |
| 45 | | - $srcRepo = $wgRequest->getVal( 'srcRepo' ); |
| 46 | | - if ( !$srcRepo ) { |
| 47 | | - $srcRepo = 'temp'; |
| | 45 | + $srcZone = $wgRequest->getVal( 'srcZone' ); |
| | 46 | + if ( !$srcZone ) { |
| | 47 | + $srcZone = 'temp'; |
| 48 | 48 | } |
| 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' ); |
| 51 | 51 | |
| 52 | 52 | $srcRel = $wgRequest->getVal( 'src' ); |
| 53 | 53 | $dstRel = $wgRequest->getVal( 'dst' ); |
| — | — | @@ -61,7 +61,7 @@ |
| 62 | 62 | return false; |
| 63 | 63 | } |
| 64 | 64 | |
| 65 | | - // Don't publish into odd subdirectories of the public repository. |
| | 65 | + // Don't publish into odd subdirectories of the public zone. |
| 66 | 66 | // Some directories may be temporary caches with a potential for |
| 67 | 67 | // data loss. |
| 68 | 68 | if ( !preg_match( '!^archive|[a-zA-Z0-9]/!', $dstRel ) ) { |
| — | — | @@ -77,9 +77,9 @@ |
| 78 | 78 | return false; |
| 79 | 79 | } |
| 80 | 80 | |
| 81 | | - $srcRoot = $this->getRepositoryRoot( $srcRepo ); |
| | 81 | + $srcRoot = $this->getZoneRoot( $srcZone ); |
| 82 | 82 | if ( strval( $srcRoot ) == '' ) { |
| 83 | | - $this->error( 400, 'webstore_invalid_repository', $srcRepo ); |
| | 83 | + $this->error( 400, 'webstore_invalid_zone', $srcZone ); |
| 84 | 84 | return false; |
| 85 | 85 | } |
| 86 | 86 | |
| — | — | @@ -88,7 +88,10 @@ |
| 89 | 89 | $archivePath = $this->publicDir . '/archive/' . $archiveRel; |
| 90 | 90 | |
| 91 | 91 | 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 ) ) { |
| 93 | 96 | $status = 'archived'; |
| 94 | 97 | } else { |
| 95 | 98 | $status = 'failure'; |
| Index: trunk/extensions/WebStore/WebStore.i18n.php |
| — | — | @@ -8,6 +8,9 @@ |
| 9 | 9 | 'inplace_scaler_not_enough_params' => 'Not enough parameters.', |
| 10 | 10 | 'inplace_scaler_invalid_image' => 'Invalid image, could not determine size.', |
| 11 | 11 | '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.', |
| 12 | 15 | |
| 13 | 16 | 'webstore_access' => 'This service is restricted by client IP.', |
| 14 | 17 | 'webstore_path_invalid' => 'The filename was invalid.', |
| — | — | @@ -28,11 +31,12 @@ |
| 29 | 32 | 'webstore_temp_copy' => 'Error copying temporary file "$1" to destination file "$2".', |
| 30 | 33 | 'webstore_temp_close' => 'Error closing temporary file "$1".', |
| 31 | 34 | 'webstore_temp_lock' => 'Error locking temporary file "$1".', |
| | 35 | + 'webstore_no_archive' => 'Destination file exists and no archive was given.', |
| 32 | 36 | |
| 33 | 37 | 'webstore_no_file' => 'No file was uploaded.', |
| 34 | 38 | 'webstore_move_uploaded' => 'Error moving uploaded file "$1" to temporary location "$2".', |
| 35 | 39 | |
| 36 | | - 'webstore_invalid_repository' => 'Invalid repository "$1".', |
| | 40 | + 'webstore_invalid_zone' => 'Invalid zone "$1".', |
| 37 | 41 | |
| 38 | 42 | 'webstore_no_deleted' => 'No archive directory for deleted files is defined.', |
| 39 | 43 | 'webstore_curl' => 'Error from cURL: $1', |
| — | — | @@ -40,11 +44,14 @@ |
| 41 | 45 | 'webstore_php_warning' => 'PHP Warning: $1', |
| 42 | 46 | 'webstore_metadata_not_found' => 'File not found: $1', |
| 43 | 47 | '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.', |
| 44 | 50 | |
| 45 | 51 | 'webstore_invalid_response' => "Invalid response from server:\n\n$1\n", |
| 46 | 52 | 'webstore_no_response' => 'No response from server', |
| 47 | 53 | 'webstore_backend_error' => "Error from storage server:\n\n$1\n", |
| 48 | 54 | 'webstore_php_error' => 'PHP errors were encountered:', |
| | 55 | + 'webstore_no_handler' => 'No handler for transforming this MIME type', |
| 49 | 56 | ), |
| 50 | 57 | ); |
| 51 | 58 | |