r24808 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r24807‎ | r24808 | r24809 >
Date:10:50, 15 August 2007
Author:tstarling
Status:old
Tags:
Comment:
Basic integrated audio/video support, with Ogg implementation.
* JavaScript video player based loosely on Greg Maxwell's player
* Image page text snippet customisation
* Abstraction of transform parameters in the parser. Introduced Linker::makeImageLink2().
* Made canRender(), mustRender() depend on file, not just on handler. Moved width=0, height=0 checking to ImageHandler::canRender(), since audio streams have width=height=0 but should be rendered.

Also:
* Automatic upgrade for oldimage rows on image page view, allows media handler selection based on oi_*_mime
* oi_*_mime unconditionally referenced, REQUIRES SCHEMA UPGRADE
* Don't destroy file info for missing files on upgrade
* Simple, centralised extension message file handling
* Made MessageCache::loadAllMessages non-static, optimised for repeated-call case due to abuse in User.php
* Support for lightweight parser output hooks, with callback whitelist for security
* Moved Linker::formatSize() to Language, to join the new formatTimePeriod() and formatBitrate()
* Introduced MagicWordArray, regex capture trick requires that magic word IDs DO NOT CONTAIN HYPHENS.
Modified paths:
  • /trunk/extensions/OggHandler/OggHandler.i18n.php (added) (history)
  • /trunk/extensions/OggHandler/OggHandler.php (modified) (history)
  • /trunk/extensions/OggHandler/OggPlayer.js (added) (history)
  • /trunk/extensions/OggHandler/ext.php (added) (history)
  • /trunk/phase3/RELEASE-NOTES (modified) (history)
  • /trunk/phase3/includes/AutoLoader.php (modified) (history)
  • /trunk/phase3/includes/DefaultSettings.php (modified) (history)
  • /trunk/phase3/includes/GlobalFunctions.php (modified) (history)
  • /trunk/phase3/includes/ImagePage.php (modified) (history)
  • /trunk/phase3/includes/Linker.php (modified) (history)
  • /trunk/phase3/includes/MagicWord.php (modified) (history)
  • /trunk/phase3/includes/MessageCache.php (modified) (history)
  • /trunk/phase3/includes/MimeMagic.php (modified) (history)
  • /trunk/phase3/includes/OutputPage.php (modified) (history)
  • /trunk/phase3/includes/Parser.php (modified) (history)
  • /trunk/phase3/includes/ParserOutput.php (modified) (history)
  • /trunk/phase3/includes/SpecialAllmessages.php (modified) (history)
  • /trunk/phase3/includes/SpecialSpecialpages.php (modified) (history)
  • /trunk/phase3/includes/User.php (modified) (history)
  • /trunk/phase3/includes/filerepo/File.php (modified) (history)
  • /trunk/phase3/includes/filerepo/LocalFile.php (modified) (history)
  • /trunk/phase3/includes/filerepo/OldLocalFile.php (modified) (history)
  • /trunk/phase3/includes/media/DjVu.php (modified) (history)
  • /trunk/phase3/includes/media/Generic.php (modified) (history)
  • /trunk/phase3/includes/media/SVG.php (modified) (history)
  • /trunk/phase3/languages/Language.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesAr.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesBg.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesCs.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesEn.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesId.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesKk_cn.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesKk_kz.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesKk_tr.php (modified) (history)
  • /trunk/phase3/languages/messages/MessagesNl.php (modified) (history)

Diff [purge]

Index: trunk/phase3/includes/MagicWord.php
@@ -386,6 +386,181 @@
387387 function isCaseSensitive() {
388388 return $this->mCaseSensitive;
389389 }
 390+
 391+ function getId() {
 392+ return $this->mId;
 393+ }
390394 }
391395
 396+/**
 397+ * Class for handling an array of magic words
 398+ */
 399+class MagicWordArray {
 400+ var $names = array();
 401+ var $hash;
 402+ var $baseRegex, $regex;
392403
 404+ function __construct( $names = array() ) {
 405+ $this->names = $names;
 406+ }
 407+
 408+ /**
 409+ * Add a magic word by name
 410+ */
 411+ public function add( $name ) {
 412+ global $wgContLang;
 413+ $this->names[] = $name;
 414+ $this->hash = $this->baseRegex = $this->regex = null;
 415+ }
 416+
 417+ /**
 418+ * Add a number of magic words by name
 419+ */
 420+ public function addArray( $names ) {
 421+ $this->names = array_merge( $this->names, array_values( $names ) );
 422+ $this->hash = $this->baseRegex = $this->regex = null;
 423+ }
 424+
 425+ /**
 426+ * Get a 2-d hashtable for this array
 427+ */
 428+ function getHash() {
 429+ if ( is_null( $this->hash ) ) {
 430+ global $wgContLang;
 431+ $this->hash = array( 0 => array(), 1 => array() );
 432+ foreach ( $this->names as $name ) {
 433+ $magic = MagicWord::get( $name );
 434+ $case = intval( $magic->isCaseSensitive() );
 435+ foreach ( $magic->getSynonyms() as $syn ) {
 436+ if ( !$case ) {
 437+ $syn = $wgContLang->lc( $syn );
 438+ }
 439+ $this->hash[$case][$syn] = $name;
 440+ }
 441+ }
 442+ }
 443+ return $this->hash;
 444+ }
 445+
 446+ /**
 447+ * Get the base regex
 448+ */
 449+ function getBaseRegex() {
 450+ if ( is_null( $this->baseRegex ) ) {
 451+ $this->baseRegex = array( 0 => '', 1 => '' );
 452+ foreach ( $this->names as $name ) {
 453+ $magic = MagicWord::get( $name );
 454+ $case = intval( $magic->isCaseSensitive() );
 455+ foreach ( $magic->getSynonyms() as $i => $syn ) {
 456+ $group = "(?P<{$i}_{$name}>" . preg_quote( $syn, '/' ) . ')';
 457+ if ( $this->baseRegex[$case] === '' ) {
 458+ $this->baseRegex[$case] = $group;
 459+ } else {
 460+ $this->baseRegex[$case] .= '|' . $group;
 461+ }
 462+ }
 463+ }
 464+ }
 465+ return $this->baseRegex;
 466+ }
 467+
 468+ /**
 469+ * Get an unanchored regex
 470+ */
 471+ function getRegex() {
 472+ if ( is_null( $this->regex ) ) {
 473+ $base = $this->getBaseRegex();
 474+ $this->regex = array( '', '' );
 475+ if ( $this->baseRegex[0] !== '' ) {
 476+ $this->regex[0] = "/{$base[0]}/iuS";
 477+ }
 478+ if ( $this->baseRegex[1] !== '' ) {
 479+ $this->regex[1] = "/{$base[1]}/S";
 480+ }
 481+ }
 482+ return $this->regex;
 483+ }
 484+
 485+ /**
 486+ * Get a regex for matching variables
 487+ */
 488+ function getVariableRegex() {
 489+ return str_replace( "\\$1", "(.*?)", $this->getRegex() );
 490+ }
 491+
 492+ /**
 493+ * Get an anchored regex for matching variables
 494+ */
 495+ function getVariableStartToEndRegex() {
 496+ $base = $this->getBaseRegex();
 497+ $newRegex = array( '', '' );
 498+ if ( $base[0] !== '' ) {
 499+ $newRegex[0] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[0]})$/iuS" );
 500+ }
 501+ if ( $base[1] !== '' ) {
 502+ $newRegex[1] = str_replace( "\\$1", "(.*?)", "/^(?:{$base[1]})$/S" );
 503+ }
 504+ return $newRegex;
 505+ }
 506+
 507+ /**
 508+ * Parse a match array from preg_match
 509+ */
 510+ function parseMatch( $m ) {
 511+ reset( $m );
 512+ while ( list( $key, $value ) = each( $m ) ) {
 513+ if ( $key === 0 || $value === '' ) {
 514+ continue;
 515+ }
 516+ $parts = explode( '_', $key, 2 );
 517+ if ( count( $parts ) != 2 ) {
 518+ // This shouldn't happen
 519+ // continue;
 520+ throw new MWException( __METHOD__ . ': bad parameter name' );
 521+ }
 522+ list( $synIndex, $magicName ) = $parts;
 523+ $paramValue = next( $m );
 524+ return array( $magicName, $paramValue );
 525+ }
 526+ // This shouldn't happen either
 527+ throw new MWException( __METHOD__.': parameter not found' );
 528+ return array( false, false );
 529+ }
 530+
 531+ /**
 532+ * Match some text, with parameter capture
 533+ * Returns an array with the magic word name in the first element and the
 534+ * parameter in the second element.
 535+ * Both elements are false if there was no match.
 536+ */
 537+ public function matchVariableStartToEnd( $text ) {
 538+ global $wgContLang;
 539+ $regexes = $this->getVariableStartToEndRegex();
 540+ foreach ( $regexes as $case => $regex ) {
 541+ if ( $regex !== '' ) {
 542+ $m = false;
 543+ if ( preg_match( $regex, $text, $m ) ) {
 544+ return $this->parseMatch( $m );
 545+ }
 546+ }
 547+ }
 548+ return array( false, false );
 549+ }
 550+
 551+ /**
 552+ * Match some text, without parameter capture
 553+ * Returns the magic word name, or false if there was no capture
 554+ */
 555+ public function matchStartToEnd( $text ) {
 556+ $hash = $this->getHash();
 557+ if ( isset( $hash[1][$text] ) ) {
 558+ return $hash[1][$text];
 559+ }
 560+ global $wgContLang;
 561+ $lc = $wgContLang->lc( $text );
 562+ if ( isset( $hash[0][$lc] ) ) {
 563+ return $hash[0][$lc];
 564+ }
 565+ return false;
 566+ }
 567+}
Index: trunk/phase3/includes/Linker.php
@@ -433,42 +433,96 @@
434434 return $s;
435435 }
436436
437 - /** Creates the HTML source for images
438 - * @param object $nt
439 - * @param string $label label text
440 - * @param string $alt alt text
441 - * @param string $align horizontal alignment: none, left, center, right)
442 - * @param array $params some format keywords: width, height, page, upright, upright_factor, frameless, border
443 - * @param boolean $framed shows image in original size in a frame
444 - * @param boolean $thumb shows image as thumbnail in a frame
445 - * @param string $manual_thumb image name for the manual thumbnail
446 - * @param string $valign vertical alignment: baseline, sub, super, top, text-top, middle, bottom, text-bottom
447 - * @return string
448 - */
449 - function makeImageLinkObj( $nt, $label, $alt, $align = '', $params = array(), $framed = false,
450 - $thumb = false, $manual_thumb = '', $valign = '', $time = false )
 437+ /**
 438+ * Creates the HTML source for images
 439+ * @deprecated use makeImageLink2
 440+ *
 441+ * @param object $title
 442+ * @param string $label label text
 443+ * @param string $alt alt text
 444+ * @param string $align horizontal alignment: none, left, center, right)
 445+ * @param array $handlerParams Parameters to be passed to the media handler
 446+ * @param boolean $framed shows image in original size in a frame
 447+ * @param boolean $thumb shows image as thumbnail in a frame
 448+ * @param string $manualthumb image name for the manual thumbnail
 449+ * @param string $valign vertical alignment: baseline, sub, super, top, text-top, middle, bottom, text-bottom
 450+ * @return string
 451+ */
 452+ function makeImageLinkObj( $title, $label, $alt, $align = '', $handlerParams = array(), $framed = false,
 453+ $thumb = false, $manualthumb = '', $valign = '', $time = false )
451454 {
 455+ $frameParams = array( 'alt' => $alt, 'caption' => $label );
 456+ if ( $align ) {
 457+ $frameParams['align'] = $align;
 458+ }
 459+ if ( $framed ) {
 460+ $frameParams['framed'] = true;
 461+ }
 462+ if ( $thumb ) {
 463+ $frameParams['thumb'] = true;
 464+ }
 465+ if ( $manualthumb ) {
 466+ $frameParams['manualthumb'] = $manualthumb;
 467+ }
 468+ if ( $valign ) {
 469+ $frameParams['valign'] = $valign;
 470+ }
 471+ $file = wfFindFile( $title, $time );
 472+ return $this->makeImageLink2( $title, $file, $label, $alt, $frameParams, $handlerParams );
 473+ }
 474+
 475+ /**
 476+ * Make an image link
 477+ * @param Title $title Title object
 478+ * @param File $file File object, or false if it doesn't exist
 479+ *
 480+ * @param array $frameParams Associative array of parameters external to the media handler.
 481+ * Boolean parameters are indicated by presence or absence, the value is arbitrary and
 482+ * will often be false.
 483+ * thumbnail If present, downscale and frame
 484+ * manualthumb Image name to use as a thumbnail, instead of automatic scaling
 485+ * framed Shows image in original size in a frame
 486+ * frameless Downscale but don't frame
 487+ * upright If present, tweak default sizes for portrait orientation
 488+ * upright_factor Fudge factor for "upright" tweak (default 0.75)
 489+ * border If present, show a border around the image
 490+ * align Horizontal alignment (left, right, center, none)
 491+ * valign Vertical alignment (baseline, sub, super, top, text-top, middle,
 492+ * bottom, text-bottom)
 493+ * alt Alternate text for image (i.e. alt attribute). Plain text.
 494+ * caption HTML for image caption.
 495+ *
 496+ * @param array $handlerParams Associative array of media handler parameters, to be passed
 497+ * to transform(). Typical keys are "width" and "page".
 498+ */
 499+ function makeImageLink2( Title $title, $file, $frameParams = array(), $handlerParams = array() ) {
452500 global $wgContLang, $wgUser, $wgThumbLimits, $wgThumbUpright;
 501+ if ( $file && !$file->allowInlineDisplay() ) {
 502+ wfDebug( __METHOD__.': '.$title->getPrefixedDBkey()." does not allow inline display\n" );
 503+ return $this->makeKnownLinkObj( $title );
 504+ }
453505
454 - $img = wfFindFile( $nt, $time );
 506+ // Shortcuts
 507+ $fp =& $frameParams;
 508+ $hp =& $handlerParams;
455509
456 - if ( $img && !$img->allowInlineDisplay() ) {
457 - wfDebug( __METHOD__.': '.$nt->getPrefixedDBkey()." does not allow inline display\n" );
458 - return $this->makeKnownLinkObj( $nt );
459 - }
 510+ // Clean up parameters
 511+ $page = isset( $hp['page'] ) ? $hp['page'] : false;
 512+ if ( !isset( $fp['align'] ) ) $fp['align'] = '';
 513+ if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
460514
461 - $error = $prefix = $postfix = '';
462 - $page = isset( $params['page'] ) ? $params['page'] : false;
 515+ $prefix = $postfix = '';
463516
464 - if ( 'center' == $align )
 517+ if ( 'center' == $fp['align'] )
465518 {
466519 $prefix = '<div class="center">';
467520 $postfix = '</div>';
468 - $align = 'none';
 521+ $fp['align'] = 'none';
469522 }
470 - if ( $img && !isset( $params['width'] ) ) {
471 - $params['width'] = $img->getWidth( $page );
472 - if( $thumb || $framed || isset( $params['frameless'] ) ) {
 523+ if ( $file && !isset( $hp['width'] ) ) {
 524+ $hp['width'] = $file->getWidth( $page );
 525+
 526+ if( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) || isset( $fp['frameless'] ) || !$hp['width'] ) {
473527 $wopt = $wgUser->getOption( 'thumbsize' );
474528
475529 if( !isset( $wgThumbLimits[$wopt] ) ) {
@@ -476,16 +530,21 @@
477531 }
478532
479533 // Reduce width for upright images when parameter 'upright' is used
480 - if ( !isset( $params['upright_factor'] ) || $params['upright_factor'] == 0 ) {
481 - $params['upright_factor'] = $wgThumbUpright;
 534+ if ( !isset( $fp['upright_factor'] ) || $fp['upright_factor'] == 0 ) {
 535+ $fp['upright_factor'] = $wgThumbUpright;
482536 }
483537 // Use width which is smaller: real image width or user preference width
484538 // For caching health: If width scaled down due to upright parameter, round to full __0 pixel to avoid the creation of a lot of odd thumbs
485 - $params['width'] = min( $params['width'], isset( $params['upright'] ) ? round( $wgThumbLimits[$wopt] * $params['upright_factor'], -1 ) : $wgThumbLimits[$wopt] );
 539+ $prefWidth = isset( $fp['upright'] ) ?
 540+ round( $wgThumbLimits[$wopt] * $fp['upright_factor'], -1 ) :
 541+ $wgThumbLimits[$wopt];
 542+ if ( $hp['width'] <= 0 || $prefWidth < $hp['width'] ) {
 543+ $hp['width'] = $prefWidth;
 544+ }
486545 }
487546 }
488547
489 - if ( $thumb || $framed ) {
 548+ if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) ) {
490549
491550 # Create a thumbnail. Alignment depends on language
492551 # writing direction, # right aligned for left-to-right-
@@ -494,15 +553,15 @@
495554 #
496555 # If thumbnail width has not been provided, it is set
497556 # to the default user option as specified in Language*.php
498 - if ( $align == '' ) {
499 - $align = $wgContLang->isRTL() ? 'left' : 'right';
 557+ if ( $fp['align'] == '' ) {
 558+ $fp['align'] = $wgContLang->isRTL() ? 'left' : 'right';
500559 }
501 - return $prefix.$this->makeThumbLinkObj( $nt, $img, $label, $alt, $align, $params, $framed, $manual_thumb ).$postfix;
 560+ return $prefix.$this->makeThumbLink2( $title, $file, $fp, $hp ).$postfix;
502561 }
503562
504 - if ( $img && $params['width'] ) {
 563+ if ( $file && $hp['width'] ) {
505564 # Create a resized image, without the additional thumbnail features
506 - $thumb = $img->transform( $params );
 565+ $thumb = $file->transform( $hp );
507566 } else {
508567 $thumb = false;
509568 }
@@ -512,58 +571,76 @@
513572 } else {
514573 $query = '';
515574 }
516 - $u = $nt->getLocalURL( $query );
 575+ $url = $title->getLocalURL( $query );
517576 $imgAttribs = array(
518 - 'alt' => $alt,
519 - 'longdesc' => $u
 577+ 'alt' => $fp['alt'],
 578+ 'longdesc' => $url
520579 );
521580
522 - if ( $valign ) {
523 - $imgAttribs['style'] = "vertical-align: $valign";
 581+ if ( isset( $fp['valign'] ) ) {
 582+ $imgAttribs['style'] = "vertical-align: {$fp['valign']}";
524583 }
525 - if ( isset( $params['border'] ) ) {
 584+ if ( isset( $fp['border'] ) ) {
526585 $imgAttribs['class'] = "thumbborder";
527586 }
528587 $linkAttribs = array(
529 - 'href' => $u,
 588+ 'href' => $url,
530589 'class' => 'image',
531 - 'title' => $alt
 590+ 'title' => $fp['alt']
532591 );
533592
534593 if ( !$thumb ) {
535 - $s = $this->makeBrokenImageLinkObj( $nt );
 594+ $s = $this->makeBrokenImageLinkObj( $title );
536595 } else {
537596 $s = $thumb->toHtml( $imgAttribs, $linkAttribs );
538597 }
539 - if ( '' != $align ) {
540 - $s = "<div class=\"float{$align}\"><span>{$s}</span></div>";
 598+ if ( '' != $fp['align'] ) {
 599+ $s = "<div class=\"float{$fp['align']}\"><span>{$s}</span></div>";
541600 }
542601 return str_replace("\n", ' ',$prefix.$s.$postfix);
543602 }
544603
545604 /**
546605 * Make HTML for a thumbnail including image, border and caption
547 - * @param Title $nt
548 - * @param Image $img Image object or false if it doesn't exist
 606+ * @param Title $title
 607+ * @param File $file File object or false if it doesn't exist
549608 */
550 - function makeThumbLinkObj( Title $nt, $img, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manual_thumb = "" ) {
 609+ function makeThumbLinkObj( Title $title, $file, $label = '', $alt, $align = 'right', $params = array(), $framed=false , $manualthumb = "" ) {
 610+ $frameParams = array(
 611+ 'alt' => $alt,
 612+ 'caption' => $label,
 613+ 'align' => $align
 614+ );
 615+ if ( $framed ) $frameParams['framed'] = true;
 616+ if ( $manualthumb ) $frameParams['manualthumb'] = $manualthumb;
 617+ return $this->makeThumbLink2( $title, $file, $frameParams, $handlerParams );
 618+ }
 619+
 620+ function makeThumbLink2( Title $title, $file, $frameParams = array(), $handlerParams = array() ) {
551621 global $wgStylePath, $wgContLang;
552 - $exists = $img && $img->exists();
 622+ $exists = $file && $file->exists();
553623
554 - $page = isset( $params['page'] ) ? $params['page'] : false;
 624+ # Shortcuts
 625+ $fp =& $frameParams;
 626+ $hp =& $handlerParams;
555627
556 - if ( empty( $params['width'] ) ) {
 628+ $page = isset( $hp['page'] ) ? $hp['page'] : false;
 629+ if ( !isset( $fp['align'] ) ) $fp['align'] = 'right';
 630+ if ( !isset( $fp['alt'] ) ) $fp['alt'] = '';
 631+ if ( !isset( $fp['caption'] ) ) $fp['caption'] = '';
 632+
 633+ if ( empty( $hp['width'] ) ) {
557634 // Reduce width for upright images when parameter 'upright' is used
558 - $params['width'] = isset( $params['upright'] ) ? 130 : 180;
 635+ $hp['width'] = isset( $fp['upright'] ) ? 130 : 180;
559636 }
560637 $thumb = false;
561638
562639 if ( !$exists ) {
563 - $outerWidth = $params['width'] + 2;
 640+ $outerWidth = $hp['width'] + 2;
564641 } else {
565 - if ( $manual_thumb != '' ) {
 642+ if ( isset( $fp['manualthumb'] ) ) {
566643 # Use manually specified thumbnail
567 - $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb );
 644+ $manual_title = Title::makeTitleSafe( NS_IMAGE, $fp['manualthumb'] );
568645 if( $manual_title ) {
569646 $manual_img = wfFindFile( $manual_title );
570647 if ( $manual_img ) {
@@ -572,63 +649,63 @@
573650 $exists = false;
574651 }
575652 }
576 - } elseif ( $framed ) {
 653+ } elseif ( isset( $fp['framed'] ) ) {
577654 // Use image dimensions, don't scale
578 - $thumb = $img->getUnscaledThumb( $page );
 655+ $thumb = $file->getUnscaledThumb( $page );
579656 } else {
580657 # Do not present an image bigger than the source, for bitmap-style images
581658 # This is a hack to maintain compatibility with arbitrary pre-1.10 behaviour
582 - $srcWidth = $img->getWidth( $page );
583 - if ( $srcWidth && !$img->mustRender() && $params['width'] > $srcWidth ) {
584 - $params['width'] = $srcWidth;
 659+ $srcWidth = $file->getWidth( $page );
 660+ if ( $srcWidth && !$file->mustRender() && $hp['width'] > $srcWidth ) {
 661+ $hp['width'] = $srcWidth;
585662 }
586 - $thumb = $img->transform( $params );
 663+ $thumb = $file->transform( $hp );
587664 }
588665
589666 if ( $thumb ) {
590667 $outerWidth = $thumb->getWidth() + 2;
591668 } else {
592 - $outerWidth = $params['width'] + 2;
 669+ $outerWidth = $hp['width'] + 2;
593670 }
594671 }
595672
596673 $query = $page ? 'page=' . urlencode( $page ) : '';
597 - $u = $nt->getLocalURL( $query );
 674+ $url = $title->getLocalURL( $query );
598675
599676 $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) );
600677 $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right';
601678 $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : '';
602679
603 - $s = "<div class=\"thumb t{$align}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
 680+ $s = "<div class=\"thumb t{$fp['align']}\"><div class=\"thumbinner\" style=\"width:{$outerWidth}px;\">";
604681 if( !$exists ) {
605 - $s .= $this->makeBrokenImageLinkObj( $nt );
 682+ $s .= $this->makeBrokenImageLinkObj( $title );
606683 $zoomicon = '';
607684 } elseif ( !$thumb ) {
608685 $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) );
609686 $zoomicon = '';
610687 } else {
611688 $imgAttribs = array(
612 - 'alt' => $alt,
613 - 'longdesc' => $u,
 689+ 'alt' => $fp['alt'],
 690+ 'longdesc' => $url,
614691 'class' => 'thumbimage'
615692 );
616693 $linkAttribs = array(
617 - 'href' => $u,
 694+ 'href' => $url,
618695 'class' => 'internal',
619 - 'title' => $alt
 696+ 'title' => $fp['alt']
620697 );
621 -
 698+
622699 $s .= $thumb->toHtml( $imgAttribs, $linkAttribs );
623 - if ( $framed ) {
 700+ if ( isset( $fp['framed'] ) ) {
624701 $zoomicon="";
625702 } else {
626703 $zoomicon = '<div class="magnify" style="float:'.$magnifyalign.'">'.
627 - '<a href="'.$u.'" class="internal" title="'.$more.'">'.
 704+ '<a href="'.$url.'" class="internal" title="'.$more.'">'.
628705 '<img src="'.$wgStylePath.'/common/images/magnify-clip.png" ' .
629706 'width="15" height="11" alt="" /></a></div>';
630707 }
631708 }
632 - $s .= ' <div class="thumbcaption"'.$textalign.'>'.$zoomicon.$label."</div></div></div>";
 709+ $s .= ' <div class="thumbcaption"'.$textalign.'>'.$zoomicon.$fp['caption']."</div></div></div>";
633710 return str_replace("\n", ' ', $s);
634711 }
635712
@@ -1269,28 +1346,7 @@
12701347 */
12711348 public function formatSize( $size ) {
12721349 global $wgLang;
1273 - // For small sizes no decimal places necessary
1274 - $round = 0;
1275 - if( $size > 1024 ) {
1276 - $size = $size / 1024;
1277 - if( $size > 1024 ) {
1278 - $size = $size / 1024;
1279 - // For MB and bigger two decimal places are smarter
1280 - $round = 2;
1281 - if( $size > 1024 ) {
1282 - $size = $size / 1024;
1283 - $msg = 'size-gigabytes';
1284 - } else {
1285 - $msg = 'size-megabytes';
1286 - }
1287 - } else {
1288 - $msg = 'size-kilobytes';
1289 - }
1290 - } else {
1291 - $msg = 'size-bytes';
1292 - }
1293 - $size = round( $size, $round );
1294 - return wfMsgHtml( $msg, $wgLang->formatNum( $size ) );
 1350+ return htmlspecialchars( $wgLang->formatSize( $size ) );
12951351 }
12961352
12971353 /**
@@ -1346,3 +1402,4 @@
13471403
13481404
13491405
 1406+
Index: trunk/phase3/includes/Parser.php
@@ -97,7 +97,8 @@
9898 * @private
9999 */
100100 # Persistent:
101 - var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables;
 101+ var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables,
 102+ $mImageParams, $mImageParamsMagicArray;
102103
103104 # Cleared with clearState():
104105 var $mOutput, $mAutonumber, $mDTopen, $mStripState;
@@ -4473,10 +4474,50 @@
44744475 return $ig->toHTML();
44754476 }
44764477
 4478+ function getImageParams( $handler ) {
 4479+ if ( $handler ) {
 4480+ $handlerClass = get_class( $handler );
 4481+ } else {
 4482+ $handlerClass = '';
 4483+ }
 4484+ if ( !isset( $this->mImageParams[$handlerClass] ) ) {
 4485+ // Initialise static lists
 4486+ static $internalParamNames = array(
 4487+ 'horizAlign' => array( 'left', 'right', 'center', 'none' ),
 4488+ 'vertAlign' => array( 'baseline', 'sub', 'super', 'top', 'text-top', 'middle',
 4489+ 'bottom', 'text-bottom' ),
 4490+ 'frame' => array( 'thumbnail', 'manualthumb', 'framed', 'frameless',
 4491+ 'upright', 'border' ),
 4492+ );
 4493+ static $internalParamMap;
 4494+ if ( !$internalParamMap ) {
 4495+ $internalParamMap = array();
 4496+ foreach ( $internalParamNames as $type => $names ) {
 4497+ foreach ( $names as $name ) {
 4498+ $magicName = str_replace( '-', '_', "img_$name" );
 4499+ $internalParamMap[$magicName] = array( $type, $name );
 4500+ }
 4501+ }
 4502+ }
 4503+
 4504+ // Add handler params
 4505+ $paramMap = $internalParamMap;
 4506+ if ( $handler ) {
 4507+ $handlerParamMap = $handler->getParamMap();
 4508+ foreach ( $handlerParamMap as $magic => $paramName ) {
 4509+ $paramMap[$magic] = array( 'handler', $paramName );
 4510+ }
 4511+ }
 4512+ $this->mImageParams[$handlerClass] = $paramMap;
 4513+ $this->mImageParamsMagicArray[$handlerClass] = new MagicWordArray( array_keys( $paramMap ) );
 4514+ }
 4515+ return array( $this->mImageParams[$handlerClass], $this->mImageParamsMagicArray[$handlerClass] );
 4516+ }
 4517+
44774518 /**
44784519 * Parse image options text and use it to make an image
44794520 */
4480 - function makeImage( $nt, $options ) {
 4521+ function makeImage( $title, $options ) {
44814522 # @TODO: let the MediaHandler specify its transform parameters
44824523 #
44834524 # Check if the options text is of the form "options|alt text"
@@ -4500,77 +4541,55 @@
45014542 # * middle
45024543 # * bottom
45034544 # * text-bottom
 4545+
 4546+ $parts = array_map( 'trim', explode( '|', $options) );
 4547+ $sk = $this->mOptions->getSkin();
45044548
 4549+ # Give extensions a chance to select the file revision for us
 4550+ $skip = $time = false;
 4551+ wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$title, &$skip, &$time ) );
45054552
4506 - $part = array_map( 'trim', explode( '|', $options) );
 4553+ if ( $skip ) {
 4554+ return $sk->makeLinkObj( $title );
 4555+ }
45074556
4508 - $mwAlign = array();
4509 - $alignments = array( 'left', 'right', 'center', 'none', 'baseline', 'sub', 'super', 'top', 'text-top', 'middle', 'bottom', 'text-bottom' );
4510 - foreach ( $alignments as $alignment ) {
4511 - $mwAlign[$alignment] =& MagicWord::get( 'img_'.$alignment );
 4557+ # Get parameter map
 4558+ $file = wfFindFile( $title, $time );
 4559+ $handler = $file ? $file->getHandler() : false;
 4560+
 4561+ list( $paramMap, $mwArray ) = $this->getImageParams( $handler );
 4562+
 4563+ # Process the input parameters
 4564+ $caption = '';
 4565+ $params = array( 'frame' => array(), 'handler' => array(),
 4566+ 'horizAlign' => array(), 'vertAlign' => array() );
 4567+ foreach( $parts as $part ) {
 4568+ list( $magicName, $value ) = $mwArray->matchVariableStartToEnd( $part );
 4569+ if ( isset( $paramMap[$magicName] ) ) {
 4570+ list( $type, $paramName ) = $paramMap[$magicName];
 4571+ $params[$type][$paramName] = $value;
 4572+ } else {
 4573+ $caption = $part;
 4574+ }
45124575 }
4513 - $mwThumb =& MagicWord::get( 'img_thumbnail' );
4514 - $mwManualThumb =& MagicWord::get( 'img_manualthumb' );
4515 - $mwWidth =& MagicWord::get( 'img_width' );
4516 - $mwFramed =& MagicWord::get( 'img_framed' );
4517 - $mwFrameless =& MagicWord::get( 'img_frameless' );
4518 - $mwUpright =& MagicWord::get( 'img_upright' );
4519 - $mwBorder =& MagicWord::get( 'img_border' );
4520 - $mwPage =& MagicWord::get( 'img_page' );
4521 - $caption = '';
45224576
4523 - $params = array();
4524 - $framed = $thumb = false;
4525 - $manual_thumb = '' ;
4526 - $align = $valign = '';
4527 - $sk = $this->mOptions->getSkin();
 4577+ # Process alignment parameters
 4578+ if ( $params['horizAlign'] ) {
 4579+ $params['frame']['align'] = key( $params['horizAlign'] );
 4580+ }
 4581+ if ( $params['vertAlign'] ) {
 4582+ $params['frame']['valign'] = key( $params['vertAlign'] );
 4583+ }
45284584
4529 - foreach( $part as $val ) {
4530 - if ( !is_null( $mwThumb->matchVariableStartToEnd($val) ) ) {
4531 - $thumb=true;
4532 - } elseif ( !is_null( $match = $mwUpright->matchVariableStartToEnd( $val ) ) ) {
4533 - $params['upright'] = true;
4534 - $params['upright_factor'] = floatval( $match );
4535 - } elseif ( !is_null( $match = $mwFrameless->matchVariableStartToEnd( $val ) ) ) {
4536 - $params['frameless'] = true;
4537 - } elseif ( !is_null( $mwBorder->matchVariableStartToEnd( $val ) ) ) {
4538 - $params['border'] = true;
4539 - } elseif ( ! is_null( $match = $mwManualThumb->matchVariableStartToEnd($val) ) ) {
4540 - # use manually specified thumbnail
4541 - $thumb=true;
4542 - $manual_thumb = $match;
4543 - } else {
4544 - foreach( $alignments as $alignment ) {
4545 - if ( ! is_null( $mwAlign[$alignment]->matchVariableStartToEnd($val) ) ) {
4546 - switch ( $alignment ) {
4547 - case 'left': case 'right': case 'center': case 'none':
4548 - $align = $alignment; break;
4549 - default:
4550 - $valign = $alignment;
4551 - }
4552 - continue 2;
4553 - }
 4585+ # Validate the handler parameters
 4586+ if ( $handler ) {
 4587+ foreach ( $params['handler'] as $name => $value ) {
 4588+ if ( !$handler->validateParam( $name, $value ) ) {
 4589+ unset( $params['handler'][$name] );
45544590 }
4555 - if ( ! is_null( $match = $mwPage->matchVariableStartToEnd($val) ) ) {
4556 - # Select a page in a multipage document
4557 - $params['page'] = $match;
4558 - } elseif ( !isset( $params['width'] ) && ! is_null( $match = $mwWidth->matchVariableStartToEnd($val) ) ) {
4559 - wfDebug( "img_width match: $match\n" );
4560 - # $match is the image width in pixels
4561 - $m = array();
4562 - if ( preg_match( '/^([0-9]*)x([0-9]*)$/', $match, $m ) ) {
4563 - $params['width'] = intval( $m[1] );
4564 - $params['height'] = intval( $m[2] );
4565 - } else {
4566 - $params['width'] = intval($match);
4567 - }
4568 - } elseif ( ! is_null( $mwFramed->matchVariableStartToEnd($val) ) ) {
4569 - $framed=true;
4570 - } else {
4571 - $caption = $val;
4572 - }
45734591 }
45744592 }
 4593+
45754594 # Strip bad stuff out of the alt text
45764595 $alt = $this->replaceLinkHoldersText( $caption );
45774596
@@ -4580,18 +4599,18 @@
45814600 $alt = $this->mStripState->unstripBoth( $alt );
45824601 $alt = Sanitizer::stripAllTags( $alt );
45834602
4584 - # Give extensions a chance to select the file revision for us
4585 - $skip = $time = false;
4586 - wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$nt, &$skip, &$time ) );
 4603+ $params['frame']['alt'] = $alt;
 4604+ $params['frame']['caption'] = $caption;
45874605
45884606 # Linker does the rest
4589 - if( $skip ) {
4590 - $link = $sk->makeLinkObj( $nt );
4591 - } else {
4592 - $link = $sk->makeImageLinkObj( $nt, $caption, $alt, $align, $params, $framed, $thumb, $manual_thumb, $valign, $time );
 4607+ $ret = $sk->makeImageLink2( $title, $file, $params['frame'], $params['handler'] );
 4608+
 4609+ # Give the handler a chance to modify the parser object
 4610+ if ( $handler ) {
 4611+ $handler->parserTransformHook( $this, $file );
45934612 }
4594 -
4595 - return $link;
 4613+
 4614+ return $ret;
45964615 }
45974616
45984617 /**
Index: trunk/phase3/includes/ParserOutput.php
@@ -19,7 +19,8 @@
2020 $mExternalLinks, # External link URLs, in the key only
2121 $mNewSection, # Show a new section link?
2222 $mNoGallery, # No gallery on category page? (__NOGALLERY__)
23 - $mHeadItems; # Items to put in the <head> section
 23+ $mHeadItems, # Items to put in the <head> section
 24+ $mOutputHooks; # Hook tags as per $wgParserOutputHooks
2425
2526 /**
2627 * Overridden title for display
@@ -44,6 +45,7 @@
4546 $this->mNoGallery = false;
4647 $this->mHeadItems = array();
4748 $this->mTemplateIds = array();
 49+ $this->mOutputHooks = array();
4850 }
4951
5052 function getText() { return $this->mText; }
@@ -58,6 +60,7 @@
5961 function &getExternalLinks() { return $this->mExternalLinks; }
6062 function getNoGallery() { return $this->mNoGallery; }
6163 function getSubtitle() { return $this->mSubtitle; }
 64+ function getOutputHooks() { return $this->mOutputHooks; }
6265
6366 function containsOldMagic() { return $this->mContainsOldMagic; }
6467 function setText( $text ) { return wfSetVar( $this->mText, $text ); }
@@ -71,6 +74,10 @@
7275 function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; }
7376 function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; }
7477
 78+ function addOutputHook( $hook, $data = false ) {
 79+ $this->mOutputHooks[] = array( $hook, $data );
 80+ }
 81+
7582 function setNewSection( $value ) {
7683 $this->mNewSection = (bool)$value;
7784 }
Index: trunk/phase3/includes/User.php
@@ -2527,7 +2527,8 @@
25282528 * @static
25292529 */
25302530 static function getGroupName( $group ) {
2531 - MessageCache::loadAllMessages();
 2531+ global $wgMessageCache;
 2532+ $wgMessageCache->loadAllMessages();
25322533 $key = "group-$group";
25332534 $name = wfMsg( $key );
25342535 return $name == '' || wfEmptyMsg( $key, $name )
@@ -2541,7 +2542,8 @@
25422543 * @static
25432544 */
25442545 static function getGroupMember( $group ) {
2545 - MessageCache::loadAllMessages();
 2546+ global $wgMessageCache;
 2547+ $wgMessageCache->loadAllMessages();
25462548 $key = "group-$group-member";
25472549 $name = wfMsg( $key );
25482550 return $name == '' || wfEmptyMsg( $key, $name )
@@ -2586,7 +2588,8 @@
25872589 * @return mixed
25882590 */
25892591 static function getGroupPage( $group ) {
2590 - MessageCache::loadAllMessages();
 2592+ global $wgMessageCache;
 2593+ $wgMessageCache->loadAllMessages();
25912594 $page = wfMsgForContent( 'grouppage-' . $group );
25922595 if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) {
25932596 $title = Title::newFromText( $page );
Index: trunk/phase3/includes/MessageCache.php
@@ -23,6 +23,7 @@
2424 var $mExtensionMessages = array();
2525 var $mInitialised = false;
2626 var $mDeferred = true;
 27+ var $mAllMessagesLoaded;
2728
2829 function __construct( &$memCached, $useDB, $expiry, $memcPrefix) {
2930 wfProfileIn( __METHOD__ );
@@ -669,12 +670,33 @@
670671 }
671672 }
672673
673 - static function loadAllMessages() {
 674+ function loadAllMessages() {
 675+ global $wgExtensionMessagesFiles;
 676+ if ( $this->mAllMessagesLoaded ) {
 677+ return;
 678+ }
 679+ $this->mAllMessagesLoaded = true;
 680+
674681 # Some extensions will load their messages when you load their class file
675682 wfLoadAllExtensions();
676683 # Others will respond to this hook
677684 wfRunHooks( 'LoadAllMessages' );
 685+ # Some register their messages in $wgExtensionMessagesFiles
 686+ foreach ( $wgExtensionMessagesFiles as $name => $file ) {
 687+ if ( $file ) {
 688+ $this->loadMessagesFile( $file );
 689+ $wgExtensionMessagesFiles[$name] = false;
 690+ }
 691+ }
678692 # Still others will respond to neither, they are EVIL. We sometimes need to know!
679693 }
 694+
 695+ /**
 696+ * Load messages from a given file
 697+ */
 698+ function loadMessagesFile( $filename ) {
 699+ require( $filename );
 700+ $this->addMessagesByLang( $messages );
 701+ }
680702 }
681703
Index: trunk/phase3/includes/DefaultSettings.php
@@ -1917,6 +1917,28 @@
19181918 $wgSkinExtensionFunctions = array();
19191919
19201920 /**
 1921+ * Extension messages files
 1922+ * Associative array mapping extension name to the filename where messages can be found.
 1923+ * The file must create a variable called $messages.
 1924+ * When the messages are needed, the extension should call wfLoadMessagesFile()
 1925+ */
 1926+$wgExtensionMessagesFiles = array();
 1927+
 1928+/**
 1929+ * Parser output hooks.
 1930+ * This is an associative array where the key is an extension-defined tag
 1931+ * (typically the extension name), and the value is a PHP callback.
 1932+ * These will be called as an OutputPageParserOutput hook, if the relevant
 1933+ * tag has been registered with the parser output object.
 1934+ *
 1935+ * Registration is done with $pout->addOutputHook( $tag, $data ).
 1936+ *
 1937+ * The callback has the form:
 1938+ * function outputHook( $outputPage, $parserOutput, $data ) { ... }
 1939+ */
 1940+$wgParserOutputHooks = array();
 1941+
 1942+/**
19211943 * List of valid skin names.
19221944 * The key should be the name in all lower case, the value should be a display name.
19231945 * The default skins will be added later, by Skin::getSkinNames(). Use
Index: trunk/phase3/includes/filerepo/File.php
@@ -210,6 +210,18 @@
211211 public function getHeight( $page = 1 ) { return false; }
212212
213213 /**
 214+ * Get the duration of a media file in seconds
 215+ */
 216+ public function getLength() {
 217+ $handler = $this->getHandler();
 218+ if ( $handler ) {
 219+ return $handler->getLength( $this );
 220+ } else {
 221+ return 0;
 222+ }
 223+ }
 224+
 225+ /**
214226 * Get handler-specific metadata
215227 * Overridden by LocalFile, UnregisteredLocalFile
216228 * STUB
@@ -239,7 +251,9 @@
240252 function getMediaType() { return MEDIATYPE_UNKNOWN; }
241253
242254 /**
243 - * Checks if the file can be presented to the browser as a bitmap.
 255+ * Checks if the output of transform() for this file is likely
 256+ * to be valid. If this is false, various user elements will
 257+ * display a placeholder instead.
244258 *
245259 * Currently, this checks if the file is an image format
246260 * that can be converted to a format
@@ -248,7 +262,7 @@
249263 */
250264 function canRender() {
251265 if ( !isset( $this->canRender ) ) {
252 - $this->canRender = $this->getHandler() && $this->handler->canRender();
 266+ $this->canRender = $this->getHandler() && $this->handler->canRender( $this );
253267 }
254268 return $this->canRender;
255269 }
@@ -271,16 +285,11 @@
272286 * @return bool
273287 */
274288 function mustRender() {
275 - return $this->getHandler() && $this->handler->mustRender();
 289+ return $this->getHandler() && $this->handler->mustRender( $this );
276290 }
277291
278292 /**
279 - * Determines if this media file may be shown inline on a page.
280 - *
281 - * This is currently synonymous to canRender(), but this could be
282 - * extended to also allow inline display of other media,
283 - * like flash animations or videos. If you do so, please keep in mind that
284 - * that could be a security risk.
 293+ * Alias for canRender()
285294 */
286295 function allowInlineDisplay() {
287296 return $this->canRender();
@@ -470,7 +479,7 @@
471480
472481 wfProfileIn( __METHOD__ );
473482 do {
474 - if ( !$this->getHandler() || !$this->handler->canRender() ) {
 483+ if ( !$this->canRender() ) {
475484 // not a bitmap or renderable image, don't try.
476485 $thumb = $this->iconThumb();
477486 break;
@@ -906,7 +915,7 @@
907916 * @return Bool
908917 */
909918 function isMultipage() {
910 - return $this->getHandler() && $this->handler->isMultiPage();
 919+ return $this->getHandler() && $this->handler->isMultiPage( $this );
911920 }
912921
913922 /**
@@ -915,7 +924,7 @@
916925 */
917926 function pageCount() {
918927 if ( !isset( $this->pageCount ) ) {
919 - if ( $this->getHandler() && $this->handler->isMultiPage() ) {
 928+ if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) {
920929 $this->pageCount = $this->handler->pageCount( $this );
921930 } else {
922931 $this->pageCount = false;
@@ -1014,7 +1023,9 @@
10151024 static function getPropsFromPath( $path, $ext = true ) {
10161025 wfProfileIn( __METHOD__ );
10171026 wfDebug( __METHOD__.": Getting file info for $path\n" );
1018 - $info = array( 'fileExists' => file_exists( $path ) );
 1027+ $info = array(
 1028+ 'fileExists' => file_exists( $path ) && !is_dir( $path )
 1029+ );
10191030 $gis = false;
10201031 if ( $info['fileExists'] ) {
10211032 $magic = MimeMagic::singleton();
@@ -1030,8 +1041,8 @@
10311042 $handler = MediaHandler::getHandler( $info['mime'] );
10321043 if ( $handler ) {
10331044 $tempImage = (object)array();
1034 - $gis = $handler->getImageSize( $tempImage, $path );
10351045 $info['metadata'] = $handler->getMetadata( $tempImage, $path );
 1046+ $gis = $handler->getImageSize( $tempImage, $path, $info['metadata'] );
10361047 } else {
10371048 $gis = false;
10381049 $info['metadata'] = '';
@@ -1074,13 +1085,42 @@
10751086 * Returns false on failure
10761087 */
10771088 static function sha1Base36( $path ) {
 1089+ wfSuppressWarnings();
10781090 $hash = sha1_file( $path );
 1091+ wfRestoreWarnings();
10791092 if ( $hash === false ) {
10801093 return false;
10811094 } else {
10821095 return wfBaseConvert( $hash, 16, 36, 31 );
10831096 }
10841097 }
 1098+
 1099+ function getLongDesc() {
 1100+ $handler = $this->getHandler();
 1101+ if ( $handler ) {
 1102+ return $handler->getLongDesc( $this );
 1103+ } else {
 1104+ return MediaHandler::getLongDesc( $this );
 1105+ }
 1106+ }
 1107+
 1108+ function getShortDesc() {
 1109+ $handler = $this->getHandler();
 1110+ if ( $handler ) {
 1111+ return $handler->getShortDesc( $this );
 1112+ } else {
 1113+ return MediaHandler::getShortDesc( $this );
 1114+ }
 1115+ }
 1116+
 1117+ function getDimensionsString() {
 1118+ $handler = $this->getHandler();
 1119+ if ( $handler ) {
 1120+ return $handler->getDimensionsString( $this );
 1121+ } else {
 1122+ return '';
 1123+ }
 1124+ }
10851125 }
10861126 /**
10871127 * Aliases for backwards compatibility with 1.6
@@ -1090,3 +1130,4 @@
10911131 define( 'MW_IMG_DELETED_USER', File::DELETED_USER );
10921132 define( 'MW_IMG_DELETED_RESTRICTED', File::DELETED_RESTRICTED );
10931133
 1134+
Index: trunk/phase3/includes/filerepo/OldLocalFile.php
@@ -118,7 +118,10 @@
119119 }
120120
121121 function saveToCache() {
122 - // Cache the entire history of the image (up to MAX_CACHE_ROWS).
 122+ // If a timestamp was specified, cache the entire history of the image (up to MAX_CACHE_ROWS).
 123+ if ( is_null( $this->requestedTime ) ) {
 124+ return;
 125+ }
123126 // This is expensive, so we only do it if $wgMemc is real
124127 global $wgMemc;
125128 if ( $wgMemc instanceof FakeMemcachedClient ) {
@@ -155,6 +158,7 @@
156159
157160 function loadFromDB() {
158161 wfProfileIn( __METHOD__ );
 162+ $this->dataLoaded = true;
159163 $dbr = $this->repo->getSlaveDB();
160164 $conds = array( 'oi_name' => $this->getName() );
161165 if ( is_null( $this->requestedTime ) ) {
@@ -169,7 +173,6 @@
170174 } else {
171175 $this->fileExists = false;
172176 }
173 - $this->dataLoaded = true;
174177 wfProfileOut( __METHOD__ );
175178 }
176179
@@ -178,8 +181,8 @@
179182 $fields[] = $prefix . 'archive_name';
180183
181184 // XXX: Temporary hack before schema update
182 - $fields = array_diff( $fields, array(
183 - 'oi_media_type', 'oi_major_mime', 'oi_minor_mime', 'oi_metadata' ) );
 185+ //$fields = array_diff( $fields, array(
 186+ // 'oi_media_type', 'oi_major_mime', 'oi_minor_mime', 'oi_metadata' ) );
184187 return $fields;
185188 }
186189
@@ -193,11 +196,18 @@
194197
195198 function upgradeRow() {
196199 wfProfileIn( __METHOD__ );
197 -
198200 $this->loadFromFile();
 201+
 202+ # Don't destroy file info of missing files
 203+ if ( !$this->fileExists ) {
 204+ wfDebug( __METHOD__.": file does not exist, aborting\n" );
 205+ wfProfileOut( __METHOD__ );
 206+ return;
 207+ }
199208
200209 $dbw = $this->repo->getMasterDB();
201210 list( $major, $minor ) = self::splitMime( $this->mime );
 211+ $mime = $this->mime;
202212
203213 wfDebug(__METHOD__.': upgrading '.$this->archive_name." to the current schema\n");
204214 $dbw->update( 'oldimage',
@@ -205,10 +215,10 @@
206216 'oi_width' => $this->width,
207217 'oi_height' => $this->height,
208218 'oi_bits' => $this->bits,
209 - #'oi_media_type' => $this->media_type,
210 - #'oi_major_mime' => $major,
211 - #'oi_minor_mime' => $minor,
212 - #'oi_metadata' => $this->metadata,
 219+ 'oi_media_type' => $this->media_type,
 220+ 'oi_major_mime' => $major,
 221+ 'oi_minor_mime' => $minor,
 222+ 'oi_metadata' => $this->metadata,
213223 'oi_sha1' => $this->sha1,
214224 ), array(
215225 'oi_name' => $this->getName(),
Index: trunk/phase3/includes/filerepo/LocalFile.php
@@ -39,7 +39,7 @@
4040 $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...)
4141 $mime, # MIME type, determined by MimeMagic::guessMimeType
4242 $major_mime, # Major mime type
43 - $minor_mine, # Minor mime type
 43+ $minor_mime, # Minor mime type
4444 $size, # Size in bytes (loadFromXxx)
4545 $metadata, # Handler-specific metadata
4646 $timestamp, # Upload timestamp
@@ -231,6 +231,7 @@
232232 * Load file metadata from a DB result row
233233 */
234234 function loadFromRow( $row, $prefix = 'img_' ) {
 235+ $this->dataLoaded = true;
235236 $array = $this->decodeRow( $row, $prefix );
236237 foreach ( $array as $name => $value ) {
237238 $this->$name = $value;
@@ -287,6 +288,11 @@
288289
289290 $this->loadFromFile();
290291
 292+ # Don't destroy file info of missing files
 293+ if ( !$this->fileExists ) {
 294+ wfDebug( __METHOD__.": file does not exist, aborting\n" );
 295+ return;
 296+ }
291297 $dbw = $this->repo->getMasterDB();
292298 list( $major, $minor ) = self::splitMime( $this->mime );
293299
@@ -318,6 +324,12 @@
319325 $this->$field = $info[$field];
320326 }
321327 }
 328+ // Fix up mime fields
 329+ if ( isset( $info['major_mime'] ) ) {
 330+ $this->mime = "{$info['major_mime']}/{$info['minor_mime']}";
 331+ } elseif ( isset( $info['mime'] ) ) {
 332+ list( $this->major_mime, $this->minor_mime ) = self::splitMime( $this->mime );
 333+ }
322334 }
323335
324336 /** splitMime inherited */
@@ -560,14 +572,9 @@
561573 $dbr = $this->repo->getSlaveDB();
562574
563575 if ( $this->historyLine == 0 ) {// called for the first time, return line from cur
564 - $this->historyRes = $dbr->select( 'image',
 576+ $this->historyRes = $dbr->select( 'image',
565577 array(
566 - 'img_size',
567 - 'img_description',
568 - 'img_user','img_user_text',
569 - 'img_timestamp',
570 - 'img_width',
571 - 'img_height',
 578+ '*',
572579 "'' AS oi_archive_name"
573580 ),
574581 array( 'img_name' => $this->title->getDBkey() ),
@@ -580,17 +587,7 @@
581588 }
582589 } else if ( $this->historyLine == 1 ) {
583590 $dbr->freeResult($this->historyRes);
584 - $this->historyRes = $dbr->select( 'oldimage',
585 - array(
586 - 'oi_size AS img_size',
587 - 'oi_description AS img_description',
588 - 'oi_user AS img_user',
589 - 'oi_user_text AS img_user_text',
590 - 'oi_timestamp AS img_timestamp',
591 - 'oi_width as img_width',
592 - 'oi_height as img_height',
593 - 'oi_archive_name'
594 - ),
 591+ $this->historyRes = $dbr->select( 'oldimage', '*',
595592 array( 'oi_name' => $this->title->getDBkey() ),
596593 __METHOD__,
597594 array( 'ORDER BY' => 'oi_timestamp DESC' )
Index: trunk/phase3/includes/MimeMagic.php
@@ -374,7 +374,9 @@
375375 $mime = $this->detectMimeType( $file, $ext );
376376
377377 // Read a chunk of the file
 378+ wfSuppressWarnings();
378379 $f = fopen( $file, "rt" );
 380+ wfRestoreWarnings();
379381 if( !$f ) return "unknown/unknown";
380382 $head = fread( $f, 1024 );
381383 fclose( $f );
Index: trunk/phase3/includes/ImagePage.php
@@ -16,6 +16,7 @@
1717 class ImagePage extends Article {
1818
1919 /* private */ var $img; // Image object this page is shown for
 20+ /* private */ var $repo;
2021 var $mExtraDescription = false;
2122
2223 function __construct( $title ) {
@@ -24,6 +25,7 @@
2526 if ( !$this->img ) {
2627 $this->img = wfLocalFile( $this->mTitle );
2728 }
 29+ $this->repo = $this->img->repo;
2830 }
2931
3032 /**
@@ -46,6 +48,7 @@
4749 return Article::view();
4850
4951 if ($wgShowEXIF && $this->img->exists()) {
 52+ // FIXME: bad interface, see note on MediaHandler::formatMetadata().
5053 $formattedMetadata = $this->img->formatMetadata();
5154 $showmeta = $formattedMetadata !== false;
5255 } else {
@@ -115,6 +118,8 @@
116119 /**
117120 * Make a table with metadata to be shown in the output page.
118121 *
 122+ * FIXME: bad interface, see note on MediaHandler::formatMetadata().
 123+ *
119124 * @access private
120125 *
121126 * @param array $exif The array containing the EXIF data
@@ -188,14 +193,15 @@
189194 $mime = $this->img->getMimeType();
190195 $showLink = false;
191196 $linkAttribs = array( 'href' => $full_url );
 197+ $longDesc = $this->img->getLongDesc();
192198
193 - wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this , &$wgOut ) ) ;
 199+ wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this , &$wgOut ) ) ;
194200
195 - if ( $this->img->allowInlineDisplay() and $width and $height) {
 201+ if ( $this->img->allowInlineDisplay() ) {
196202 # image
197203
198204 # "Download high res version" link below the image
199 - $msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->img->getSize() ), $mime );
 205+ #$msgsize = wfMsgHtml('file-info-size', $width_orig, $height_orig, $sk->formatSize( $this->img->getSize() ), $mime );
200206 # We'll show a thumbnail of this image
201207 if ( $width > $maxWidth || $height > $maxHeight ) {
202208 # Calculate the thumbnail size.
@@ -229,7 +235,7 @@
230236 } else {
231237 $anchorclose .=
232238 $msgsmall .
233 - '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $msgsize;
 239+ '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $longDesc;
234240 }
235241
236242 if ( $this->img->isMultipage() ) {
@@ -301,26 +307,17 @@
302308
303309
304310 if ($showLink) {
305 - // Workaround for incorrect MIME type on SVGs uploaded in previous versions
306 - if ($mime == 'image/svg') $mime = 'image/svg+xml';
307 -
308311 $filename = wfEscapeWikiText( $this->img->getName() );
309312 $info = wfMsg( 'file-info', $sk->formatSize( $this->img->getSize() ), $mime );
310 - $infores = '';
311313
312 - // Check for MIME type. Other types may have more information in the future.
313 - if (substr($mime,0,9) == 'image/svg' ) {
314 - $infores = wfMsg('file-svg', $width_orig, $height_orig ) . '<br />';
315 - }
316 -
317314 global $wgContLang;
318315 $dirmark = $wgContLang->getDirMark();
319316 if (!$this->img->isSafeFile()) {
320317 $warning = wfMsg( 'mediawarning' );
321318 $wgOut->addWikiText( <<<EOT
322 -<div class="fullMedia">$infores
 319+<div class="fullMedia">
323320 <span class="dangerousLink">[[Media:$filename|$filename]]</span>$dirmark
324 -<span class="fileInfo"> $info</span>
 321+<span class="fileInfo"> $longDesc</span>
325322 </div>
326323
327324 <div class="mediaWarning">$warning</div>
@@ -328,8 +325,8 @@
329326 );
330327 } else {
331328 $wgOut->addWikiText( <<<EOT
332 -<div class="fullMedia">$infores
333 -[[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $info</span>
 329+<div class="fullMedia">
 330+[[Media:$filename|$filename]]$dirmark <span class="fileInfo"> $longDesc</span>
334331 </div>
335332 EOT
336333 );
@@ -421,18 +418,22 @@
422419
423420 if ( $line ) {
424421 $list = new ImageHistoryList( $sk, $this->img );
 422+ $file = $this->repo->newFileFromRow( $line );
 423+ $dims = $file->getDimensionsString();
425424 $s = $list->beginImageHistoryList() .
426425 $list->imageHistoryLine( true, wfTimestamp(TS_MW, $line->img_timestamp),
427426 $this->mTitle->getDBkey(), $line->img_user,
428427 $line->img_user_text, $line->img_size, $line->img_description,
429 - $line->img_width, $line->img_height
 428+ $dims
430429 );
431430
432431 while ( $line = $this->img->nextHistoryLine() ) {
433 - $s .= $list->imageHistoryLine( false, $line->img_timestamp,
434 - $line->oi_archive_name, $line->img_user,
435 - $line->img_user_text, $line->img_size, $line->img_description,
436 - $line->img_width, $line->img_height
 432+ $file = $this->repo->newFileFromRow( $line );
 433+ $dims = $file->getDimensionsString();
 434+ $s .= $list->imageHistoryLine( false, $line->oi_timestamp,
 435+ $line->oi_archive_name, $line->oi_user,
 436+ $line->oi_user_text, $line->oi_size, $line->oi_description,
 437+ $dims
437438 );
438439 }
439440 $s .= $list->endImageHistoryList();
@@ -655,7 +656,7 @@
656657 */
657658 class ImageHistoryList {
658659
659 - protected $img, $skin, $title;
 660+ protected $img, $skin, $title, $repo;
660661
661662 public function __construct( $skin, $img ) {
662663 $this->skin = $skin;
@@ -682,7 +683,7 @@
683684 return "</table>\n";
684685 }
685686
686 - public function imageHistoryLine( $iscur, $timestamp, $img, $user, $usertext, $size, $description, $width, $height ) {
 687+ public function imageHistoryLine( $iscur, $timestamp, $img, $user, $usertext, $size, $description, $dims ) {
687688 global $wgUser, $wgLang, $wgTitle, $wgContLang;
688689 $local = $this->img->isLocal();
689690 $row = '';
@@ -740,10 +741,7 @@
741742 $row .= '</td>';
742743
743744 // Image dimensions
744 - // FIXME: It would be nice to show the duration (sound files) or
745 - // width/height/duration (video files) here, but this needs some
746 - // additional media handler work
747 - $row .= '<td>' . wfMsgHtml( 'widthheight', $width, $height ) . '</td>';
 745+ $row .= '<td>' . htmlspecialchars( $dims ) . '</td>';
748746
749747 // File size
750748 $row .= '<td class="mw-imagepage-filesize">' . $this->skin->formatSize( $size ) . '</td>';
@@ -754,4 +752,4 @@
755753 return "<tr>{$row}</tr>\n";
756754 }
757755
758 -}
\ No newline at end of file
 756+}
Index: trunk/phase3/includes/OutputPage.php
@@ -109,6 +109,10 @@
110110 $this->mHeadItems[$name] = $value;
111111 }
112112
 113+ function hasHeadItem( $name ) {
 114+ return isset( $this->mHeadItems[$name] );
 115+ }
 116+
113117 function setETag($tag) { $this->mETag = $tag; }
114118 function setArticleBodyOnly($only) { $this->mArticleBodyOnly = $only; }
115119 function getArticleBodyOnly($only) { return $this->mArticleBodyOnly; }
@@ -377,7 +381,16 @@
378382 # Display title
379383 if( ( $dt = $parserOutput->getDisplayTitle() ) !== false )
380384 $this->setPageTitle( $dt );
381 -
 385+
 386+ # Hooks registered in the object
 387+ global $wgParserOutputHooks;
 388+ foreach ( $parserOutput->getOutputHooks() as $hookInfo ) {
 389+ list( $hookName, $data ) = $hookInfo;
 390+ if ( isset( $wgParserOutputHooks[$hookName] ) ) {
 391+ call_user_func( $wgParserOutputHooks[$hookName], $this, $parserOutput, $data );
 392+ }
 393+ }
 394+
382395 wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) );
383396 }
384397
Index: trunk/phase3/includes/GlobalFunctions.php
@@ -2297,3 +2297,17 @@
22982298 function wfBoolToStr( $value ) {
22992299 return $value ? 'true' : 'false';
23002300 }
 2301+
 2302+/**
 2303+ * Load an extension messages file
 2304+ */
 2305+function wfLoadExtensionMessages( $extensionName ) {
 2306+ global $wgExtensionMessagesFiles, $wgMessageCache;
 2307+ if ( !empty( $wgExtensionMessagesFiles[$extensionName] ) ) {
 2308+ $wgMessageCache->loadMessagesFile( $wgExtensionMessagesFiles[$extensionName] );
 2309+ // Prevent double-loading
 2310+ $wgExtensionMessagesFiles[$extensionName] = false;
 2311+ }
 2312+}
 2313+
 2314+
Index: trunk/phase3/includes/AutoLoader.php
@@ -118,6 +118,7 @@
119119 'LogPage' => 'includes/LogPage.php',
120120 'MacBinary' => 'includes/MacBinary.php',
121121 'MagicWord' => 'includes/MagicWord.php',
 122+ 'MagicWordArray' => 'includes/MagicWord.php',
122123 'MathRenderer' => 'includes/Math.php',
123124 'MediaTransformOutput' => 'includes/MediaTransformOutput.php',
124125 'ThumbnailImage' => 'includes/MediaTransformOutput.php',
@@ -385,3 +386,4 @@
386387 }
387388
388389
 390+
Index: trunk/phase3/includes/SpecialSpecialpages.php
@@ -8,9 +8,9 @@
99 *
1010 */
1111 function wfSpecialSpecialpages() {
12 - global $wgOut, $wgUser;
 12+ global $wgOut, $wgUser, $wgMessageCache;
1313
14 - MessageCache::loadAllMessages();
 14+ $wgMessageCache->loadAllMessages();
1515
1616 $wgOut->setRobotpolicy( 'index,nofollow' );
1717 $sk = $wgUser->getSkin();
Index: trunk/phase3/includes/SpecialAllmessages.php
@@ -25,7 +25,8 @@
2626 $navText = wfMsg( 'allmessagestext' );
2727
2828 # Make sure all extension messages are available
29 - MessageCache::loadAllMessages();
 29+
 30+ $wgMessageCache->loadAllMessages();
3031
3132 $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) );
3233 ksort( $sortedArray );
Index: trunk/phase3/includes/media/DjVu.php
@@ -17,6 +17,13 @@
1818 function mustRender() { return true; }
1919 function isMultiPage() { return true; }
2020
 21+ function getParamMap() {
 22+ return array(
 23+ 'img_width' => 'width',
 24+ 'img_page' => 'page',
 25+ );
 26+ }
 27+
2128 function validateParam( $name, $value ) {
2229 if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) {
2330 if ( $value <= 0 ) {
Index: trunk/phase3/includes/media/Generic.php
@@ -36,6 +36,12 @@
3737 return self::$handlers[$class];
3838 }
3939
 40+ /**
 41+ * Get an associative array mapping magic word IDs to parameter names.
 42+ * Will be used by the parser to identify parameters.
 43+ */
 44+ abstract function getParamMap();
 45+
4046 /*
4147 * Validate a thumbnail parameter at parse time.
4248 * Return true to accept the parameter, and false to reject it.
@@ -126,20 +132,20 @@
127133 /**
128134 * True if the handled types can be transformed
129135 */
130 - function canRender() { return true; }
 136+ function canRender( $file ) { return true; }
131137 /**
132138 * True if handled types cannot be displayed directly in a browser
133139 * but can be rendered
134140 */
135 - function mustRender() { return false; }
 141+ function mustRender( $file ) { return false; }
136142 /**
137143 * True if the type has multi-page capabilities
138144 */
139 - function isMultiPage() { return false; }
 145+ function isMultiPage( $file ) { return false; }
140146 /**
141147 * Page count for a multi-page document, false if unsupported or unknown
142148 */
143 - function pageCount() { return false; }
 149+ function pageCount( $file ) { return false; }
144150 /**
145151 * False if the handler is disabled for all files
146152 */
@@ -177,10 +183,18 @@
178184 *
179185 * The function should return false if there is no metadata to display.
180186 */
 187+
 188+ /**
 189+ * FIXME: I don't really like this interface, it's not very flexible
 190+ * I think the media handler should generate HTML instead. It can do
 191+ * all the formatting according to some standard. That makes it possible
 192+ * to do things like visual indication of grouped and chained streams
 193+ * in ogg container files.
 194+ */
181195 function formatMetadata( $image, $metadata ) {
182196 return false;
183197 }
184 -
 198+
185199 protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
186200 $array[$visibility][] = array(
187201 'id' => "$type-$id",
@@ -189,6 +203,26 @@
190204 );
191205 }
192206
 207+ function getShortDesc( $file ) {
 208+ global $wgLang;
 209+ $nbytes = '(' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
 210+ $wgLang->formatNum( $file->getSize() ) ) . ')';
 211+ }
 212+
 213+ function getLongDesc( $file ) {
 214+ global $wgUser;
 215+ $sk = $wgUser->getSkin();
 216+ return wfMsg( 'file-info', $sk->formatSize( $file->getSize() ), $file->getMimeType() );
 217+ }
 218+
 219+ function getDimensionsString() {
 220+ return '';
 221+ }
 222+
 223+ /**
 224+ * Modify the parser object post-transform
 225+ */
 226+ function parserTransformHook( $parser, $file ) {}
193227 }
194228
195229 /**
@@ -197,6 +231,18 @@
198232 * @addtogroup Media
199233 */
200234 abstract class ImageHandler extends MediaHandler {
 235+ function canRender( $file ) {
 236+ if ( $file->getWidth() && $file->getHeight() ) {
 237+ return true;
 238+ } else {
 239+ return false;
 240+ }
 241+ }
 242+
 243+ function getParamMap() {
 244+ return array( 'img_width' => 'width' );
 245+ }
 246+
201247 function validateParam( $name, $value ) {
202248 if ( in_array( $name, array( 'width', 'height' ) ) ) {
203249 if ( $value <= 0 ) {
@@ -325,6 +371,31 @@
326372 wfRestoreWarnings();
327373 return $gis;
328374 }
 375+
 376+ function getShortDesc( $file ) {
 377+ global $wgLang;
 378+ $nbytes = '(' . wfMsgExt( 'nbytes', array( 'parsemag', 'escape' ),
 379+ $wgLang->formatNum( $file->getSize() ) ) . ')';
 380+ $widthheight = wfMsgHtml( 'widthheight', $file->getWidth(), $file->getHeight() );
 381+
 382+ return "$widthheight ($nbytes)";
 383+ }
 384+
 385+ function getLongDesc( $file ) {
 386+ global $wgLang;
 387+ return wfMsgHtml('file-info-size', $file->getWidth(), $file->getHeight(),
 388+ $wgLang->formatSize( $file->getSize() ), $file->getMimeType() );
 389+ }
 390+
 391+ function getDimensionsString( $file ) {
 392+ $pages = $file->pageCount();
 393+ if ( $pages > 1 ) {
 394+ return wfMsg( 'widthheightpage', $file->getWidth(), $file->getHeight(), $pages );
 395+ } else {
 396+ return wfMsg( 'widthheight', $file->getWidth(), $file->getHeight() );
 397+ }
 398+ }
329399 }
330400
331401
 402+
Index: trunk/phase3/includes/media/SVG.php
@@ -14,7 +14,7 @@
1515 }
1616 }
1717
18 - function mustRender() {
 18+ function mustRender( $file ) {
1919 return true;
2020 }
2121
@@ -91,5 +91,12 @@
9292 function getThumbType( $ext, $mime ) {
9393 return array( 'png', 'image/png' );
9494 }
 95+
 96+ function getLongDesc( $file ) {
 97+ global $wgLang;
 98+ return wfMsg( 'svg-long-desc', $file->getWidth(), $file->getHeight(),
 99+ $wgLang->formatSize( $file->getSize() ) );
 100+ }
95101 }
96102
 103+
Index: trunk/phase3/languages/messages/MessagesEn.php
@@ -209,8 +209,10 @@
210210
211211 /**
212212 * Magic words
213 - * Customisable syntax for wikitext and elsewhere
 213+ * Customisable syntax for wikitext and elsewhere.
214214 *
 215+ * IDs must be valid identifiers, they can't contain hyphens.
 216+ *
215217 * Note to translators:
216218 * Please include the English words as synonyms. This allows people
217219 * from other wikis to contribute more easily.
@@ -287,10 +289,10 @@
288290 'img_sub' => array( 1, 'sub' ),
289291 'img_super' => array( 1, 'super', 'sup' ),
290292 'img_top' => array( 1, 'top' ),
291 - 'img_text-top' => array( 1, 'text-top' ),
 293+ 'img_text_top' => array( 1, 'text-top' ),
292294 'img_middle' => array( 1, 'middle' ),
293295 'img_bottom' => array( 1, 'bottom' ),
294 - 'img_text-bottom' => array( 1, 'text-bottom' ),
 296+ 'img_text_bottom' => array( 1, 'text-bottom' ),
295297 'int' => array( 0, 'INT:' ),
296298 'sitename' => array( 1, 'SITENAME' ),
297299 'ns' => array( 0, 'NS:' ),
@@ -2422,10 +2424,11 @@
24232425 'imagemaxsize' => 'Limit images on image description pages to:',
24242426 'thumbsize' => 'Thumbnail size:',
24252427 'widthheight' => '$1×$2', # only translate this message to other languages if you have to change it
 2428+'widthheightpage' => '$1×$2, $3 pages',
24262429 'file-info' => '(file size: $1, MIME type: $2)',
24272430 'file-info-size' => '($1 × $2 pixel, file size: $3, MIME type: $4)',
24282431 'file-nohires' => '<small>No higher resolution available.</small>',
2429 -'file-svg' => '<small>This is a lossless scalable vector image. Base size: $1 × $2 pixels.</small>',
 2432+'svg-long-desc' => '(SVG file, nominally $1 × $2 pixels, file size: $3)',
24302433 'show-big-image' => 'Full resolution',
24312434 'show-big-image-thumb' => '<small>Size of this preview: $1 × $2 pixels</small>',
24322435
@@ -2434,6 +2437,12 @@
24352438 'showhidebots' => '($1 bots)',
24362439 'noimages' => 'Nothing to see.',
24372440
 2441+'video-dims' => '$1, $2×$3',
 2442+# Used by Language::formatTimePeriod() to format lengths in the above messages
 2443+'seconds-abbrev' => 's',
 2444+'minutes-abbrev' => 'm',
 2445+'hours-abbrev' => 'h',
 2446+
24382447 # Bad image list
24392448 'bad_image_list' => 'The format is as follows:
24402449
Index: trunk/phase3/languages/messages/MessagesKk_cn.php
@@ -223,10 +223,10 @@
224224 'img_sub' => array( 1, 'استىلىعى', 'است', 'sub'),
225225 'img_super' => array( 1, 'ٷستٸلٸگٸ', 'ٷست', 'sup', 'super', 'sup' ),
226226 'img_top' => array( 1, 'ٷستٸنە', 'top' ),
227 - 'img_text-top' => array( 1, 'مٵتٸن-ٷستٸندە', 'text-top' ),
 227+ 'img_text_top' => array( 1, 'مٵتٸن-ٷستٸندە', 'text-top' ),
228228 'img_middle' => array( 1, 'ارالىعىنا', 'middle' ),
229229 'img_bottom' => array( 1, 'استىنا', 'bottom' ),
230 - 'img_text-bottom' => array( 1, 'مٵتٸن-استىندا', 'text-bottom' ),
 230+ 'img_text_bottom' => array( 1, 'مٵتٸن-استىندا', 'text-bottom' ),
231231 'int' => array( 0, 'ٸشكٸ:', 'INT:' ),
232232 'sitename' => array( 1, 'توراپاتاۋى', 'SITENAME' ),
233233 'ns' => array( 0, 'ەا:', 'ەسٸمايا:', 'NS:' ),
Index: trunk/phase3/languages/messages/MessagesKk_kz.php
@@ -215,10 +215,10 @@
216216 'img_sub' => array( 1, 'астылығы', 'аст', 'sub'),
217217 'img_super' => array( 1, 'үстілігі', 'үст', 'sup', 'super', 'sup' ),
218218 'img_top' => array( 1, 'үстіне', 'top' ),
219 - 'img_text-top' => array( 1, 'мәтін-үстінде', 'text-top' ),
 219+ 'img_text_top' => array( 1, 'мәтін-үстінде', 'text-top' ),
220220 'img_middle' => array( 1, 'аралығына', 'middle' ),
221221 'img_bottom' => array( 1, 'астына', 'bottom' ),
222 - 'img_text-bottom' => array( 1, 'мәтін-астында', 'text-bottom' ),
 222+ 'img_text_bottom' => array( 1, 'мәтін-астында', 'text-bottom' ),
223223 'int' => array( 0, 'ІШКІ:', 'INT:' ),
224224 'sitename' => array( 1, 'ТОРАПАТАУЫ', 'SITENAME' ),
225225 'ns' => array( 0, 'ЕА:', 'ЕСІМАЯ:', 'NS:' ),
Index: trunk/phase3/languages/messages/MessagesCs.php
@@ -133,10 +133,10 @@
134134 'img_sub' => array( 1, 'sub' ),
135135 'img_super' => array( 1, 'super', 'sup' ),
136136 'img_top' => array( 1, 'top' ),
137 - 'img_text-top' => array( 1, 'text-top' ),
 137+ 'img_text_top' => array( 1, 'text-top' ),
138138 'img_middle' => array( 1, 'middle' ),
139139 'img_bottom' => array( 1, 'bottom' ),
140 - 'img_text-bottom' => array( 1, 'text-bottom' ),
 140+ 'img_text_bottom' => array( 1, 'text-bottom' ),
141141 'int' => array( 0, 'INT:' ),
142142 'sitename' => array( 1, 'SITENAME', 'NÁZEVSERVERU' ),
143143 'ns' => array( 0, 'NS:' ),
Index: trunk/phase3/languages/messages/MessagesId.php
@@ -147,10 +147,10 @@
148148 'img_sub' => array( 1, 'sub' ),
149149 'img_super' => array( 1, 'super', 'sup' ),
150150 'img_top' => array( 1, 'atas', 'top' ),
151 - 'img_text-top' => array( 1, 'atas-teks', 'text-top' ),
 151+ 'img_text_top' => array( 1, 'atas-teks', 'text-top' ),
152152 'img_middle' => array( 1, 'tengah', 'middle' ),
153153 'img_bottom' => array( 1, 'bawah', 'bottom' ),
154 - 'img_text-bottom' => array( 1, 'bawah-teks', 'text-bottom' ),
 154+ 'img_text_bottom' => array( 1, 'bawah-teks', 'text-bottom' ),
155155 'int' => array( 0, 'INT:' ),
156156 'sitename' => array( 1, 'NAMASITUS', 'SITENAME' ),
157157 'ns' => array( 0, 'RN:', 'NS:' ),
Index: trunk/phase3/languages/messages/MessagesAr.php
@@ -156,10 +156,10 @@
157157 'img_page' => array( 1, "صفحة=$1", "صفحة $1", "page=$1", "page $1" ),
158158 'img_border' => array( 1, "حد", "حدود", "border" ),
159159 'img_top' => array( 1, "أعلى", "top" ),
160 - 'img_text-top' => array( 1, "نص_أعلى", "text-top" ),
 160+ 'img_text_top' => array( 1, "نص_أعلى", "text-top" ),
161161 'img_middle' => array( 1, "وسط", "middle" ),
162162 'img_bottom' => array( 1, "أسفل", "bottom" ),
163 - 'img_text-bottom' => array( 1, "نص_أسفل", "text-bottom" ),
 163+ 'img_text_bottom' => array( 1, "نص_أسفل", "text-bottom" ),
164164 'int' => array( 0, "محتوى:", "INT:" ),
165165 'sitename' => array( 1, "اسم_الموقع", "اسم_موقع", "SITENAME" ),
166166 'ns' => array( 0, "نط:", "NS:" ),
Index: trunk/phase3/languages/messages/MessagesBg.php
@@ -114,10 +114,10 @@
115115 'img_sub' => array( 1, 'sub' ),
116116 'img_super' => array( 1, 'super', 'sup' ),
117117 'img_top' => array( 1, 'top' ),
118 - 'img_text-top' => array( 1, 'text-top' ),
 118+ 'img_text_top' => array( 1, 'text-top' ),
119119 'img_middle' => array( 1, 'middle' ),
120120 'img_bottom' => array( 1, 'bottom' ),
121 - 'img_text-bottom' => array( 1, 'text-bottom' ),
 121+ 'img_text_bottom' => array( 1, 'text-bottom' ),
122122 'int' => array( 0, 'INT:', 'ВЪТР:'),
123123 'sitename' => array( 1, 'SITENAME', 'ИМЕНАСАЙТА'),
124124 'ns' => array( 0, 'NS:', 'ИП:' ),
Index: trunk/phase3/languages/messages/MessagesNl.php
@@ -165,10 +165,10 @@
166166 'img_sub' => array( 1, 'sub' ),
167167 'img_super' => array( 1, 'super', 'sup' ),
168168 'img_top' => array( 1, 'top', 'boven' ),
169 - 'img_text-top' => array( 1, 'text-top', 'tekst-boven' ),
 169+ 'img_text_top' => array( 1, 'text-top', 'tekst-boven' ),
170170 'img_middle' => array( 1, 'middle', 'midden' ),
171171 'img_bottom' => array( 1, 'bottom', 'beneden' ),
172 - 'img_text-bottom' => array( 1, 'text-bottom', 'tekst-beneden' ),
 172+ 'img_text_bottom' => array( 1, 'text-bottom', 'tekst-beneden' ),
173173 'int' => array( 0, 'INT:' ),
174174 'sitename' => array( 1, 'SITENAME', 'SITENAAM' ),
175175 'ns' => array( 0, 'NS:', 'NR:' ),
Index: trunk/phase3/languages/messages/MessagesKk_tr.php
@@ -216,10 +216,10 @@
217217 'img_sub' => array( 1, 'astılığı', 'ast', 'sub'),
218218 'img_super' => array( 1, 'üstiligi', 'üst', 'sup', 'super', 'sup' ),
219219 'img_top' => array( 1, 'üstine', 'top' ),
220 - 'img_text-top' => array( 1, 'mätin-üstinde', 'text-top' ),
 220+ 'img_text_top' => array( 1, 'mätin-üstinde', 'text-top' ),
221221 'img_middle' => array( 1, 'aralığına', 'middle' ),
222222 'img_bottom' => array( 1, 'astına', 'bottom' ),
223 - 'img_text-bottom' => array( 1, 'mätin-astında', 'text-bottom' ),
 223+ 'img_text_bottom' => array( 1, 'mätin-astında', 'text-bottom' ),
224224 'int' => array( 0, 'İŞKİ:', 'INT:' ),
225225 'sitename' => array( 1, 'TORAPATAWI', 'SITENAME' ),
226226 'ns' => array( 0, 'EA:', 'ESİMAYA:', 'NS:' ),
Index: trunk/phase3/languages/Language.php
@@ -1824,6 +1824,73 @@
18251825 wfProfileOut( __METHOD__ );
18261826 return array( $wikiUpperChars, $wikiLowerChars );
18271827 }
 1828+
 1829+ function formatTimePeriod( $seconds ) {
 1830+ if ( $seconds < 10 ) {
 1831+ return $this->formatNum( sprintf( "%.1f", $seconds ) ) . wfMsg( 'seconds-abbrev' );
 1832+ } elseif ( $seconds < 60 ) {
 1833+ return $this->formatNum( round( $seconds ) ) . wfMsg( 'seconds-abbrev' );
 1834+ } elseif ( $seconds < 3600 ) {
 1835+ return $this->formatNum( floor( $seconds / 60 ) ) . wfMsg( 'minutes-abbrev' ) .
 1836+ $this->formatNum( round( fmod( $seconds, 60 ) ) ) . wfMsg( 'seconds-abbrev' );
 1837+ } else {
 1838+ $hours = floor( $seconds / 3600 );
 1839+ $minutes = floor( ( $seconds - $hours * 3600 ) / 60 );
 1840+ $secondsPart = round( $seconds - $hours * 3600 - $minutes * 60 );
 1841+ return $this->formatNum( $hours ) . wfMsg( 'hours-abbrev' ) .
 1842+ $this->formatNum( $minutes ) . wfMsg( 'minutes-abbrev' ) .
 1843+ $this->formatNum( $secondsPart ) . wfMsg( 'seconds-abbrev' );
 1844+ }
 1845+ }
 1846+
 1847+ function formatBitrate( $bps ) {
 1848+ $units = array( 'bps', 'kbps', 'Mbps', 'Gbps' );
 1849+ if ( $bps <= 0 ) {
 1850+ return $this->formatNum( $bps ) . $units[0];
 1851+ }
 1852+ $unitIndex = floor( log10( $bps ) / 3 );
 1853+ $mantissa = $bps / pow( 1000, $unitIndex );
 1854+ if ( $mantissa < 10 ) {
 1855+ $mantissa = round( $mantissa, 1 );
 1856+ } else {
 1857+ $mantissa = round( $mantissa );
 1858+ }
 1859+ return $this->formatNum( $mantissa ) . $units[$unitIndex];
 1860+ }
 1861+
 1862+ /**
 1863+ * Format a size in bytes for output, using an appropriate
 1864+ * unit (B, KB, MB or GB) according to the magnitude in question
 1865+ *
 1866+ * @param $size Size to format
 1867+ * @return string Plain text (not HTML)
 1868+ */
 1869+ function formatSize( $size ) {
 1870+ // For small sizes no decimal places necessary
 1871+ $round = 0;
 1872+ if( $size > 1024 ) {
 1873+ $size = $size / 1024;
 1874+ if( $size > 1024 ) {
 1875+ $size = $size / 1024;
 1876+ // For MB and bigger two decimal places are smarter
 1877+ $round = 2;
 1878+ if( $size > 1024 ) {
 1879+ $size = $size / 1024;
 1880+ $msg = 'size-gigabytes';
 1881+ } else {
 1882+ $msg = 'size-megabytes';
 1883+ }
 1884+ } else {
 1885+ $msg = 'size-kilobytes';
 1886+ }
 1887+ } else {
 1888+ $msg = 'size-bytes';
 1889+ }
 1890+ $size = round( $size, $round );
 1891+ $text = $this->getMessageFromDB( $msg );
 1892+ return str_replace( '$1', $this->formatNum( $size ), $text );
 1893+ }
18281894 }
18291895
18301896
 1897+
Index: trunk/phase3/RELEASE-NOTES
@@ -173,6 +173,7 @@
174174 * (bug 10872) Fall back to sane defaults when generating protection selector
175175 labels for custom restriction levels
176176 * Show edit count in user preferences
 177+* Improved support for audio/video extensions
177178
178179 == Bugfixes since 1.10 ==
179180
Index: trunk/extensions/OggHandler/OggHandler.php
@@ -3,27 +3,34 @@
44 class OggHandler extends MediaHandler {
55 const OGG_METADATA_VERSION = 1;
66
 7+ var $videoTypes = array( 'Theora' );
 8+ var $audioTypes = array( 'Vorbis', 'Speex', 'FLAC' );
 9+
710 function isEnabled() {
8 - if ( !class_exists( 'File_Ogg' ) ) {
9 - return include( 'File/Ogg.php' );
10 - } else {
11 - return true;
12 - }
 11+ return true;
1312 }
1413
 14+ function getParamMap() {
 15+ // TODO: add thumbtime, noplayer
 16+ return array( 'img_width' => 'width' );
 17+ }
 18+
1519 function validateParam( $name, $value ) {
1620 // TODO
17 - return false;
 21+ return true;
1822 }
1923
2024 function makeParamString( $params ) {
 25+ // No parameters just yet, the thumbnails are always full-size
 26+ return '';
 27+ /*
2128 $s = '';
2229 foreach ( $params as $name => $value ) {
2330 if ( $s !== '' ) {
2431 $s .= '-';
2532 }
2633 $s .= "$name=$value";
27 - }
 34+ }*/
2835 }
2936
3037 function parseParamString( $str ) {
@@ -36,13 +43,33 @@
3744 return true;
3845 }
3946
40 - function getImageSize( $image, $path ) {
41 - // TODO
42 - return false;
 47+ function getImageSize( $file, $path, $metadata = false ) {
 48+ // Just return the size of the first video stream
 49+ if ( $metadata === false ) {
 50+ $metadata = $file->getMetadata();
 51+ }
 52+ $metadata = $this->unpackMetadata( $metadata );
 53+ if ( isset( $metadata['error'] ) ) {
 54+ return false;
 55+ }
 56+ foreach ( $metadata['streams'] as $stream ) {
 57+ if ( in_array( $stream['type'], $this->videoTypes ) ) {
 58+ return array(
 59+ $stream['header']['PICW'],
 60+ $stream['header']['PICH']
 61+ );
 62+ }
 63+ }
 64+ return array( false, false );
4365 }
4466
4567 function getMetadata( $image, $path ) {
4668 $metadata = array( 'version' => self::OGG_METADATA_VERSION );
 69+
 70+ if ( !class_exists( 'File_Ogg' ) ) {
 71+ return require( 'File/Ogg.php' );
 72+ }
 73+
4774 try {
4875 $f = new File_Ogg( $path );
4976 $streams = array();
@@ -50,15 +77,19 @@
5178 foreach ( $streamIDs as $streamID ) {
5279 $stream = $f->getStream( $streamID );
5380 $streams[$streamID] = array(
 81+ 'serial' => $stream->getSerial(),
 82+ 'group' => $stream->getGroup(),
5483 'type' => $stream->getType(),
5584 'vendor' => $stream->getVendor(),
5685 'length' => $stream->getLength(),
 86+ 'size' => $stream->getSize(),
5787 'header' => $stream->getHeader(),
5888 'comments' => $stream->getComments()
5989 );
6090 }
6191 }
6292 $metadata['streams'] = $streams;
 93+ $metadata['length'] = $f->getLength();
6394 } catch ( PEAR_Exception $e ) {
6495 // File not found, invalid stream, etc.
6596 $metadata['error'] = array(
@@ -69,25 +100,87 @@
70101 return serialize( $metadata );
71102 }
72103
 104+ function unpackMetadata( $metadata ) {
 105+ $unser = @unserialize( $metadata );
 106+ if ( isset( $unser['version'] ) && $unser['version'] == self::OGG_METADATA_VERSION ) {
 107+ return $unser;
 108+ } else {
 109+ return false;
 110+ }
 111+ }
 112+
73113 function getMetadataType( $image ) {
74114 return 'ogg';
75115 }
76116
77117 function isMetadataValid( $image, $metadata ) {
78 - $unser = @unserialize( $metadata );
79 - return isset( $unser['version'] ) && $unser['version'] === self::OGG_METADATA_VERSION;
 118+ return $this->unpackMetadata( $metadata ) !== false;
80119 }
 120+
 121+ function getThumbType( $ext, $mime ) {
 122+ return array( 'jpg', 'image/jpeg' );
 123+ }
81124
82 - function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ) {
83 - // TODO
84 - return new MediaTransformError( 'Not yet implemented', 200 );
85 - }
 125+ function doTransform( $file, $dstPath, $dstUrl, $params, $flags = 0 ) {
 126+ global $wgFFmpegLocation;
 127+ $width = $params['width'];
 128+ $srcWidth = $file->getWidth();
 129+ $srcHeight = $file->getHeight();
 130+ $height = $srcWidth == 0 ? $srcHeight : $width * $srcHeight / $srcWidth;
 131+ $length = $this->getLength( $file );
86132
87 - function canRender() {
88 - // TODO
89 - return false;
 133+ if ( $srcHeight == 0 || $srcWidth == 0 ) {
 134+ // Make audio player
 135+ if ( empty( $params['width'] ) ) {
 136+ $width = 200;
 137+ } else {
 138+ $width = $params['width'];
 139+ }
 140+ $height = 20;
 141+ return new OggAudioDisplay( $file->getURL(), $width, $height, $length );
 142+ }
 143+
 144+ if ( $flags & self::TRANSFORM_LATER ) {
 145+ return new OggVideoDisplay( $file->getURL(), $dstUrl, $width, $height, $length );
 146+ }
 147+
 148+ wfMkdirParents( dirname( $dstPath ) );
 149+
 150+ wfDebug( "Creating video thumbnail at $dstPath\n" );
 151+
 152+ $cmd = wfEscapeShellArg( $wgFFmpegLocation ) .
 153+ ' -i ' . wfEscapeShellArg( $file->getPath() ) .
 154+ # MJPEG, that's the same as JPEG except it's supported by the windows build of ffmpeg
 155+ # No audio, one frame
 156+ ' -f mjpeg -an -vframes 1' .
 157+ # Seek to midpoint, it tends to be more interesting than the fade in at the start
 158+ ' -ss ' . intval( $length / 2 ) . ' ' .
 159+ wfEscapeShellArg( $dstPath ) . ' 2>&1';
 160+
 161+ $retval = 0;
 162+ $returnText = wfShellExec( $cmd, $retval );
 163+
 164+ if ( $retval ) {
 165+ // Filter nonsense
 166+ $lines = explode( "\n", str_replace( "\r\n", "\n", $returnText ) );
 167+ if ( substr( $lines[0], 0, 6 ) == 'FFmpeg' ) {
 168+ for ( $i = 1; $i < count( $lines ); $i++ ) {
 169+ if ( substr( $lines[$i], 0, 2 ) != ' ' ) {
 170+ break;
 171+ }
 172+ }
 173+ $lines = array_slice( $lines, $i );
 174+ }
 175+ // Return error box
 176+ return new MediaTransformError( 'thumbnail_error', $width, $height, implode( "\n", $lines ) );
 177+ }
 178+ return new OggVideoDisplay( $file->getURL(), $dstUrl, $width, $height, $length );
90179 }
91180
 181+ function canRender() { return true; }
 182+ function mustRender( $file ) { return true; }
 183+
 184+ /*
92185 function formatMetadata( $image, $metadata ) {
93186 if ( !$this->isMetadataValid( $image, $metadata ) ) {
94187 return false;
@@ -106,6 +199,7 @@
107200 self::addMeta( $formatted, 'visible', 'ogg', 'type', $stream['type'], $n );
108201 self::addMeta( $formatted, 'visible', 'ogg', 'vendor', $stream['vendor'], $n );
109202 self::addMeta( $formatted, 'visible', 'ogg', 'length', $stream['length'], $n );
 203+ self::addMeta( $formatted, 'visible', 'ogg', 'size', $stream['size'], $n );
110204
111205 foreach ( $stream['header'] as $name => $value ) {
112206 self::addMeta( $formatted, 'visible', $type, $name, $value, $n );
@@ -116,12 +210,212 @@
117211 }
118212 }
119213 return $formatted;
 214+ }*/
 215+
 216+ function getLength( $file ) {
 217+ $metadata = $this->unpackMetadata( $file->getMetadata() );
 218+ if ( !$metadata || isset( $metadata['error'] ) ) {
 219+ return 0;
 220+ } else {
 221+ return $metadata['length'];
 222+ }
120223 }
121224
 225+ function getStreamTypes( $file ) {
 226+ $streamTypes = '';
 227+ $metadata = $this->unpackMetadata( $file->getMetadata() );
 228+ if ( !$metadata || isset( $metadata['error'] ) ) {
 229+ return false;
 230+ }
 231+ foreach ( $metadata['streams'] as $stream ) {
 232+ $streamTypes[$stream['type']] = true;
 233+ }
 234+ return array_keys( $streamTypes );
 235+ }
122236
 237+ function getShortDesc( $file ) {
 238+ global $wgLang;
 239+ wfLoadExtensionMessages( 'OggHandler' );
 240+ $streamTypes = $this->getStreamTypes( $file );
 241+ if ( !$streamTypes ) {
 242+ return parent::getShortDesc( $file );
 243+ }
 244+ if ( array_intersect( $streamTypes, $this->videoTypes ) ) {
 245+ // Count multiplexed audio/video as video for short descriptions
 246+ $msg = 'ogg-short-video';
 247+ } elseif ( array_intersect( $streamTypes, $this->audioTypes ) ) {
 248+ $msg = 'ogg-short-audio';
 249+ } else {
 250+ $msg = 'ogg-short-general';
 251+ }
 252+ return wfMsg( $msg, implode( '/', $streamTypes ),
 253+ $wgLang->formatTimePeriod( $this->getLength( $file ) ) );
 254+ }
 255+
 256+ function getLongDesc( $file ) {
 257+ global $wgLang;
 258+ wfLoadExtensionMessages( 'OggHandler' );
 259+ $streamTypes = $this->getStreamTypes( $file );
 260+ if ( !$streamTypes ) {
 261+ $unpacked = $this->unpackMetadata( $file->getMetadata() );
 262+ return wfMsg( 'ogg-long-error', $unpacked['error']['message'] );
 263+ }
 264+ if ( array_intersect( $streamTypes, $this->videoTypes ) ) {
 265+ if ( array_intersect( $streamTypes, $this->audioTypes ) ) {
 266+ $msg = 'ogg-long-multiplexed';
 267+ } else {
 268+ $msg = 'ogg-long-video';
 269+ }
 270+ } elseif ( array_intersect( $streamTypes, $this->audioTypes ) ) {
 271+ $msg = 'ogg-long-audio';
 272+ } else {
 273+ $msg = 'ogg-long-general';
 274+ }
 275+ $size = 0;
 276+ $unpacked = $this->unpackMetadata( $file->getMetadata() );
 277+ if ( !$unpacked || isset( $metadata['error'] ) ) {
 278+ $length = 0;
 279+ } else {
 280+ $length = $this->getLength( $file );
 281+ foreach ( $unpacked['streams'] as $stream ) {
 282+ $size += $stream['size'];
 283+ }
 284+ }
 285+ $bitrate = $length == 0 ? 0 : $size / $length * 8;
 286+ return wfMsg( $msg, implode( '/', $streamTypes ),
 287+ $wgLang->formatTimePeriod( $length ),
 288+ $wgLang->formatBitrate( $bitrate ),
 289+ $wgLang->formatNum( $file->getWidth() ),
 290+ $wgLang->formatNum( $file->getHeight() )
 291+ );
 292+ }
 293+
 294+ function getDimensionsString( $file ) {
 295+ global $wgLang;
 296+ wfLoadExtensionMessages( 'OggHandler' );
 297+ if ( $file->getWidth() ) {
 298+ return wfMsg( 'video-dims', $wgLang->formatTimePeriod( $this->getLength( $file ) ),
 299+ $wgLang->formatNum( $file->getWidth() ),
 300+ $wgLang->formatNum( $file->getHeight() ) );
 301+ } else {
 302+ return $wgLang->formatTimePeriod( $this->getLength( $file ) );
 303+ }
 304+ }
 305+
 306+ function setHeaders( $out ) {
 307+ global $wgScriptPath;
 308+ if ( $out->hasHeadItem( 'OggHandler' ) ) {
 309+ return;
 310+ }
 311+
 312+ wfLoadExtensionMessages( 'OggHandler' );
 313+
 314+ $msgNames = array( 'ogg-play', 'ogg-pause', 'ogg-stop', 'ogg-no-player',
 315+ 'ogg-player-videoElement', 'ogg-player-oggPlugin', 'ogg-player-cortado', 'ogg-player-vlcPlugin',
 316+ 'ogg-player-vlcActiveX', 'ogg-using-player' );
 317+ $msgValues = array_map( 'wfMsg', $msgNames );
 318+ $jsMsgs = Xml::encodeJsVar( (object)array_combine( $msgNames, $msgValues ) );
 319+ $encCortadoUrl = Xml::encodeJsVar( "$wgScriptPath/extensions/OggHandler/cortado-ovt-stripped-0.2.2.jar" );
 320+
 321+ $out->addHeadItem( 'OggHandler', <<<EOT
 322+<script type="text/javascript" src="$wgScriptPath/extensions/OggHandler/OggPlayer.js"></script>
 323+<script type="text/javascript">
 324+wgOggPlayer.msg = $jsMsgs;
 325+wgOggPlayer.cortadoUrl = $encCortadoUrl;
 326+//wgOggPlayer.forcePlayer = 'cortado';
 327+</script>
 328+EOT
 329+ );
 330+
 331+ }
 332+
 333+ function parserTransformHook( $parser, $file ) {
 334+ if ( isset( $parser->mOutput->hasOggTransform ) ) {
 335+ return;
 336+ }
 337+ $parser->mOutput->hasOggTransform = true;
 338+ $parser->mOutput->addOutputHook( 'OggHandler' );
 339+ }
 340+
 341+ static function outputHook( $outputPage, $parserOutput, $data ) {
 342+ $instance = MediaHandler::getHandler( 'application/ogg' );
 343+ if ( $instance ) {
 344+ $instance->setHeaders( $outputPage );
 345+ }
 346+ }
123347 }
124348
 349+class OggTransformOutput extends MediaTransformOutput {
 350+ static $serial = 0;
125351
 352+ function __construct( $videoUrl, $thumbUrl, $width, $height, $length, $isVideo ) {
 353+ $this->videoUrl = $videoUrl;
 354+ $this->thumbUrl = $thumbUrl;
 355+ $this->width = round( $width );
 356+ $this->height = round( $height );
 357+ $this->length = round( $length );
 358+ $this->isVideo = $isVideo;
 359+ }
126360
 361+ function toHtml( $attribs = array() , $linkAttribs = false ) {
 362+ wfLoadExtensionMessages( 'OggHandler' );
127363
 364+ OggTransformOutput::$serial++;
 365+
 366+ $encThumbUrl = htmlspecialchars( $this->thumbUrl );
 367+
 368+ if ( substr( $this->videoUrl, 0, 4 ) != 'http' ) {
 369+ global $wgServer;
 370+ $encUrl = Xml::encodeJsVar( $wgServer . $this->videoUrl );
 371+ } else {
 372+ $encUrl = Xml::encodeJsVar( $this->videoUrl );
 373+ }
 374+ #$encUrl = htmlspecialchars( $encUrl );
 375+ $length = intval( $this->length );
 376+ $width = intval( $this->width );
 377+ $height = intval( $this->height );
 378+ if ( $this->isVideo ) {
 379+ $msgStartPlayer = wfMsg( 'ogg-play-video' );
 380+ $thumb =
 381+ Xml::tags( 'a', $linkAttribs,
 382+ Xml::element( 'img',
 383+ array(
 384+ 'src' => $this->thumbUrl,
 385+ 'width' => $width,
 386+ 'height' => $height,
 387+ ) + $attribs,
 388+ null )
 389+ ) .
 390+ "<br/>\n";
 391+ } else {
 392+ $msgStartPlayer = wfMsg( 'ogg-play-sound' );
 393+ $thumb = '';
 394+ }
 395+ $id = "ogg_player_" . OggTransformOutput::$serial;
 396+
 397+ $s = Xml::tags( 'div', array( 'id' => $id ),
 398+ $thumb .
 399+ Xml::element( 'button',
 400+ array(
 401+ 'onclick' => "wgOggPlayer.init(false, '$id', $encUrl, $width, $height, $length);",
 402+ ),
 403+ $msgStartPlayer
 404+ )
 405+ );
 406+ return $s;
 407+ }
 408+}
 409+
 410+class OggVideoDisplay extends OggTransformOutput {
 411+ function __construct( $videoUrl, $thumbUrl, $width, $height, $length ) {
 412+ parent::__construct( $videoUrl, $thumbUrl, $width, $height, $length, true );
 413+ }
 414+}
 415+
 416+class OggAudioDisplay extends OggTransformOutput {
 417+ function __construct( $videoUrl, $width, $height, $length ) {
 418+ parent::__construct( $videoUrl, false, $width, $height, $length, false );
 419+ }
 420+}
 421+
128422 ?>
Index: trunk/extensions/OggHandler/OggPlayer.js
@@ -0,0 +1,262 @@
 2+
 3+// This is a global configuration object which can embed multiple video instances
 4+var wgOggPlayer = {
 5+ 'detectionDone': false,
 6+ 'vlcActiveX': false,
 7+
 8+ // List of players in order of preference
 9+ // Downpreffed VLC because it crashes my browser all the damn time -- TS
 10+ 'players': ['videoElement', 'oggPlugin', 'cortado', 'vlcPlugin', 'vlcActiveX'],
 11+
 12+ 'clientSupports': {},
 13+
 14+ // Configuration from MW
 15+ 'msg': {},
 16+ 'cortadoUrl' : '',
 17+ 'showPlayerSelect': true,
 18+
 19+ // Main entry point: initialise a video player
 20+ // Player will be created as a child of the given ID
 21+ // There may be multiple players in a document
 22+ 'init': function ( player, id, videoUrl, width, height, length ) {
 23+ var elt = document.getElementById( id );
 24+
 25+ this.detect( elt );
 26+
 27+ if ( !player ) {
 28+ // See if there is a cookie specifying a preferred player
 29+ var cookieName = "ogg_player=";
 30+ var cookiePos = document.cookie.indexOf(cookieName);
 31+ if (cookiePos > -1) {
 32+ player = document.cookie.substr( cookiePos + cookieName.length );
 33+ var semicolon = player.indexOf( ";" );
 34+ if ( semicolon > -1 ) {
 35+ player = player.substr( cookiePos, semicolon );
 36+ }
 37+ }
 38+ }
 39+
 40+ if ( !player || !this.clientSupports[player] ) {
 41+ for ( var i = 0; i < this.players.length; i++ ) {
 42+ if ( this.clientSupports[this.players[i]] ) {
 43+ player = this.players[i];
 44+ break;
 45+ }
 46+ }
 47+ }
 48+
 49+ elt.innerHTML = '';
 50+
 51+ switch ( player ) {
 52+ case 'videoElement':
 53+ this.embedVideoElement( elt, videoUrl, width, height, length );
 54+ break;
 55+ case 'oggPlugin':
 56+ this.embedOggPlugin( elt, videoUrl, width, height, length );
 57+ break;
 58+ case 'vlcPlugin':
 59+ this.embedVlcPlugin( elt, videoUrl, width, height, length );
 60+ break;
 61+ case 'vlcActiveX':
 62+ this.embedVlcActiveX( elt, videoUrl, width, height, length );
 63+ break;
 64+ case 'cortado':
 65+ this.embedCortado( elt, videoUrl, width, height, length );
 66+ break;
 67+ default:
 68+ elt.innerHTML = this.msg['ogg-no-player'] + '<br/>';
 69+ }
 70+ if ( this.showPlayerSelect ) {
 71+ elt.appendChild( document.createElement( 'br' ) );
 72+ var label = document.createElement( 'label' );
 73+ label.setAttribute( 'class', 'ogg_player_using' );
 74+ label.appendChild( document.createTextNode( ' ' + this.msg['ogg-using-player'] ) );
 75+ label.appendChild( this.makePlayerSelect( player, id, videoUrl, width, height, length ) );
 76+ elt.appendChild( label );
 77+ }
 78+ },
 79+
 80+ // Detect client capabilities
 81+ 'detect': function( elt ) {
 82+ if (this.detectionDone) {
 83+ return;
 84+ }
 85+ this.detectionDone = true;
 86+
 87+ // MSIE VLC
 88+ try {
 89+ var vlcObj = new ActiveXObject( "VideoLAN.VLCPlugin.2" );
 90+ this.clientSupports['vlcActiveX'] = true;
 91+ } catch ( e ) {}
 92+
 93+ // <video> element
 94+ elt.innerHTML = '<video id="testvideo"></video>\n';
 95+ var testvideo = document.getElementById('testvideo');
 96+ if (testvideo && testvideo.play) {
 97+ this.clientSupports['videoElement'] = true;
 98+ }
 99+
 100+ // Mozilla plugins
 101+ if(navigator.mimeTypes && navigator.mimeTypes.length > 0) {
 102+ for ( var i = 0; i < navigator.mimeTypes.length; i++) {
 103+ var type = navigator.mimeTypes[i].type;
 104+ var pluginName = navigator.mimeTypes[i].enabledPlugin ? navigator.mimeTypes[i].enabledPlugin.name : '';
 105+ if(type.indexOf("application/ogg") > -1 &&
 106+ pluginName != "VLC multimedia plugin" && pluginName != "VLC Multimedia Plugin")
 107+ {
 108+ this.clientSupports['oggPlugin'] = true;
 109+ }
 110+ if(navigator.mimeTypes[i].type.indexOf("application/x-vlc-plugin") > -1) {
 111+ this.clientSupports['vlcPlugin'] = true;
 112+ }
 113+ }
 114+ }
 115+
 116+ // Java
 117+ this.clientSupports['cortado'] = navigator.javaEnabled();
 118+ },
 119+
 120+ 'makePlayerSelect' : function ( selectedPlayer, id, videoUrl, width, height, length ) {
 121+ var select = document.createElement( 'select' );
 122+ for ( var player in this.clientSupports ) {
 123+ var option = document.createElement( 'option' );
 124+ option.value = player;
 125+ option.appendChild( document.createTextNode( this.msg['ogg-player-' + player] ) );
 126+ if ( selectedPlayer == player ) {
 127+ option.selected = true;
 128+ }
 129+ select.appendChild( option );
 130+ }
 131+ select.value = selectedPlayer;
 132+
 133+ var me = this;
 134+ select.onchange = function () {
 135+ var player = select.value;
 136+ document.cookie = "ogg_player=" + player;
 137+ me.init( player, id, videoUrl, width, height, length );
 138+ };
 139+ return select;
 140+ },
 141+
 142+ 'newButton': function ( caption, callback ) {
 143+ var elt = document.createElement('input');
 144+ elt.type = 'button';
 145+ elt.value = this.msg[caption];
 146+ elt.onclick = callback;
 147+ return elt;
 148+ },
 149+
 150+ 'newPlayButton': function ( videoElt ) {
 151+ return this.newButton( 'ogg-play', function () { videoElt.play(); } );
 152+ },
 153+
 154+ 'newPauseButton': function ( videoElt ) {
 155+ return this.newButton( 'ogg-pause', function () { videoElt.pause(); } );
 156+ },
 157+
 158+ 'newStopButton': function ( videoElt ) {
 159+ return this.newButton( 'ogg-stop', function () { videoElt.stop(); } );
 160+ },
 161+
 162+ 'embedVideoElement': function ( elt, videoUrl, width, height, length ) {
 163+ var videoElt = document.createElement('video');
 164+ videoElt.setAttribute('width', width);
 165+ videoElt.setAttribute('height', height);
 166+ videoElt.setAttribute('src', videoUrl);
 167+ videoElt.setAttribute('autoplay', '1');
 168+ videoElt.setAttribute('controls', '1');
 169+ elt.appendChild(videoElt);
 170+
 171+ // Try to detect implementations that don't support controls
 172+ // This works for the Opera test build
 173+ if (!videoElt.controls) {
 174+ elt.appendChild( document.createElement( 'br' ) );
 175+ elt.appendChild( this.newPlayButton( videoElt ) );
 176+ elt.appendChild( this.newPauseButton( videoElt ) );
 177+ elt.appendChild( this.newStopButton( videoElt ) );
 178+ //videoElt.play();
 179+ }
 180+ },
 181+
 182+ 'embedOggPlugin': function ( elt, videoUrl, width, height, length ) {
 183+ var videoElt = document.createElement( 'object' );
 184+ videoElt.setAttribute( 'type', 'application/ogg' );
 185+ videoElt.setAttribute( 'width', width );
 186+ videoElt.setAttribute( 'height', height );
 187+ videoElt.setAttribute( 'data', videoUrl );
 188+ elt.appendChild(videoElt);
 189+ },
 190+
 191+ 'embedVlcPlugin' : function ( elt, videoUrl, width, height, length ) {
 192+ var videoElt = document.createElement( 'object' );
 193+ videoElt.setAttribute( 'type', 'application/x-vlc-plugin' );
 194+ videoElt.setAttribute( 'width', width );
 195+ videoElt.setAttribute( 'height', height );
 196+ videoElt.setAttribute( 'data', videoUrl );
 197+ elt.appendChild(videoElt);
 198+ elt.appendChild( document.createElement( 'br' ) );
 199+ // TODO: seek bar
 200+ elt.appendChild( this.newPlayButton( videoElt ) );
 201+ elt.appendChild( this.newPauseButton( videoElt ) );
 202+ elt.appendChild( this.newStopButton( videoElt ) );
 203+ },
 204+
 205+ 'embedVlcActiveX' : function ( elt, videoUrl, width, height, length ) {
 206+ var id = elt.id + "_obj";
 207+
 208+ // I ran into some hairy object initialisation issues when trying to
 209+ // create this object with DOM functions. If anyone knows a better way
 210+ // than innerHTML, please let me know. -- TS
 211+ elt.innerHTML +=
 212+ "<object id='" + id + "'" +
 213+ " classid='clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921'" +
 214+ " codebase='http://downloads.videolan.org/pub/videolan/vlc/latest/win32/axvlc.cab#Version=0,8,6,0'" +
 215+ " width='" + width + "'" +
 216+ " height='" + height + "'>" +
 217+ "<param name='mrl' value='" + videoUrl.replace(/&/, '&amp;') + "'/>" +
 218+ "</object>" ;
 219+
 220+ var videoElt = document.getElementById( id );
 221+ elt.appendChild( document.createElement( 'br' ) );
 222+ // TODO: seek bar
 223+ elt.appendChild( this.newPlayButton( videoElt.playlist ) );
 224+ // FIXME: playlist.pause() doesn't work
 225+ elt.appendChild( this.newStopButton( videoElt.playlist ) );
 226+ },
 227+
 228+ 'embedCortado' : function ( elt, videoUrl, width, height, length ) {
 229+ var videoElt;
 230+ if ( false ) {
 231+ // Use <object>
 232+ videoElt = document.createElement( 'object' );
 233+ videoElt.setAttribute( 'codetype', 'application/x-java-applet' );
 234+ videoElt.setAttribute( 'classid', 'com.fluendo.player.Cortado.class' );
 235+ } else {
 236+ // Use <applet>
 237+ videoElt = document.createElement( 'applet' );
 238+ videoElt.setAttribute( 'code', 'com.fluendo.player.Cortado.class' );
 239+ }
 240+ videoElt.setAttribute( 'width', width );
 241+ videoElt.setAttribute( 'height', height );
 242+ videoElt.setAttribute( 'archive', this.cortadoUrl );
 243+
 244+ var param = document.createElement( 'param' );
 245+ this.addParam( videoElt, 'url', videoUrl );
 246+ this.addParam( videoElt, 'duration', length );
 247+ this.addParam( videoElt, 'seekable', 'true' );
 248+ this.addParam( videoElt, 'autoPlay', 'true' );
 249+ this.addParam( videoElt, 'showStatus', 'show' );
 250+ this.addParam( videoElt, 'statusHeight', 18 );
 251+ elt.appendChild( videoElt );
 252+ },
 253+
 254+ 'addParam': function ( elt, name, value ) {
 255+ var param = document.createElement( 'param' );
 256+ param.setAttribute( 'name', name );
 257+ param.setAttribute( 'value', value );
 258+ elt.appendChild( param );
 259+ }
 260+};
 261+
 262+
 263+
Property changes on: trunk/extensions/OggHandler/OggPlayer.js
___________________________________________________________________
Added: svn:eol-style
1264 + native
Index: trunk/extensions/OggHandler/ext.php
@@ -0,0 +1,18 @@
 2+<?php
 3+
 4+$oggDir = dirname(__FILE__);
 5+$wgAutoloadClasses['OggHandler'] = "$oggDir/Handler.php";
 6+$wgMediaHandlers['application/ogg'] = 'OggHandler';
 7+if ( !in_array( 'ogg', $wgFileExtensions ) ) {
 8+ $wgFileExtensions[] = 'ogg';
 9+}
 10+ini_set( 'include_path',
 11+ "$oggDir/PEAR/File_Ogg" .
 12+ PATH_SEPARATOR .
 13+ ini_get( 'include_path' ) );
 14+
 15+$wgFFmpegLocation = 'ffmpeg';
 16+$wgExtensionMessagesFiles['OggHandler'] = "$oggDir/OggHandler.i18n.php";
 17+$wgParserOutputHooks['OggHandler'] = array( 'OggHandler', 'outputHook' );
 18+
 19+?>
Property changes on: trunk/extensions/OggHandler/ext.php
___________________________________________________________________
Added: svn:eol-style
120 + native
Index: trunk/extensions/OggHandler/OggHandler.i18n.php
@@ -0,0 +1,26 @@
 2+<?php
 3+
 4+$messages = array(
 5+ 'en' => array(
 6+ 'ogg-short-audio' => 'Ogg $1 sound file, $2',
 7+ 'ogg-short-video' => 'Ogg $1 video file, $2',
 8+ 'ogg-short-general' => 'Ogg $1 media file, $2',
 9+ 'ogg-long-audio' => '(Ogg $1 sound file, length $2, $3)',
 10+ 'ogg-long-video' => '(Ogg $1 video file, length $2, $4×$5 pixels, $3)',
 11+ 'ogg-long-multiplexed' => '(Ogg multiplexed audio/video file, $1, length $2, $4×$5 pixels, $3 overall)',
 12+ 'ogg-long-general' => '(Ogg media file, length $2, $3)',
 13+ 'ogg-long-error' => '(Invalid ogg file: $1)',
 14+ 'ogg-play' => 'Play',
 15+ 'ogg-pause' => 'Pause',
 16+ 'ogg-stop' => 'Stop',
 17+ 'ogg-play-video' => 'Play video',
 18+ 'ogg-play-sound' => 'Play sound',
 19+ 'ogg-no-player' => 'Sorry, no video player is available',
 20+ 'ogg-player-videoElement' => '<video> element',
 21+ 'ogg-player-oggPlugin' => 'Ogg plugin',
 22+ 'ogg-player-cortado' => 'Cortado (Java)',
 23+ 'ogg-player-vlcPlugin' => 'VLC (Mozilla)',
 24+ 'ogg-player-vlcActiveX' => 'VLC (ActiveX)',
 25+ 'ogg-using-player' => 'Using player: ',
 26+ ),
 27+);
Property changes on: trunk/extensions/OggHandler/OggHandler.i18n.php
___________________________________________________________________
Added: svn:eol-style
128 + native

Follow-up revisions

RevisionCommit summaryAuthorDate
r24866Merged revisions 24755-24865 via svnmerge from...david23:08, 16 August 2007
r24966Fix a regression from r24808:...raymond21:37, 20 August 2007
r25016Merged revisions 24866-25015 via svnmerge from...david23:06, 21 August 2007
r25188Fix a regression from r24808 for some image options....raymond11:24, 27 August 2007
r25223Merged revisions 25126-25214 via svnmerge from...david07:39, 28 August 2007
r70909Follow up r24808....platonides18:56, 11 August 2010

Status & tagging log