Index: trunk/phase3/includes/MagicWord.php |
— | — | @@ -386,6 +386,181 @@ |
387 | 387 | function isCaseSensitive() { |
388 | 388 | return $this->mCaseSensitive; |
389 | 389 | } |
| 390 | + |
| 391 | + function getId() { |
| 392 | + return $this->mId; |
| 393 | + } |
390 | 394 | } |
391 | 395 | |
| 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; |
392 | 403 | |
| 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 @@ |
434 | 434 | return $s; |
435 | 435 | } |
436 | 436 | |
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 ) |
451 | 454 | { |
| 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() ) { |
452 | 500 | 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 | + } |
453 | 505 | |
454 | | - $img = wfFindFile( $nt, $time ); |
| 506 | + // Shortcuts |
| 507 | + $fp =& $frameParams; |
| 508 | + $hp =& $handlerParams; |
455 | 509 | |
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'] = ''; |
460 | 514 | |
461 | | - $error = $prefix = $postfix = ''; |
462 | | - $page = isset( $params['page'] ) ? $params['page'] : false; |
| 515 | + $prefix = $postfix = ''; |
463 | 516 | |
464 | | - if ( 'center' == $align ) |
| 517 | + if ( 'center' == $fp['align'] ) |
465 | 518 | { |
466 | 519 | $prefix = '<div class="center">'; |
467 | 520 | $postfix = '</div>'; |
468 | | - $align = 'none'; |
| 521 | + $fp['align'] = 'none'; |
469 | 522 | } |
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'] ) { |
473 | 527 | $wopt = $wgUser->getOption( 'thumbsize' ); |
474 | 528 | |
475 | 529 | if( !isset( $wgThumbLimits[$wopt] ) ) { |
— | — | @@ -476,16 +530,21 @@ |
477 | 531 | } |
478 | 532 | |
479 | 533 | // 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; |
482 | 536 | } |
483 | 537 | // Use width which is smaller: real image width or user preference width |
484 | 538 | // 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 | + } |
486 | 545 | } |
487 | 546 | } |
488 | 547 | |
489 | | - if ( $thumb || $framed ) { |
| 548 | + if ( isset( $fp['thumbnail'] ) || isset( $fp['framed'] ) ) { |
490 | 549 | |
491 | 550 | # Create a thumbnail. Alignment depends on language |
492 | 551 | # writing direction, # right aligned for left-to-right- |
— | — | @@ -494,15 +553,15 @@ |
495 | 554 | # |
496 | 555 | # If thumbnail width has not been provided, it is set |
497 | 556 | # 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'; |
500 | 559 | } |
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; |
502 | 561 | } |
503 | 562 | |
504 | | - if ( $img && $params['width'] ) { |
| 563 | + if ( $file && $hp['width'] ) { |
505 | 564 | # Create a resized image, without the additional thumbnail features |
506 | | - $thumb = $img->transform( $params ); |
| 565 | + $thumb = $file->transform( $hp ); |
507 | 566 | } else { |
508 | 567 | $thumb = false; |
509 | 568 | } |
— | — | @@ -512,58 +571,76 @@ |
513 | 572 | } else { |
514 | 573 | $query = ''; |
515 | 574 | } |
516 | | - $u = $nt->getLocalURL( $query ); |
| 575 | + $url = $title->getLocalURL( $query ); |
517 | 576 | $imgAttribs = array( |
518 | | - 'alt' => $alt, |
519 | | - 'longdesc' => $u |
| 577 | + 'alt' => $fp['alt'], |
| 578 | + 'longdesc' => $url |
520 | 579 | ); |
521 | 580 | |
522 | | - if ( $valign ) { |
523 | | - $imgAttribs['style'] = "vertical-align: $valign"; |
| 581 | + if ( isset( $fp['valign'] ) ) { |
| 582 | + $imgAttribs['style'] = "vertical-align: {$fp['valign']}"; |
524 | 583 | } |
525 | | - if ( isset( $params['border'] ) ) { |
| 584 | + if ( isset( $fp['border'] ) ) { |
526 | 585 | $imgAttribs['class'] = "thumbborder"; |
527 | 586 | } |
528 | 587 | $linkAttribs = array( |
529 | | - 'href' => $u, |
| 588 | + 'href' => $url, |
530 | 589 | 'class' => 'image', |
531 | | - 'title' => $alt |
| 590 | + 'title' => $fp['alt'] |
532 | 591 | ); |
533 | 592 | |
534 | 593 | if ( !$thumb ) { |
535 | | - $s = $this->makeBrokenImageLinkObj( $nt ); |
| 594 | + $s = $this->makeBrokenImageLinkObj( $title ); |
536 | 595 | } else { |
537 | 596 | $s = $thumb->toHtml( $imgAttribs, $linkAttribs ); |
538 | 597 | } |
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>"; |
541 | 600 | } |
542 | 601 | return str_replace("\n", ' ',$prefix.$s.$postfix); |
543 | 602 | } |
544 | 603 | |
545 | 604 | /** |
546 | 605 | * 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 |
549 | 608 | */ |
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() ) { |
551 | 621 | global $wgStylePath, $wgContLang; |
552 | | - $exists = $img && $img->exists(); |
| 622 | + $exists = $file && $file->exists(); |
553 | 623 | |
554 | | - $page = isset( $params['page'] ) ? $params['page'] : false; |
| 624 | + # Shortcuts |
| 625 | + $fp =& $frameParams; |
| 626 | + $hp =& $handlerParams; |
555 | 627 | |
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'] ) ) { |
557 | 634 | // 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; |
559 | 636 | } |
560 | 637 | $thumb = false; |
561 | 638 | |
562 | 639 | if ( !$exists ) { |
563 | | - $outerWidth = $params['width'] + 2; |
| 640 | + $outerWidth = $hp['width'] + 2; |
564 | 641 | } else { |
565 | | - if ( $manual_thumb != '' ) { |
| 642 | + if ( isset( $fp['manualthumb'] ) ) { |
566 | 643 | # Use manually specified thumbnail |
567 | | - $manual_title = Title::makeTitleSafe( NS_IMAGE, $manual_thumb ); |
| 644 | + $manual_title = Title::makeTitleSafe( NS_IMAGE, $fp['manualthumb'] ); |
568 | 645 | if( $manual_title ) { |
569 | 646 | $manual_img = wfFindFile( $manual_title ); |
570 | 647 | if ( $manual_img ) { |
— | — | @@ -572,63 +649,63 @@ |
573 | 650 | $exists = false; |
574 | 651 | } |
575 | 652 | } |
576 | | - } elseif ( $framed ) { |
| 653 | + } elseif ( isset( $fp['framed'] ) ) { |
577 | 654 | // Use image dimensions, don't scale |
578 | | - $thumb = $img->getUnscaledThumb( $page ); |
| 655 | + $thumb = $file->getUnscaledThumb( $page ); |
579 | 656 | } else { |
580 | 657 | # Do not present an image bigger than the source, for bitmap-style images |
581 | 658 | # 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; |
585 | 662 | } |
586 | | - $thumb = $img->transform( $params ); |
| 663 | + $thumb = $file->transform( $hp ); |
587 | 664 | } |
588 | 665 | |
589 | 666 | if ( $thumb ) { |
590 | 667 | $outerWidth = $thumb->getWidth() + 2; |
591 | 668 | } else { |
592 | | - $outerWidth = $params['width'] + 2; |
| 669 | + $outerWidth = $hp['width'] + 2; |
593 | 670 | } |
594 | 671 | } |
595 | 672 | |
596 | 673 | $query = $page ? 'page=' . urlencode( $page ) : ''; |
597 | | - $u = $nt->getLocalURL( $query ); |
| 674 | + $url = $title->getLocalURL( $query ); |
598 | 675 | |
599 | 676 | $more = htmlspecialchars( wfMsg( 'thumbnail-more' ) ); |
600 | 677 | $magnifyalign = $wgContLang->isRTL() ? 'left' : 'right'; |
601 | 678 | $textalign = $wgContLang->isRTL() ? ' style="text-align:right"' : ''; |
602 | 679 | |
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;\">"; |
604 | 681 | if( !$exists ) { |
605 | | - $s .= $this->makeBrokenImageLinkObj( $nt ); |
| 682 | + $s .= $this->makeBrokenImageLinkObj( $title ); |
606 | 683 | $zoomicon = ''; |
607 | 684 | } elseif ( !$thumb ) { |
608 | 685 | $s .= htmlspecialchars( wfMsg( 'thumbnail_error', '' ) ); |
609 | 686 | $zoomicon = ''; |
610 | 687 | } else { |
611 | 688 | $imgAttribs = array( |
612 | | - 'alt' => $alt, |
613 | | - 'longdesc' => $u, |
| 689 | + 'alt' => $fp['alt'], |
| 690 | + 'longdesc' => $url, |
614 | 691 | 'class' => 'thumbimage' |
615 | 692 | ); |
616 | 693 | $linkAttribs = array( |
617 | | - 'href' => $u, |
| 694 | + 'href' => $url, |
618 | 695 | 'class' => 'internal', |
619 | | - 'title' => $alt |
| 696 | + 'title' => $fp['alt'] |
620 | 697 | ); |
621 | | - |
| 698 | + |
622 | 699 | $s .= $thumb->toHtml( $imgAttribs, $linkAttribs ); |
623 | | - if ( $framed ) { |
| 700 | + if ( isset( $fp['framed'] ) ) { |
624 | 701 | $zoomicon=""; |
625 | 702 | } else { |
626 | 703 | $zoomicon = '<div class="magnify" style="float:'.$magnifyalign.'">'. |
627 | | - '<a href="'.$u.'" class="internal" title="'.$more.'">'. |
| 704 | + '<a href="'.$url.'" class="internal" title="'.$more.'">'. |
628 | 705 | '<img src="'.$wgStylePath.'/common/images/magnify-clip.png" ' . |
629 | 706 | 'width="15" height="11" alt="" /></a></div>'; |
630 | 707 | } |
631 | 708 | } |
632 | | - $s .= ' <div class="thumbcaption"'.$textalign.'>'.$zoomicon.$label."</div></div></div>"; |
| 709 | + $s .= ' <div class="thumbcaption"'.$textalign.'>'.$zoomicon.$fp['caption']."</div></div></div>"; |
633 | 710 | return str_replace("\n", ' ', $s); |
634 | 711 | } |
635 | 712 | |
— | — | @@ -1269,28 +1346,7 @@ |
1270 | 1347 | */ |
1271 | 1348 | public function formatSize( $size ) { |
1272 | 1349 | 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 ) ); |
1295 | 1351 | } |
1296 | 1352 | |
1297 | 1353 | /** |
— | — | @@ -1346,3 +1402,4 @@ |
1347 | 1403 | |
1348 | 1404 | |
1349 | 1405 | |
| 1406 | + |
Index: trunk/phase3/includes/Parser.php |
— | — | @@ -97,7 +97,8 @@ |
98 | 98 | * @private |
99 | 99 | */ |
100 | 100 | # Persistent: |
101 | | - var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables; |
| 101 | + var $mTagHooks, $mTransparentTagHooks, $mFunctionHooks, $mFunctionSynonyms, $mVariables, |
| 102 | + $mImageParams, $mImageParamsMagicArray; |
102 | 103 | |
103 | 104 | # Cleared with clearState(): |
104 | 105 | var $mOutput, $mAutonumber, $mDTopen, $mStripState; |
— | — | @@ -4473,10 +4474,50 @@ |
4474 | 4475 | return $ig->toHTML(); |
4475 | 4476 | } |
4476 | 4477 | |
| 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 | + |
4477 | 4518 | /** |
4478 | 4519 | * Parse image options text and use it to make an image |
4479 | 4520 | */ |
4480 | | - function makeImage( $nt, $options ) { |
| 4521 | + function makeImage( $title, $options ) { |
4481 | 4522 | # @TODO: let the MediaHandler specify its transform parameters |
4482 | 4523 | # |
4483 | 4524 | # Check if the options text is of the form "options|alt text" |
— | — | @@ -4500,77 +4541,55 @@ |
4501 | 4542 | # * middle |
4502 | 4543 | # * bottom |
4503 | 4544 | # * text-bottom |
| 4545 | + |
| 4546 | + $parts = array_map( 'trim', explode( '|', $options) ); |
| 4547 | + $sk = $this->mOptions->getSkin(); |
4504 | 4548 | |
| 4549 | + # Give extensions a chance to select the file revision for us |
| 4550 | + $skip = $time = false; |
| 4551 | + wfRunHooks( 'BeforeParserMakeImageLinkObj', array( &$this, &$title, &$skip, &$time ) ); |
4505 | 4552 | |
4506 | | - $part = array_map( 'trim', explode( '|', $options) ); |
| 4553 | + if ( $skip ) { |
| 4554 | + return $sk->makeLinkObj( $title ); |
| 4555 | + } |
4507 | 4556 | |
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 | + } |
4512 | 4575 | } |
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 = ''; |
4522 | 4576 | |
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 | + } |
4528 | 4584 | |
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] ); |
4554 | 4590 | } |
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 | | - } |
4573 | 4591 | } |
4574 | 4592 | } |
| 4593 | + |
4575 | 4594 | # Strip bad stuff out of the alt text |
4576 | 4595 | $alt = $this->replaceLinkHoldersText( $caption ); |
4577 | 4596 | |
— | — | @@ -4580,18 +4599,18 @@ |
4581 | 4600 | $alt = $this->mStripState->unstripBoth( $alt ); |
4582 | 4601 | $alt = Sanitizer::stripAllTags( $alt ); |
4583 | 4602 | |
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; |
4587 | 4605 | |
4588 | 4606 | # 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 ); |
4593 | 4612 | } |
4594 | | - |
4595 | | - return $link; |
| 4613 | + |
| 4614 | + return $ret; |
4596 | 4615 | } |
4597 | 4616 | |
4598 | 4617 | /** |
Index: trunk/phase3/includes/ParserOutput.php |
— | — | @@ -19,7 +19,8 @@ |
20 | 20 | $mExternalLinks, # External link URLs, in the key only |
21 | 21 | $mNewSection, # Show a new section link? |
22 | 22 | $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 |
24 | 25 | |
25 | 26 | /** |
26 | 27 | * Overridden title for display |
— | — | @@ -44,6 +45,7 @@ |
45 | 46 | $this->mNoGallery = false; |
46 | 47 | $this->mHeadItems = array(); |
47 | 48 | $this->mTemplateIds = array(); |
| 49 | + $this->mOutputHooks = array(); |
48 | 50 | } |
49 | 51 | |
50 | 52 | function getText() { return $this->mText; } |
— | — | @@ -58,6 +60,7 @@ |
59 | 61 | function &getExternalLinks() { return $this->mExternalLinks; } |
60 | 62 | function getNoGallery() { return $this->mNoGallery; } |
61 | 63 | function getSubtitle() { return $this->mSubtitle; } |
| 64 | + function getOutputHooks() { return $this->mOutputHooks; } |
62 | 65 | |
63 | 66 | function containsOldMagic() { return $this->mContainsOldMagic; } |
64 | 67 | function setText( $text ) { return wfSetVar( $this->mText, $text ); } |
— | — | @@ -71,6 +74,10 @@ |
72 | 75 | function addLanguageLink( $t ) { $this->mLanguageLinks[] = $t; } |
73 | 76 | function addExternalLink( $url ) { $this->mExternalLinks[$url] = 1; } |
74 | 77 | |
| 78 | + function addOutputHook( $hook, $data = false ) { |
| 79 | + $this->mOutputHooks[] = array( $hook, $data ); |
| 80 | + } |
| 81 | + |
75 | 82 | function setNewSection( $value ) { |
76 | 83 | $this->mNewSection = (bool)$value; |
77 | 84 | } |
Index: trunk/phase3/includes/User.php |
— | — | @@ -2527,7 +2527,8 @@ |
2528 | 2528 | * @static |
2529 | 2529 | */ |
2530 | 2530 | static function getGroupName( $group ) { |
2531 | | - MessageCache::loadAllMessages(); |
| 2531 | + global $wgMessageCache; |
| 2532 | + $wgMessageCache->loadAllMessages(); |
2532 | 2533 | $key = "group-$group"; |
2533 | 2534 | $name = wfMsg( $key ); |
2534 | 2535 | return $name == '' || wfEmptyMsg( $key, $name ) |
— | — | @@ -2541,7 +2542,8 @@ |
2542 | 2543 | * @static |
2543 | 2544 | */ |
2544 | 2545 | static function getGroupMember( $group ) { |
2545 | | - MessageCache::loadAllMessages(); |
| 2546 | + global $wgMessageCache; |
| 2547 | + $wgMessageCache->loadAllMessages(); |
2546 | 2548 | $key = "group-$group-member"; |
2547 | 2549 | $name = wfMsg( $key ); |
2548 | 2550 | return $name == '' || wfEmptyMsg( $key, $name ) |
— | — | @@ -2586,7 +2588,8 @@ |
2587 | 2589 | * @return mixed |
2588 | 2590 | */ |
2589 | 2591 | static function getGroupPage( $group ) { |
2590 | | - MessageCache::loadAllMessages(); |
| 2592 | + global $wgMessageCache; |
| 2593 | + $wgMessageCache->loadAllMessages(); |
2591 | 2594 | $page = wfMsgForContent( 'grouppage-' . $group ); |
2592 | 2595 | if( !wfEmptyMsg( 'grouppage-' . $group, $page ) ) { |
2593 | 2596 | $title = Title::newFromText( $page ); |
Index: trunk/phase3/includes/MessageCache.php |
— | — | @@ -23,6 +23,7 @@ |
24 | 24 | var $mExtensionMessages = array(); |
25 | 25 | var $mInitialised = false; |
26 | 26 | var $mDeferred = true; |
| 27 | + var $mAllMessagesLoaded; |
27 | 28 | |
28 | 29 | function __construct( &$memCached, $useDB, $expiry, $memcPrefix) { |
29 | 30 | wfProfileIn( __METHOD__ ); |
— | — | @@ -669,12 +670,33 @@ |
670 | 671 | } |
671 | 672 | } |
672 | 673 | |
673 | | - static function loadAllMessages() { |
| 674 | + function loadAllMessages() { |
| 675 | + global $wgExtensionMessagesFiles; |
| 676 | + if ( $this->mAllMessagesLoaded ) { |
| 677 | + return; |
| 678 | + } |
| 679 | + $this->mAllMessagesLoaded = true; |
| 680 | + |
674 | 681 | # Some extensions will load their messages when you load their class file |
675 | 682 | wfLoadAllExtensions(); |
676 | 683 | # Others will respond to this hook |
677 | 684 | 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 | + } |
678 | 692 | # Still others will respond to neither, they are EVIL. We sometimes need to know! |
679 | 693 | } |
| 694 | + |
| 695 | + /** |
| 696 | + * Load messages from a given file |
| 697 | + */ |
| 698 | + function loadMessagesFile( $filename ) { |
| 699 | + require( $filename ); |
| 700 | + $this->addMessagesByLang( $messages ); |
| 701 | + } |
680 | 702 | } |
681 | 703 | |
Index: trunk/phase3/includes/DefaultSettings.php |
— | — | @@ -1917,6 +1917,28 @@ |
1918 | 1918 | $wgSkinExtensionFunctions = array(); |
1919 | 1919 | |
1920 | 1920 | /** |
| 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 | +/** |
1921 | 1943 | * List of valid skin names. |
1922 | 1944 | * The key should be the name in all lower case, the value should be a display name. |
1923 | 1945 | * The default skins will be added later, by Skin::getSkinNames(). Use |
Index: trunk/phase3/includes/filerepo/File.php |
— | — | @@ -210,6 +210,18 @@ |
211 | 211 | public function getHeight( $page = 1 ) { return false; } |
212 | 212 | |
213 | 213 | /** |
| 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 | + /** |
214 | 226 | * Get handler-specific metadata |
215 | 227 | * Overridden by LocalFile, UnregisteredLocalFile |
216 | 228 | * STUB |
— | — | @@ -239,7 +251,9 @@ |
240 | 252 | function getMediaType() { return MEDIATYPE_UNKNOWN; } |
241 | 253 | |
242 | 254 | /** |
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. |
244 | 258 | * |
245 | 259 | * Currently, this checks if the file is an image format |
246 | 260 | * that can be converted to a format |
— | — | @@ -248,7 +262,7 @@ |
249 | 263 | */ |
250 | 264 | function canRender() { |
251 | 265 | if ( !isset( $this->canRender ) ) { |
252 | | - $this->canRender = $this->getHandler() && $this->handler->canRender(); |
| 266 | + $this->canRender = $this->getHandler() && $this->handler->canRender( $this ); |
253 | 267 | } |
254 | 268 | return $this->canRender; |
255 | 269 | } |
— | — | @@ -271,16 +285,11 @@ |
272 | 286 | * @return bool |
273 | 287 | */ |
274 | 288 | function mustRender() { |
275 | | - return $this->getHandler() && $this->handler->mustRender(); |
| 289 | + return $this->getHandler() && $this->handler->mustRender( $this ); |
276 | 290 | } |
277 | 291 | |
278 | 292 | /** |
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() |
285 | 294 | */ |
286 | 295 | function allowInlineDisplay() { |
287 | 296 | return $this->canRender(); |
— | — | @@ -470,7 +479,7 @@ |
471 | 480 | |
472 | 481 | wfProfileIn( __METHOD__ ); |
473 | 482 | do { |
474 | | - if ( !$this->getHandler() || !$this->handler->canRender() ) { |
| 483 | + if ( !$this->canRender() ) { |
475 | 484 | // not a bitmap or renderable image, don't try. |
476 | 485 | $thumb = $this->iconThumb(); |
477 | 486 | break; |
— | — | @@ -906,7 +915,7 @@ |
907 | 916 | * @return Bool |
908 | 917 | */ |
909 | 918 | function isMultipage() { |
910 | | - return $this->getHandler() && $this->handler->isMultiPage(); |
| 919 | + return $this->getHandler() && $this->handler->isMultiPage( $this ); |
911 | 920 | } |
912 | 921 | |
913 | 922 | /** |
— | — | @@ -915,7 +924,7 @@ |
916 | 925 | */ |
917 | 926 | function pageCount() { |
918 | 927 | if ( !isset( $this->pageCount ) ) { |
919 | | - if ( $this->getHandler() && $this->handler->isMultiPage() ) { |
| 928 | + if ( $this->getHandler() && $this->handler->isMultiPage( $this ) ) { |
920 | 929 | $this->pageCount = $this->handler->pageCount( $this ); |
921 | 930 | } else { |
922 | 931 | $this->pageCount = false; |
— | — | @@ -1014,7 +1023,9 @@ |
1015 | 1024 | static function getPropsFromPath( $path, $ext = true ) { |
1016 | 1025 | wfProfileIn( __METHOD__ ); |
1017 | 1026 | 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 | + ); |
1019 | 1030 | $gis = false; |
1020 | 1031 | if ( $info['fileExists'] ) { |
1021 | 1032 | $magic = MimeMagic::singleton(); |
— | — | @@ -1030,8 +1041,8 @@ |
1031 | 1042 | $handler = MediaHandler::getHandler( $info['mime'] ); |
1032 | 1043 | if ( $handler ) { |
1033 | 1044 | $tempImage = (object)array(); |
1034 | | - $gis = $handler->getImageSize( $tempImage, $path ); |
1035 | 1045 | $info['metadata'] = $handler->getMetadata( $tempImage, $path ); |
| 1046 | + $gis = $handler->getImageSize( $tempImage, $path, $info['metadata'] ); |
1036 | 1047 | } else { |
1037 | 1048 | $gis = false; |
1038 | 1049 | $info['metadata'] = ''; |
— | — | @@ -1074,13 +1085,42 @@ |
1075 | 1086 | * Returns false on failure |
1076 | 1087 | */ |
1077 | 1088 | static function sha1Base36( $path ) { |
| 1089 | + wfSuppressWarnings(); |
1078 | 1090 | $hash = sha1_file( $path ); |
| 1091 | + wfRestoreWarnings(); |
1079 | 1092 | if ( $hash === false ) { |
1080 | 1093 | return false; |
1081 | 1094 | } else { |
1082 | 1095 | return wfBaseConvert( $hash, 16, 36, 31 ); |
1083 | 1096 | } |
1084 | 1097 | } |
| 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 | + } |
1085 | 1125 | } |
1086 | 1126 | /** |
1087 | 1127 | * Aliases for backwards compatibility with 1.6 |
— | — | @@ -1090,3 +1130,4 @@ |
1091 | 1131 | define( 'MW_IMG_DELETED_USER', File::DELETED_USER ); |
1092 | 1132 | define( 'MW_IMG_DELETED_RESTRICTED', File::DELETED_RESTRICTED ); |
1093 | 1133 | |
| 1134 | + |
Index: trunk/phase3/includes/filerepo/OldLocalFile.php |
— | — | @@ -118,7 +118,10 @@ |
119 | 119 | } |
120 | 120 | |
121 | 121 | 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 | + } |
123 | 126 | // This is expensive, so we only do it if $wgMemc is real |
124 | 127 | global $wgMemc; |
125 | 128 | if ( $wgMemc instanceof FakeMemcachedClient ) { |
— | — | @@ -155,6 +158,7 @@ |
156 | 159 | |
157 | 160 | function loadFromDB() { |
158 | 161 | wfProfileIn( __METHOD__ ); |
| 162 | + $this->dataLoaded = true; |
159 | 163 | $dbr = $this->repo->getSlaveDB(); |
160 | 164 | $conds = array( 'oi_name' => $this->getName() ); |
161 | 165 | if ( is_null( $this->requestedTime ) ) { |
— | — | @@ -169,7 +173,6 @@ |
170 | 174 | } else { |
171 | 175 | $this->fileExists = false; |
172 | 176 | } |
173 | | - $this->dataLoaded = true; |
174 | 177 | wfProfileOut( __METHOD__ ); |
175 | 178 | } |
176 | 179 | |
— | — | @@ -178,8 +181,8 @@ |
179 | 182 | $fields[] = $prefix . 'archive_name'; |
180 | 183 | |
181 | 184 | // 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' ) ); |
184 | 187 | return $fields; |
185 | 188 | } |
186 | 189 | |
— | — | @@ -193,11 +196,18 @@ |
194 | 197 | |
195 | 198 | function upgradeRow() { |
196 | 199 | wfProfileIn( __METHOD__ ); |
197 | | - |
198 | 200 | $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 | + } |
199 | 208 | |
200 | 209 | $dbw = $this->repo->getMasterDB(); |
201 | 210 | list( $major, $minor ) = self::splitMime( $this->mime ); |
| 211 | + $mime = $this->mime; |
202 | 212 | |
203 | 213 | wfDebug(__METHOD__.': upgrading '.$this->archive_name." to the current schema\n"); |
204 | 214 | $dbw->update( 'oldimage', |
— | — | @@ -205,10 +215,10 @@ |
206 | 216 | 'oi_width' => $this->width, |
207 | 217 | 'oi_height' => $this->height, |
208 | 218 | '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, |
213 | 223 | 'oi_sha1' => $this->sha1, |
214 | 224 | ), array( |
215 | 225 | 'oi_name' => $this->getName(), |
Index: trunk/phase3/includes/filerepo/LocalFile.php |
— | — | @@ -39,7 +39,7 @@ |
40 | 40 | $media_type, # MEDIATYPE_xxx (bitmap, drawing, audio...) |
41 | 41 | $mime, # MIME type, determined by MimeMagic::guessMimeType |
42 | 42 | $major_mime, # Major mime type |
43 | | - $minor_mine, # Minor mime type |
| 43 | + $minor_mime, # Minor mime type |
44 | 44 | $size, # Size in bytes (loadFromXxx) |
45 | 45 | $metadata, # Handler-specific metadata |
46 | 46 | $timestamp, # Upload timestamp |
— | — | @@ -231,6 +231,7 @@ |
232 | 232 | * Load file metadata from a DB result row |
233 | 233 | */ |
234 | 234 | function loadFromRow( $row, $prefix = 'img_' ) { |
| 235 | + $this->dataLoaded = true; |
235 | 236 | $array = $this->decodeRow( $row, $prefix ); |
236 | 237 | foreach ( $array as $name => $value ) { |
237 | 238 | $this->$name = $value; |
— | — | @@ -287,6 +288,11 @@ |
288 | 289 | |
289 | 290 | $this->loadFromFile(); |
290 | 291 | |
| 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 | + } |
291 | 297 | $dbw = $this->repo->getMasterDB(); |
292 | 298 | list( $major, $minor ) = self::splitMime( $this->mime ); |
293 | 299 | |
— | — | @@ -318,6 +324,12 @@ |
319 | 325 | $this->$field = $info[$field]; |
320 | 326 | } |
321 | 327 | } |
| 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 | + } |
322 | 334 | } |
323 | 335 | |
324 | 336 | /** splitMime inherited */ |
— | — | @@ -560,14 +572,9 @@ |
561 | 573 | $dbr = $this->repo->getSlaveDB(); |
562 | 574 | |
563 | 575 | 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', |
565 | 577 | array( |
566 | | - 'img_size', |
567 | | - 'img_description', |
568 | | - 'img_user','img_user_text', |
569 | | - 'img_timestamp', |
570 | | - 'img_width', |
571 | | - 'img_height', |
| 578 | + '*', |
572 | 579 | "'' AS oi_archive_name" |
573 | 580 | ), |
574 | 581 | array( 'img_name' => $this->title->getDBkey() ), |
— | — | @@ -580,17 +587,7 @@ |
581 | 588 | } |
582 | 589 | } else if ( $this->historyLine == 1 ) { |
583 | 590 | $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', '*', |
595 | 592 | array( 'oi_name' => $this->title->getDBkey() ), |
596 | 593 | __METHOD__, |
597 | 594 | array( 'ORDER BY' => 'oi_timestamp DESC' ) |
Index: trunk/phase3/includes/MimeMagic.php |
— | — | @@ -374,7 +374,9 @@ |
375 | 375 | $mime = $this->detectMimeType( $file, $ext ); |
376 | 376 | |
377 | 377 | // Read a chunk of the file |
| 378 | + wfSuppressWarnings(); |
378 | 379 | $f = fopen( $file, "rt" ); |
| 380 | + wfRestoreWarnings(); |
379 | 381 | if( !$f ) return "unknown/unknown"; |
380 | 382 | $head = fread( $f, 1024 ); |
381 | 383 | fclose( $f ); |
Index: trunk/phase3/includes/ImagePage.php |
— | — | @@ -16,6 +16,7 @@ |
17 | 17 | class ImagePage extends Article { |
18 | 18 | |
19 | 19 | /* private */ var $img; // Image object this page is shown for |
| 20 | + /* private */ var $repo; |
20 | 21 | var $mExtraDescription = false; |
21 | 22 | |
22 | 23 | function __construct( $title ) { |
— | — | @@ -24,6 +25,7 @@ |
25 | 26 | if ( !$this->img ) { |
26 | 27 | $this->img = wfLocalFile( $this->mTitle ); |
27 | 28 | } |
| 29 | + $this->repo = $this->img->repo; |
28 | 30 | } |
29 | 31 | |
30 | 32 | /** |
— | — | @@ -46,6 +48,7 @@ |
47 | 49 | return Article::view(); |
48 | 50 | |
49 | 51 | if ($wgShowEXIF && $this->img->exists()) { |
| 52 | + // FIXME: bad interface, see note on MediaHandler::formatMetadata(). |
50 | 53 | $formattedMetadata = $this->img->formatMetadata(); |
51 | 54 | $showmeta = $formattedMetadata !== false; |
52 | 55 | } else { |
— | — | @@ -115,6 +118,8 @@ |
116 | 119 | /** |
117 | 120 | * Make a table with metadata to be shown in the output page. |
118 | 121 | * |
| 122 | + * FIXME: bad interface, see note on MediaHandler::formatMetadata(). |
| 123 | + * |
119 | 124 | * @access private |
120 | 125 | * |
121 | 126 | * @param array $exif The array containing the EXIF data |
— | — | @@ -188,14 +193,15 @@ |
189 | 194 | $mime = $this->img->getMimeType(); |
190 | 195 | $showLink = false; |
191 | 196 | $linkAttribs = array( 'href' => $full_url ); |
| 197 | + $longDesc = $this->img->getLongDesc(); |
192 | 198 | |
193 | | - wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this , &$wgOut ) ) ; |
| 199 | + wfRunHooks( 'ImageOpenShowImageInlineBefore', array( &$this , &$wgOut ) ) ; |
194 | 200 | |
195 | | - if ( $this->img->allowInlineDisplay() and $width and $height) { |
| 201 | + if ( $this->img->allowInlineDisplay() ) { |
196 | 202 | # image |
197 | 203 | |
198 | 204 | # "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 ); |
200 | 206 | # We'll show a thumbnail of this image |
201 | 207 | if ( $width > $maxWidth || $height > $maxHeight ) { |
202 | 208 | # Calculate the thumbnail size. |
— | — | @@ -229,7 +235,7 @@ |
230 | 236 | } else { |
231 | 237 | $anchorclose .= |
232 | 238 | $msgsmall . |
233 | | - '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $msgsize; |
| 239 | + '<br />' . Xml::tags( 'a', $linkAttribs, $msgbig ) . ' ' . $longDesc; |
234 | 240 | } |
235 | 241 | |
236 | 242 | if ( $this->img->isMultipage() ) { |
— | — | @@ -301,26 +307,17 @@ |
302 | 308 | |
303 | 309 | |
304 | 310 | if ($showLink) { |
305 | | - // Workaround for incorrect MIME type on SVGs uploaded in previous versions |
306 | | - if ($mime == 'image/svg') $mime = 'image/svg+xml'; |
307 | | - |
308 | 311 | $filename = wfEscapeWikiText( $this->img->getName() ); |
309 | 312 | $info = wfMsg( 'file-info', $sk->formatSize( $this->img->getSize() ), $mime ); |
310 | | - $infores = ''; |
311 | 313 | |
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 | | - |
317 | 314 | global $wgContLang; |
318 | 315 | $dirmark = $wgContLang->getDirMark(); |
319 | 316 | if (!$this->img->isSafeFile()) { |
320 | 317 | $warning = wfMsg( 'mediawarning' ); |
321 | 318 | $wgOut->addWikiText( <<<EOT |
322 | | -<div class="fullMedia">$infores |
| 319 | +<div class="fullMedia"> |
323 | 320 | <span class="dangerousLink">[[Media:$filename|$filename]]</span>$dirmark |
324 | | -<span class="fileInfo"> $info</span> |
| 321 | +<span class="fileInfo"> $longDesc</span> |
325 | 322 | </div> |
326 | 323 | |
327 | 324 | <div class="mediaWarning">$warning</div> |
— | — | @@ -328,8 +325,8 @@ |
329 | 326 | ); |
330 | 327 | } else { |
331 | 328 | $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> |
334 | 331 | </div> |
335 | 332 | EOT |
336 | 333 | ); |
— | — | @@ -421,18 +418,22 @@ |
422 | 419 | |
423 | 420 | if ( $line ) { |
424 | 421 | $list = new ImageHistoryList( $sk, $this->img ); |
| 422 | + $file = $this->repo->newFileFromRow( $line ); |
| 423 | + $dims = $file->getDimensionsString(); |
425 | 424 | $s = $list->beginImageHistoryList() . |
426 | 425 | $list->imageHistoryLine( true, wfTimestamp(TS_MW, $line->img_timestamp), |
427 | 426 | $this->mTitle->getDBkey(), $line->img_user, |
428 | 427 | $line->img_user_text, $line->img_size, $line->img_description, |
429 | | - $line->img_width, $line->img_height |
| 428 | + $dims |
430 | 429 | ); |
431 | 430 | |
432 | 431 | 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 |
437 | 438 | ); |
438 | 439 | } |
439 | 440 | $s .= $list->endImageHistoryList(); |
— | — | @@ -655,7 +656,7 @@ |
656 | 657 | */ |
657 | 658 | class ImageHistoryList { |
658 | 659 | |
659 | | - protected $img, $skin, $title; |
| 660 | + protected $img, $skin, $title, $repo; |
660 | 661 | |
661 | 662 | public function __construct( $skin, $img ) { |
662 | 663 | $this->skin = $skin; |
— | — | @@ -682,7 +683,7 @@ |
683 | 684 | return "</table>\n"; |
684 | 685 | } |
685 | 686 | |
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 ) { |
687 | 688 | global $wgUser, $wgLang, $wgTitle, $wgContLang; |
688 | 689 | $local = $this->img->isLocal(); |
689 | 690 | $row = ''; |
— | — | @@ -740,10 +741,7 @@ |
741 | 742 | $row .= '</td>'; |
742 | 743 | |
743 | 744 | // 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>'; |
748 | 746 | |
749 | 747 | // File size |
750 | 748 | $row .= '<td class="mw-imagepage-filesize">' . $this->skin->formatSize( $size ) . '</td>'; |
— | — | @@ -754,4 +752,4 @@ |
755 | 753 | return "<tr>{$row}</tr>\n"; |
756 | 754 | } |
757 | 755 | |
758 | | -} |
\ No newline at end of file |
| 756 | +} |
Index: trunk/phase3/includes/OutputPage.php |
— | — | @@ -109,6 +109,10 @@ |
110 | 110 | $this->mHeadItems[$name] = $value; |
111 | 111 | } |
112 | 112 | |
| 113 | + function hasHeadItem( $name ) { |
| 114 | + return isset( $this->mHeadItems[$name] ); |
| 115 | + } |
| 116 | + |
113 | 117 | function setETag($tag) { $this->mETag = $tag; } |
114 | 118 | function setArticleBodyOnly($only) { $this->mArticleBodyOnly = $only; } |
115 | 119 | function getArticleBodyOnly($only) { return $this->mArticleBodyOnly; } |
— | — | @@ -377,7 +381,16 @@ |
378 | 382 | # Display title |
379 | 383 | if( ( $dt = $parserOutput->getDisplayTitle() ) !== false ) |
380 | 384 | $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 | + |
382 | 395 | wfRunHooks( 'OutputPageParserOutput', array( &$this, $parserOutput ) ); |
383 | 396 | } |
384 | 397 | |
Index: trunk/phase3/includes/GlobalFunctions.php |
— | — | @@ -2297,3 +2297,17 @@ |
2298 | 2298 | function wfBoolToStr( $value ) { |
2299 | 2299 | return $value ? 'true' : 'false'; |
2300 | 2300 | } |
| 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 @@ |
119 | 119 | 'LogPage' => 'includes/LogPage.php', |
120 | 120 | 'MacBinary' => 'includes/MacBinary.php', |
121 | 121 | 'MagicWord' => 'includes/MagicWord.php', |
| 122 | + 'MagicWordArray' => 'includes/MagicWord.php', |
122 | 123 | 'MathRenderer' => 'includes/Math.php', |
123 | 124 | 'MediaTransformOutput' => 'includes/MediaTransformOutput.php', |
124 | 125 | 'ThumbnailImage' => 'includes/MediaTransformOutput.php', |
— | — | @@ -385,3 +386,4 @@ |
386 | 387 | } |
387 | 388 | |
388 | 389 | |
| 390 | + |
Index: trunk/phase3/includes/SpecialSpecialpages.php |
— | — | @@ -8,9 +8,9 @@ |
9 | 9 | * |
10 | 10 | */ |
11 | 11 | function wfSpecialSpecialpages() { |
12 | | - global $wgOut, $wgUser; |
| 12 | + global $wgOut, $wgUser, $wgMessageCache; |
13 | 13 | |
14 | | - MessageCache::loadAllMessages(); |
| 14 | + $wgMessageCache->loadAllMessages(); |
15 | 15 | |
16 | 16 | $wgOut->setRobotpolicy( 'index,nofollow' ); |
17 | 17 | $sk = $wgUser->getSkin(); |
Index: trunk/phase3/includes/SpecialAllmessages.php |
— | — | @@ -25,7 +25,8 @@ |
26 | 26 | $navText = wfMsg( 'allmessagestext' ); |
27 | 27 | |
28 | 28 | # Make sure all extension messages are available |
29 | | - MessageCache::loadAllMessages(); |
| 29 | + |
| 30 | + $wgMessageCache->loadAllMessages(); |
30 | 31 | |
31 | 32 | $sortedArray = array_merge( Language::getMessagesFor( 'en' ), $wgMessageCache->getExtensionMessagesFor( 'en' ) ); |
32 | 33 | ksort( $sortedArray ); |
Index: trunk/phase3/includes/media/DjVu.php |
— | — | @@ -17,6 +17,13 @@ |
18 | 18 | function mustRender() { return true; } |
19 | 19 | function isMultiPage() { return true; } |
20 | 20 | |
| 21 | + function getParamMap() { |
| 22 | + return array( |
| 23 | + 'img_width' => 'width', |
| 24 | + 'img_page' => 'page', |
| 25 | + ); |
| 26 | + } |
| 27 | + |
21 | 28 | function validateParam( $name, $value ) { |
22 | 29 | if ( in_array( $name, array( 'width', 'height', 'page' ) ) ) { |
23 | 30 | if ( $value <= 0 ) { |
Index: trunk/phase3/includes/media/Generic.php |
— | — | @@ -36,6 +36,12 @@ |
37 | 37 | return self::$handlers[$class]; |
38 | 38 | } |
39 | 39 | |
| 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 | + |
40 | 46 | /* |
41 | 47 | * Validate a thumbnail parameter at parse time. |
42 | 48 | * Return true to accept the parameter, and false to reject it. |
— | — | @@ -126,20 +132,20 @@ |
127 | 133 | /** |
128 | 134 | * True if the handled types can be transformed |
129 | 135 | */ |
130 | | - function canRender() { return true; } |
| 136 | + function canRender( $file ) { return true; } |
131 | 137 | /** |
132 | 138 | * True if handled types cannot be displayed directly in a browser |
133 | 139 | * but can be rendered |
134 | 140 | */ |
135 | | - function mustRender() { return false; } |
| 141 | + function mustRender( $file ) { return false; } |
136 | 142 | /** |
137 | 143 | * True if the type has multi-page capabilities |
138 | 144 | */ |
139 | | - function isMultiPage() { return false; } |
| 145 | + function isMultiPage( $file ) { return false; } |
140 | 146 | /** |
141 | 147 | * Page count for a multi-page document, false if unsupported or unknown |
142 | 148 | */ |
143 | | - function pageCount() { return false; } |
| 149 | + function pageCount( $file ) { return false; } |
144 | 150 | /** |
145 | 151 | * False if the handler is disabled for all files |
146 | 152 | */ |
— | — | @@ -177,10 +183,18 @@ |
178 | 184 | * |
179 | 185 | * The function should return false if there is no metadata to display. |
180 | 186 | */ |
| 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 | + */ |
181 | 195 | function formatMetadata( $image, $metadata ) { |
182 | 196 | return false; |
183 | 197 | } |
184 | | - |
| 198 | + |
185 | 199 | protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) { |
186 | 200 | $array[$visibility][] = array( |
187 | 201 | 'id' => "$type-$id", |
— | — | @@ -189,6 +203,26 @@ |
190 | 204 | ); |
191 | 205 | } |
192 | 206 | |
| 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 ) {} |
193 | 227 | } |
194 | 228 | |
195 | 229 | /** |
— | — | @@ -197,6 +231,18 @@ |
198 | 232 | * @addtogroup Media |
199 | 233 | */ |
200 | 234 | 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 | + |
201 | 247 | function validateParam( $name, $value ) { |
202 | 248 | if ( in_array( $name, array( 'width', 'height' ) ) ) { |
203 | 249 | if ( $value <= 0 ) { |
— | — | @@ -325,6 +371,31 @@ |
326 | 372 | wfRestoreWarnings(); |
327 | 373 | return $gis; |
328 | 374 | } |
| 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 | + } |
329 | 399 | } |
330 | 400 | |
331 | 401 | |
| 402 | + |
Index: trunk/phase3/includes/media/SVG.php |
— | — | @@ -14,7 +14,7 @@ |
15 | 15 | } |
16 | 16 | } |
17 | 17 | |
18 | | - function mustRender() { |
| 18 | + function mustRender( $file ) { |
19 | 19 | return true; |
20 | 20 | } |
21 | 21 | |
— | — | @@ -91,5 +91,12 @@ |
92 | 92 | function getThumbType( $ext, $mime ) { |
93 | 93 | return array( 'png', 'image/png' ); |
94 | 94 | } |
| 95 | + |
| 96 | + function getLongDesc( $file ) { |
| 97 | + global $wgLang; |
| 98 | + return wfMsg( 'svg-long-desc', $file->getWidth(), $file->getHeight(), |
| 99 | + $wgLang->formatSize( $file->getSize() ) ); |
| 100 | + } |
95 | 101 | } |
96 | 102 | |
| 103 | + |
Index: trunk/phase3/languages/messages/MessagesEn.php |
— | — | @@ -209,8 +209,10 @@ |
210 | 210 | |
211 | 211 | /** |
212 | 212 | * Magic words |
213 | | - * Customisable syntax for wikitext and elsewhere |
| 213 | + * Customisable syntax for wikitext and elsewhere. |
214 | 214 | * |
| 215 | + * IDs must be valid identifiers, they can't contain hyphens. |
| 216 | + * |
215 | 217 | * Note to translators: |
216 | 218 | * Please include the English words as synonyms. This allows people |
217 | 219 | * from other wikis to contribute more easily. |
— | — | @@ -287,10 +289,10 @@ |
288 | 290 | 'img_sub' => array( 1, 'sub' ), |
289 | 291 | 'img_super' => array( 1, 'super', 'sup' ), |
290 | 292 | 'img_top' => array( 1, 'top' ), |
291 | | - 'img_text-top' => array( 1, 'text-top' ), |
| 293 | + 'img_text_top' => array( 1, 'text-top' ), |
292 | 294 | 'img_middle' => array( 1, 'middle' ), |
293 | 295 | 'img_bottom' => array( 1, 'bottom' ), |
294 | | - 'img_text-bottom' => array( 1, 'text-bottom' ), |
| 296 | + 'img_text_bottom' => array( 1, 'text-bottom' ), |
295 | 297 | 'int' => array( 0, 'INT:' ), |
296 | 298 | 'sitename' => array( 1, 'SITENAME' ), |
297 | 299 | 'ns' => array( 0, 'NS:' ), |
— | — | @@ -2422,10 +2424,11 @@ |
2423 | 2425 | 'imagemaxsize' => 'Limit images on image description pages to:', |
2424 | 2426 | 'thumbsize' => 'Thumbnail size:', |
2425 | 2427 | 'widthheight' => '$1×$2', # only translate this message to other languages if you have to change it |
| 2428 | +'widthheightpage' => '$1×$2, $3 pages', |
2426 | 2429 | 'file-info' => '(file size: $1, MIME type: $2)', |
2427 | 2430 | 'file-info-size' => '($1 × $2 pixel, file size: $3, MIME type: $4)', |
2428 | 2431 | '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)', |
2430 | 2433 | 'show-big-image' => 'Full resolution', |
2431 | 2434 | 'show-big-image-thumb' => '<small>Size of this preview: $1 × $2 pixels</small>', |
2432 | 2435 | |
— | — | @@ -2434,6 +2437,12 @@ |
2435 | 2438 | 'showhidebots' => '($1 bots)', |
2436 | 2439 | 'noimages' => 'Nothing to see.', |
2437 | 2440 | |
| 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 | + |
2438 | 2447 | # Bad image list |
2439 | 2448 | 'bad_image_list' => 'The format is as follows: |
2440 | 2449 | |
Index: trunk/phase3/languages/messages/MessagesKk_cn.php |
— | — | @@ -223,10 +223,10 @@ |
224 | 224 | 'img_sub' => array( 1, 'استىلىعى', 'است', 'sub'), |
225 | 225 | 'img_super' => array( 1, 'ٷستٸلٸگٸ', 'ٷست', 'sup', 'super', 'sup' ), |
226 | 226 | 'img_top' => array( 1, 'ٷستٸنە', 'top' ), |
227 | | - 'img_text-top' => array( 1, 'مٵتٸن-ٷستٸندە', 'text-top' ), |
| 227 | + 'img_text_top' => array( 1, 'مٵتٸن-ٷستٸندە', 'text-top' ), |
228 | 228 | 'img_middle' => array( 1, 'ارالىعىنا', 'middle' ), |
229 | 229 | 'img_bottom' => array( 1, 'استىنا', 'bottom' ), |
230 | | - 'img_text-bottom' => array( 1, 'مٵتٸن-استىندا', 'text-bottom' ), |
| 230 | + 'img_text_bottom' => array( 1, 'مٵتٸن-استىندا', 'text-bottom' ), |
231 | 231 | 'int' => array( 0, 'ٸشكٸ:', 'INT:' ), |
232 | 232 | 'sitename' => array( 1, 'توراپاتاۋى', 'SITENAME' ), |
233 | 233 | 'ns' => array( 0, 'ەا:', 'ەسٸمايا:', 'NS:' ), |
Index: trunk/phase3/languages/messages/MessagesKk_kz.php |
— | — | @@ -215,10 +215,10 @@ |
216 | 216 | 'img_sub' => array( 1, 'астылығы', 'аст', 'sub'), |
217 | 217 | 'img_super' => array( 1, 'үстілігі', 'үст', 'sup', 'super', 'sup' ), |
218 | 218 | 'img_top' => array( 1, 'үстіне', 'top' ), |
219 | | - 'img_text-top' => array( 1, 'мәтін-үстінде', 'text-top' ), |
| 219 | + 'img_text_top' => array( 1, 'мәтін-үстінде', 'text-top' ), |
220 | 220 | 'img_middle' => array( 1, 'аралығына', 'middle' ), |
221 | 221 | 'img_bottom' => array( 1, 'астына', 'bottom' ), |
222 | | - 'img_text-bottom' => array( 1, 'мәтін-астында', 'text-bottom' ), |
| 222 | + 'img_text_bottom' => array( 1, 'мәтін-астында', 'text-bottom' ), |
223 | 223 | 'int' => array( 0, 'ІШКІ:', 'INT:' ), |
224 | 224 | 'sitename' => array( 1, 'ТОРАПАТАУЫ', 'SITENAME' ), |
225 | 225 | 'ns' => array( 0, 'ЕА:', 'ЕСІМАЯ:', 'NS:' ), |
Index: trunk/phase3/languages/messages/MessagesCs.php |
— | — | @@ -133,10 +133,10 @@ |
134 | 134 | 'img_sub' => array( 1, 'sub' ), |
135 | 135 | 'img_super' => array( 1, 'super', 'sup' ), |
136 | 136 | 'img_top' => array( 1, 'top' ), |
137 | | - 'img_text-top' => array( 1, 'text-top' ), |
| 137 | + 'img_text_top' => array( 1, 'text-top' ), |
138 | 138 | 'img_middle' => array( 1, 'middle' ), |
139 | 139 | 'img_bottom' => array( 1, 'bottom' ), |
140 | | - 'img_text-bottom' => array( 1, 'text-bottom' ), |
| 140 | + 'img_text_bottom' => array( 1, 'text-bottom' ), |
141 | 141 | 'int' => array( 0, 'INT:' ), |
142 | 142 | 'sitename' => array( 1, 'SITENAME', 'NÁZEVSERVERU' ), |
143 | 143 | 'ns' => array( 0, 'NS:' ), |
Index: trunk/phase3/languages/messages/MessagesId.php |
— | — | @@ -147,10 +147,10 @@ |
148 | 148 | 'img_sub' => array( 1, 'sub' ), |
149 | 149 | 'img_super' => array( 1, 'super', 'sup' ), |
150 | 150 | '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' ), |
152 | 152 | 'img_middle' => array( 1, 'tengah', 'middle' ), |
153 | 153 | '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' ), |
155 | 155 | 'int' => array( 0, 'INT:' ), |
156 | 156 | 'sitename' => array( 1, 'NAMASITUS', 'SITENAME' ), |
157 | 157 | 'ns' => array( 0, 'RN:', 'NS:' ), |
Index: trunk/phase3/languages/messages/MessagesAr.php |
— | — | @@ -156,10 +156,10 @@ |
157 | 157 | 'img_page' => array( 1, "صفحة=$1", "صفحة $1", "page=$1", "page $1" ), |
158 | 158 | 'img_border' => array( 1, "حد", "حدود", "border" ), |
159 | 159 | 'img_top' => array( 1, "أعلى", "top" ), |
160 | | - 'img_text-top' => array( 1, "نص_أعلى", "text-top" ), |
| 160 | + 'img_text_top' => array( 1, "نص_أعلى", "text-top" ), |
161 | 161 | 'img_middle' => array( 1, "وسط", "middle" ), |
162 | 162 | 'img_bottom' => array( 1, "أسفل", "bottom" ), |
163 | | - 'img_text-bottom' => array( 1, "نص_أسفل", "text-bottom" ), |
| 163 | + 'img_text_bottom' => array( 1, "نص_أسفل", "text-bottom" ), |
164 | 164 | 'int' => array( 0, "محتوى:", "INT:" ), |
165 | 165 | 'sitename' => array( 1, "اسم_الموقع", "اسم_موقع", "SITENAME" ), |
166 | 166 | 'ns' => array( 0, "نط:", "NS:" ), |
Index: trunk/phase3/languages/messages/MessagesBg.php |
— | — | @@ -114,10 +114,10 @@ |
115 | 115 | 'img_sub' => array( 1, 'sub' ), |
116 | 116 | 'img_super' => array( 1, 'super', 'sup' ), |
117 | 117 | 'img_top' => array( 1, 'top' ), |
118 | | - 'img_text-top' => array( 1, 'text-top' ), |
| 118 | + 'img_text_top' => array( 1, 'text-top' ), |
119 | 119 | 'img_middle' => array( 1, 'middle' ), |
120 | 120 | 'img_bottom' => array( 1, 'bottom' ), |
121 | | - 'img_text-bottom' => array( 1, 'text-bottom' ), |
| 121 | + 'img_text_bottom' => array( 1, 'text-bottom' ), |
122 | 122 | 'int' => array( 0, 'INT:', 'ВЪТР:'), |
123 | 123 | 'sitename' => array( 1, 'SITENAME', 'ИМЕНАСАЙТА'), |
124 | 124 | 'ns' => array( 0, 'NS:', 'ИП:' ), |
Index: trunk/phase3/languages/messages/MessagesNl.php |
— | — | @@ -165,10 +165,10 @@ |
166 | 166 | 'img_sub' => array( 1, 'sub' ), |
167 | 167 | 'img_super' => array( 1, 'super', 'sup' ), |
168 | 168 | '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' ), |
170 | 170 | 'img_middle' => array( 1, 'middle', 'midden' ), |
171 | 171 | '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' ), |
173 | 173 | 'int' => array( 0, 'INT:' ), |
174 | 174 | 'sitename' => array( 1, 'SITENAME', 'SITENAAM' ), |
175 | 175 | 'ns' => array( 0, 'NS:', 'NR:' ), |
Index: trunk/phase3/languages/messages/MessagesKk_tr.php |
— | — | @@ -216,10 +216,10 @@ |
217 | 217 | 'img_sub' => array( 1, 'astılığı', 'ast', 'sub'), |
218 | 218 | 'img_super' => array( 1, 'üstiligi', 'üst', 'sup', 'super', 'sup' ), |
219 | 219 | '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' ), |
221 | 221 | 'img_middle' => array( 1, 'aralığına', 'middle' ), |
222 | 222 | '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' ), |
224 | 224 | 'int' => array( 0, 'İŞKİ:', 'INT:' ), |
225 | 225 | 'sitename' => array( 1, 'TORAPATAWI', 'SITENAME' ), |
226 | 226 | 'ns' => array( 0, 'EA:', 'ESİMAYA:', 'NS:' ), |
Index: trunk/phase3/languages/Language.php |
— | — | @@ -1824,6 +1824,73 @@ |
1825 | 1825 | wfProfileOut( __METHOD__ ); |
1826 | 1826 | return array( $wikiUpperChars, $wikiLowerChars ); |
1827 | 1827 | } |
| 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 | + } |
1828 | 1894 | } |
1829 | 1895 | |
1830 | 1896 | |
| 1897 | + |
Index: trunk/phase3/RELEASE-NOTES |
— | — | @@ -173,6 +173,7 @@ |
174 | 174 | * (bug 10872) Fall back to sane defaults when generating protection selector |
175 | 175 | labels for custom restriction levels |
176 | 176 | * Show edit count in user preferences |
| 177 | +* Improved support for audio/video extensions |
177 | 178 | |
178 | 179 | == Bugfixes since 1.10 == |
179 | 180 | |
Index: trunk/extensions/OggHandler/OggHandler.php |
— | — | @@ -3,27 +3,34 @@ |
4 | 4 | class OggHandler extends MediaHandler { |
5 | 5 | const OGG_METADATA_VERSION = 1; |
6 | 6 | |
| 7 | + var $videoTypes = array( 'Theora' ); |
| 8 | + var $audioTypes = array( 'Vorbis', 'Speex', 'FLAC' ); |
| 9 | + |
7 | 10 | function isEnabled() { |
8 | | - if ( !class_exists( 'File_Ogg' ) ) { |
9 | | - return include( 'File/Ogg.php' ); |
10 | | - } else { |
11 | | - return true; |
12 | | - } |
| 11 | + return true; |
13 | 12 | } |
14 | 13 | |
| 14 | + function getParamMap() { |
| 15 | + // TODO: add thumbtime, noplayer |
| 16 | + return array( 'img_width' => 'width' ); |
| 17 | + } |
| 18 | + |
15 | 19 | function validateParam( $name, $value ) { |
16 | 20 | // TODO |
17 | | - return false; |
| 21 | + return true; |
18 | 22 | } |
19 | 23 | |
20 | 24 | function makeParamString( $params ) { |
| 25 | + // No parameters just yet, the thumbnails are always full-size |
| 26 | + return ''; |
| 27 | + /* |
21 | 28 | $s = ''; |
22 | 29 | foreach ( $params as $name => $value ) { |
23 | 30 | if ( $s !== '' ) { |
24 | 31 | $s .= '-'; |
25 | 32 | } |
26 | 33 | $s .= "$name=$value"; |
27 | | - } |
| 34 | + }*/ |
28 | 35 | } |
29 | 36 | |
30 | 37 | function parseParamString( $str ) { |
— | — | @@ -36,13 +43,33 @@ |
37 | 44 | return true; |
38 | 45 | } |
39 | 46 | |
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 ); |
43 | 65 | } |
44 | 66 | |
45 | 67 | function getMetadata( $image, $path ) { |
46 | 68 | $metadata = array( 'version' => self::OGG_METADATA_VERSION ); |
| 69 | + |
| 70 | + if ( !class_exists( 'File_Ogg' ) ) { |
| 71 | + return require( 'File/Ogg.php' ); |
| 72 | + } |
| 73 | + |
47 | 74 | try { |
48 | 75 | $f = new File_Ogg( $path ); |
49 | 76 | $streams = array(); |
— | — | @@ -50,15 +77,19 @@ |
51 | 78 | foreach ( $streamIDs as $streamID ) { |
52 | 79 | $stream = $f->getStream( $streamID ); |
53 | 80 | $streams[$streamID] = array( |
| 81 | + 'serial' => $stream->getSerial(), |
| 82 | + 'group' => $stream->getGroup(), |
54 | 83 | 'type' => $stream->getType(), |
55 | 84 | 'vendor' => $stream->getVendor(), |
56 | 85 | 'length' => $stream->getLength(), |
| 86 | + 'size' => $stream->getSize(), |
57 | 87 | 'header' => $stream->getHeader(), |
58 | 88 | 'comments' => $stream->getComments() |
59 | 89 | ); |
60 | 90 | } |
61 | 91 | } |
62 | 92 | $metadata['streams'] = $streams; |
| 93 | + $metadata['length'] = $f->getLength(); |
63 | 94 | } catch ( PEAR_Exception $e ) { |
64 | 95 | // File not found, invalid stream, etc. |
65 | 96 | $metadata['error'] = array( |
— | — | @@ -69,25 +100,87 @@ |
70 | 101 | return serialize( $metadata ); |
71 | 102 | } |
72 | 103 | |
| 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 | + |
73 | 113 | function getMetadataType( $image ) { |
74 | 114 | return 'ogg'; |
75 | 115 | } |
76 | 116 | |
77 | 117 | 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; |
80 | 119 | } |
| 120 | + |
| 121 | + function getThumbType( $ext, $mime ) { |
| 122 | + return array( 'jpg', 'image/jpeg' ); |
| 123 | + } |
81 | 124 | |
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 ); |
86 | 132 | |
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 ); |
90 | 179 | } |
91 | 180 | |
| 181 | + function canRender() { return true; } |
| 182 | + function mustRender( $file ) { return true; } |
| 183 | + |
| 184 | + /* |
92 | 185 | function formatMetadata( $image, $metadata ) { |
93 | 186 | if ( !$this->isMetadataValid( $image, $metadata ) ) { |
94 | 187 | return false; |
— | — | @@ -106,6 +199,7 @@ |
107 | 200 | self::addMeta( $formatted, 'visible', 'ogg', 'type', $stream['type'], $n ); |
108 | 201 | self::addMeta( $formatted, 'visible', 'ogg', 'vendor', $stream['vendor'], $n ); |
109 | 202 | self::addMeta( $formatted, 'visible', 'ogg', 'length', $stream['length'], $n ); |
| 203 | + self::addMeta( $formatted, 'visible', 'ogg', 'size', $stream['size'], $n ); |
110 | 204 | |
111 | 205 | foreach ( $stream['header'] as $name => $value ) { |
112 | 206 | self::addMeta( $formatted, 'visible', $type, $name, $value, $n ); |
— | — | @@ -116,12 +210,212 @@ |
117 | 211 | } |
118 | 212 | } |
119 | 213 | 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 | + } |
120 | 223 | } |
121 | 224 | |
| 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 | + } |
122 | 236 | |
| 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 | + } |
123 | 347 | } |
124 | 348 | |
| 349 | +class OggTransformOutput extends MediaTransformOutput { |
| 350 | + static $serial = 0; |
125 | 351 | |
| 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 | + } |
126 | 360 | |
| 361 | + function toHtml( $attribs = array() , $linkAttribs = false ) { |
| 362 | + wfLoadExtensionMessages( 'OggHandler' ); |
127 | 363 | |
| 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 | + |
128 | 422 | ?> |
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(/&/, '&') + "'/>" + |
| 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 |
1 | 264 | + 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 |
1 | 20 | + 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 |
1 | 28 | + native |