2006-11-01 06:34:42 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Image map extension.
|
|
|
|
*
|
|
|
|
* Syntax:
|
|
|
|
* <imagemap>
|
|
|
|
* Image:Foo.jpg | 100px | picture of a foo
|
|
|
|
*
|
|
|
|
* rect 0 0 50 50 [[Foo type A]]
|
|
|
|
* circle 50 50 20 [[Foo type B]]
|
2006-12-13 05:55:37 +00:00
|
|
|
*
|
|
|
|
* desc bottom-left
|
2006-11-01 06:34:42 +00:00
|
|
|
* </imagemap>
|
|
|
|
*
|
|
|
|
* Coordinates are relative to the source image, not the thumbnail
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
class ImageMap {
|
|
|
|
static public $id = 0;
|
|
|
|
|
2006-12-13 05:55:37 +00:00
|
|
|
const TOP_RIGHT = 0;
|
|
|
|
const BOTTOM_RIGHT = 1;
|
|
|
|
const BOTTOM_LEFT = 2;
|
|
|
|
const TOP_LEFT = 3;
|
|
|
|
const NONE = 4;
|
|
|
|
|
2006-11-01 06:34:42 +00:00
|
|
|
static function render( $input, $params, $parser ) {
|
2008-06-16 15:08:56 +00:00
|
|
|
global $wgScriptPath, $wgUser, $wgUrlProtocols, $wgNoFollowLinks;
|
2008-01-11 08:41:57 +00:00
|
|
|
wfLoadExtensionMessages( 'ImageMap' );
|
2006-12-13 05:55:37 +00:00
|
|
|
|
2006-11-01 06:34:42 +00:00
|
|
|
$lines = explode( "\n", $input );
|
|
|
|
|
|
|
|
$first = true;
|
|
|
|
$lineNum = 0;
|
|
|
|
$output = '';
|
|
|
|
$links = array();
|
2007-04-17 11:59:53 +00:00
|
|
|
|
|
|
|
# Define canonical desc types to allow i18n of 'imagemap_desc_types'
|
|
|
|
$descTypesCanonical = 'top-right, bottom-right, bottom-left, top-left, none';
|
2006-12-13 05:55:37 +00:00
|
|
|
$descType = self::BOTTOM_RIGHT;
|
2007-01-05 16:44:59 +00:00
|
|
|
$defaultLinkAttribs = false;
|
2007-04-17 09:18:42 +00:00
|
|
|
$realmap = true;
|
2006-11-01 06:34:42 +00:00
|
|
|
foreach ( $lines as $line ) {
|
|
|
|
++$lineNum;
|
2007-04-18 09:14:41 +00:00
|
|
|
$externLink = false;
|
2006-11-01 06:34:42 +00:00
|
|
|
|
|
|
|
$line = trim( $line );
|
2006-12-13 05:55:37 +00:00
|
|
|
if ( $line == '' || $line[0] == '#' ) {
|
2006-11-01 06:34:42 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $first ) {
|
|
|
|
$first = false;
|
|
|
|
|
|
|
|
# The first line should have an image specification on it
|
|
|
|
# Extract it and render the HTML
|
|
|
|
$bits = explode( '|', $line, 2 );
|
|
|
|
if ( count( $bits ) == 1 ) {
|
|
|
|
$image = $bits[0];
|
|
|
|
$options = '';
|
|
|
|
} else {
|
|
|
|
list( $image, $options ) = $bits;
|
|
|
|
}
|
|
|
|
$imageTitle = Title::newFromText( $image );
|
|
|
|
if ( !$imageTitle || $imageTitle->getNamespace() != NS_IMAGE ) {
|
|
|
|
return self::error( 'imagemap_no_image' );
|
|
|
|
}
|
2008-08-02 03:23:05 +00:00
|
|
|
if ( wfIsBadImage( $imageTitle->getDBkey() , $parser->mTitle ) ) {
|
|
|
|
return self::error( 'imagemap_bad_image' );
|
|
|
|
}
|
2008-09-09 22:31:06 +00:00
|
|
|
// Parse the options so we can use links and the like in the caption
|
It's incorrect to use both Sanitizer::escapeHtmlAllowEntities() and Parser::recursiveTagParse(). Parser::recursiveTagParse() accepts plain wikitext as input, and will handle any invalid entities itself.
Note that ideally, ImageMap would parse the image options in the same way as the parser does image links. This would mean replaceVariables(), removeHTMLtags(), doTableStuff(), <hr/>, doDoubleUnderscore(), doHeadings(), DateFormatter::reformat(), doAllQuotes(), replaceExternalLinks() and replaceInternalLinks2(). But recursiveTagParse() is almost the same anyway: only doMagicLinks() and formatHeadings() are added, and RIL/REL are done in the opposite order.
2008-09-15 07:10:40 +00:00
|
|
|
$parsedoptions = $parser->recursiveTagParse( $options );
|
2008-09-09 22:12:28 +00:00
|
|
|
$imageHTML = $parser->makeImage( $imageTitle, $parsedoptions );
|
2008-03-09 10:37:04 +00:00
|
|
|
$parser->mOutput->addImage( $imageTitle->getDBkey() );
|
2006-11-01 06:34:42 +00:00
|
|
|
|
2008-05-14 23:12:52 +00:00
|
|
|
$domDoc = new DOMDocument();
|
|
|
|
$domDoc->loadXML( $imageHTML );
|
2006-12-13 05:55:37 +00:00
|
|
|
$xpath = new DOMXPath( $domDoc );
|
|
|
|
$imgs = $xpath->query( '//img' );
|
|
|
|
if ( !$imgs->length ) {
|
2006-11-01 06:34:42 +00:00
|
|
|
return self::error( 'imagemap_invalid_image' );
|
|
|
|
}
|
2006-12-13 05:55:37 +00:00
|
|
|
$imageNode = $imgs->item(0);
|
|
|
|
$thumbWidth = $imageNode->getAttribute('width');
|
|
|
|
$thumbHeight = $imageNode->getAttribute('height');
|
2006-11-01 06:34:42 +00:00
|
|
|
|
2008-04-26 18:24:23 +00:00
|
|
|
if( function_exists( 'wfFindFile' ) ) {
|
|
|
|
$imageObj = wfFindFile( $imageTitle );
|
|
|
|
} else {
|
|
|
|
// Old MW
|
|
|
|
$imageObj = Image::newFromTitle( $imageTitle );
|
|
|
|
}
|
|
|
|
if ( !$imageObj || !$imageObj->exists() ) {
|
2007-05-28 01:44:57 +00:00
|
|
|
return self::error( 'imagemap_invalid_image' );
|
|
|
|
}
|
2006-11-01 06:34:42 +00:00
|
|
|
# Add the linear dimensions to avoid inaccuracy in the scale
|
|
|
|
# factor when one is much larger than the other
|
|
|
|
# (sx+sy)/(x+y) = s
|
|
|
|
$denominator = $imageObj->getWidth() + $imageObj->getHeight();
|
|
|
|
$numerator = $thumbWidth + $thumbHeight;
|
|
|
|
if ( $denominator <= 0 || $numerator <= 0 ) {
|
|
|
|
return self::error( 'imagemap_invalid_image' );
|
|
|
|
}
|
|
|
|
$scale = $numerator / $denominator;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2006-12-13 05:55:37 +00:00
|
|
|
# Handle desc spec
|
|
|
|
$cmd = strtok( $line, " \t" );
|
|
|
|
if ( $cmd == 'desc' ) {
|
|
|
|
$typesText = wfMsgForContent( 'imagemap_desc_types' );
|
2007-04-17 11:59:53 +00:00
|
|
|
if ( $descTypesCanonical != $typesText ) {
|
|
|
|
// i18n desc types exists
|
|
|
|
$typesText = $descTypesCanonical . ', ' . $typesText;
|
|
|
|
}
|
2006-12-13 05:55:37 +00:00
|
|
|
$types = array_map( 'trim', explode( ',', $typesText ) );
|
|
|
|
$type = trim( strtok( '' ) );
|
|
|
|
$descType = array_search( $type, $types );
|
2007-04-17 11:59:53 +00:00
|
|
|
if ( $descType > 4 ) {
|
|
|
|
// A localized descType is used. Subtract 5 to reach the canonical desc type.
|
|
|
|
$descType = $descType - 5;
|
|
|
|
}
|
|
|
|
if ( $descType === false || $descType < 0 ) { // <0? In theory never, but paranoia...
|
2006-12-13 05:55:37 +00:00
|
|
|
return self::error( 'imagemap_invalid_desc', $typesText );
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2006-11-01 06:34:42 +00:00
|
|
|
# Find the link
|
2007-04-18 09:14:41 +00:00
|
|
|
$link = trim( strstr( $line, '[' ) );
|
2006-11-01 06:34:42 +00:00
|
|
|
if ( preg_match( '/^ \[\[ ([^|]*+) \| ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) {
|
|
|
|
$title = Title::newFromText( $m[1] );
|
|
|
|
$alt = trim( $m[2] );
|
|
|
|
} elseif ( preg_match( '/^ \[\[ ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) {
|
|
|
|
$title = Title::newFromText( $m[1] );
|
2007-05-08 14:53:14 +00:00
|
|
|
if (is_null($title))
|
|
|
|
return self::error('imagemap_invalid_title', $lineNum);
|
2006-12-13 05:55:37 +00:00
|
|
|
$alt = $title->getFullText();
|
2008-06-16 15:08:56 +00:00
|
|
|
} elseif ( in_array( substr( $link , 1 , strpos($link, '//' )+1 ) , $wgUrlProtocols ) || in_array( substr( $link , 1 , strpos($link, ':' ) ) , $wgUrlProtocols ) ) {
|
2007-04-18 09:14:41 +00:00
|
|
|
if ( preg_match( '/^ \[ ([^\s]*+) \s ([^\]]*+) \] \w* $ /x', $link, $m ) ) {
|
2008-01-27 01:58:39 +00:00
|
|
|
$title = $m[1];
|
|
|
|
$alt = trim( $m[2] );
|
2007-04-18 09:14:41 +00:00
|
|
|
$externLink = true;
|
|
|
|
} elseif ( preg_match( '/^ \[ ([^\]]*+) \] \w* $ /x', $link, $m ) ) {
|
2008-01-27 01:58:39 +00:00
|
|
|
$title = $alt = trim( $m[1] );
|
2007-04-18 09:14:41 +00:00
|
|
|
$externLink = true;
|
|
|
|
}
|
2006-11-01 06:34:42 +00:00
|
|
|
} else {
|
|
|
|
return self::error( 'imagemap_no_link', $lineNum );
|
|
|
|
}
|
|
|
|
if ( !$title ) {
|
|
|
|
return self::error( 'imagemap_invalid_title', $lineNum );
|
|
|
|
}
|
|
|
|
|
|
|
|
$shapeSpec = substr( $line, 0, -strlen( $link ) );
|
|
|
|
|
|
|
|
# Tokenize shape spec
|
|
|
|
$shape = strtok( $shapeSpec, " \t" );
|
|
|
|
switch ( $shape ) {
|
|
|
|
case 'default':
|
|
|
|
$coords = array();
|
|
|
|
break;
|
|
|
|
case 'rect':
|
|
|
|
$coords = self::tokenizeCoords( 4, $lineNum );
|
|
|
|
if ( !is_array( $coords ) ) {
|
|
|
|
return $coords;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'circle':
|
|
|
|
$coords = self::tokenizeCoords( 3, $lineNum );
|
|
|
|
if ( !is_array( $coords ) ) {
|
|
|
|
return $coords;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'poly':
|
2007-03-09 17:35:10 +00:00
|
|
|
$coords = array();
|
2006-11-01 06:34:42 +00:00
|
|
|
$coord = strtok( " \t" );
|
|
|
|
while ( $coord !== false ) {
|
|
|
|
$coords[] = $coord;
|
2006-11-27 03:13:46 +00:00
|
|
|
$coord = strtok( " \t" );
|
2006-11-01 06:34:42 +00:00
|
|
|
}
|
|
|
|
if ( !count( $coords ) ) {
|
|
|
|
return self::error( 'imagemap_missing_coord', $lineNum );
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return self::error( 'imagemap_unrecognised_shape', $lineNum );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Scale the coords using the size of the source image
|
|
|
|
foreach ( $coords as $i => $c ) {
|
2007-01-15 19:44:30 +00:00
|
|
|
$coords[$i] = intval( round( $c * $scale ) );
|
2006-11-01 06:34:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
# Construct the area tag
|
2007-04-18 09:14:41 +00:00
|
|
|
$attribs = array();
|
|
|
|
if ( $externLink ) {
|
|
|
|
$attribs['href'] = $title;
|
|
|
|
$attribs['class'] = 'plainlinks';
|
|
|
|
if ( $wgNoFollowLinks ) {
|
|
|
|
$attribs['rel'] = 'nofollow';
|
|
|
|
}
|
2007-08-07 17:01:38 +00:00
|
|
|
} else if ( $title->getFragment() != '' && $title->getPrefixedDBkey() == '' ) {
|
|
|
|
# XXX: kluge to handle [[#Fragment]] links, should really fix getLocalURL()
|
|
|
|
# in Title.php to return an empty string in this case
|
|
|
|
$attribs['href'] = $title->getFragmentForURL();
|
2007-04-18 09:14:41 +00:00
|
|
|
} else {
|
|
|
|
$attribs['href'] = $title->escapeLocalURL() . $title->getFragmentForURL();
|
|
|
|
}
|
2007-01-05 16:44:59 +00:00
|
|
|
if ( $shape != 'default' ) {
|
|
|
|
$attribs['shape'] = $shape;
|
|
|
|
}
|
2006-11-01 06:34:42 +00:00
|
|
|
if ( $coords ) {
|
|
|
|
$attribs['coords'] = implode( ',', $coords );
|
|
|
|
}
|
|
|
|
if ( $alt != '' ) {
|
2007-01-05 16:44:59 +00:00
|
|
|
if ( $shape != 'default' ) {
|
|
|
|
$attribs['alt'] = $alt;
|
|
|
|
}
|
2006-12-13 05:55:37 +00:00
|
|
|
$attribs['title'] = $alt;
|
|
|
|
}
|
2007-01-05 16:44:59 +00:00
|
|
|
if ( $shape == 'default' ) {
|
|
|
|
$defaultLinkAttribs = $attribs;
|
|
|
|
} else {
|
|
|
|
$output .= Xml::element( 'area', $attribs ) . "\n";
|
|
|
|
}
|
2007-04-18 09:14:41 +00:00
|
|
|
if ( $externLink ) {
|
|
|
|
$extLinks[] = $title;
|
|
|
|
} else {
|
|
|
|
$links[] = $title;
|
|
|
|
}
|
2006-11-01 06:34:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if ( $first ) {
|
|
|
|
return self::error( 'imagemap_no_image' );
|
|
|
|
}
|
|
|
|
|
2007-04-17 09:18:42 +00:00
|
|
|
if ( $output == '' && $defaultLinkAttribs == '' ) {
|
2006-11-01 06:34:42 +00:00
|
|
|
return self::error( 'imagemap_no_areas' );
|
2007-04-17 09:18:42 +00:00
|
|
|
} elseif ( $output == '' && $defaultLinkAttribs != '' ) {
|
|
|
|
// no areas defined, default only. It's not a real imagemap, so we do not need some tags
|
|
|
|
$realmap = false;
|
2006-11-01 06:34:42 +00:00
|
|
|
}
|
|
|
|
|
2007-04-17 09:18:42 +00:00
|
|
|
if ( $realmap ) {
|
|
|
|
# Construct the map
|
|
|
|
$mapName = "ImageMap_" . ++self::$id;
|
|
|
|
$output = "<map name=\"$mapName\">\n$output</map>\n";
|
|
|
|
|
|
|
|
# Alter the image tag
|
|
|
|
$imageNode->setAttribute( 'usemap', "#$mapName" );
|
|
|
|
}
|
2006-12-13 05:55:37 +00:00
|
|
|
# Add a surrounding div, remove the default link to the description page
|
|
|
|
$anchor = $imageNode->parentNode;
|
|
|
|
$parent = $anchor->parentNode;
|
|
|
|
$div = $parent->insertBefore( new DOMElement( 'div' ), $anchor );
|
|
|
|
$div->setAttribute( 'style', 'position: relative;' );
|
2007-01-05 16:44:59 +00:00
|
|
|
if ( $defaultLinkAttribs ) {
|
|
|
|
$defaultAnchor = $div->appendChild( new DOMElement( 'a' ) );
|
|
|
|
foreach ( $defaultLinkAttribs as $name => $value ) {
|
|
|
|
$defaultAnchor->setAttribute( $name, $value );
|
|
|
|
}
|
|
|
|
$imageParent = $defaultAnchor;
|
|
|
|
} else {
|
|
|
|
$imageParent = $div;
|
|
|
|
}
|
|
|
|
|
|
|
|
$imageParent->appendChild( $imageNode->cloneNode( true ) );
|
2006-12-13 05:55:37 +00:00
|
|
|
$parent->removeChild( $anchor );
|
|
|
|
|
|
|
|
# Determine whether a "magnify" link is present
|
|
|
|
$xpath = new DOMXPath( $domDoc );
|
|
|
|
$magnify = $xpath->query( '//div[@class="magnify"]' );
|
|
|
|
if ( !$magnify->length && $descType != self::NONE ) {
|
|
|
|
# Add image description link
|
|
|
|
if ( $descType == self::TOP_LEFT || $descType == self::BOTTOM_LEFT ) {
|
|
|
|
$descLeft = 0;
|
|
|
|
} else {
|
|
|
|
$descLeft = $thumbWidth - 20;
|
|
|
|
}
|
|
|
|
if ( $descType == self::TOP_LEFT || $descType == self::TOP_RIGHT ) {
|
|
|
|
$descTop = 0;
|
|
|
|
} else {
|
|
|
|
$descTop = $thumbHeight - 20;
|
|
|
|
}
|
|
|
|
$descAnchor = $div->appendChild( new DOMElement( 'a' ) );
|
|
|
|
$descAnchor->setAttribute( 'href', $imageTitle->escapeLocalURL() );
|
|
|
|
$descAnchor->setAttribute( 'title', wfMsgForContent( 'imagemap_description' ) );
|
|
|
|
$descAnchor->setAttribute( 'style', "position:absolute; top: {$descTop}px; left: {$descLeft}px;" );
|
|
|
|
$descImg = $descAnchor->appendChild( new DOMElement( 'img' ) );
|
|
|
|
$descImg->setAttribute( 'alt', wfMsgForContent( 'imagemap_description' ) );
|
|
|
|
$descImg->setAttribute( 'src', "$wgScriptPath/extensions/ImageMap/desc-20.png" );
|
|
|
|
$descImg->setAttribute( 'style', 'border: none;' );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Output the result
|
|
|
|
# We use saveXML() not saveHTML() because then we get XHTML-compliant output.
|
|
|
|
# The disadvantage is that we have to strip out the DTD
|
|
|
|
$output .= preg_replace( '/<\?xml[^?]*\?>/', '', $domDoc->saveXML() );
|
2006-11-01 06:34:42 +00:00
|
|
|
|
|
|
|
# Register links
|
|
|
|
foreach ( $links as $title ) {
|
2008-01-16 06:08:01 +00:00
|
|
|
if( $title->isExternal() || $title->getNamespace() == NS_SPECIAL ) {
|
|
|
|
// Don't register special or interwiki links...
|
|
|
|
} elseif( $title->getNamespace() == NS_MEDIA ) {
|
|
|
|
// Regular Media: links are recorded as image usages
|
|
|
|
$parser->mOutput->addImage( $title->getDBkey() );
|
|
|
|
} else {
|
|
|
|
// Plain ol' link
|
|
|
|
$parser->mOutput->addLink( $title );
|
|
|
|
}
|
2006-11-01 06:34:42 +00:00
|
|
|
}
|
2007-04-19 06:00:19 +00:00
|
|
|
if ( isset( $extLinks ) ) {
|
|
|
|
foreach ( $extLinks as $title ) {
|
|
|
|
$parser->mOutput->addExternalLink( $title );
|
|
|
|
}
|
2007-04-18 09:14:41 +00:00
|
|
|
}
|
2006-12-13 05:55:37 +00:00
|
|
|
# Armour output against broken parser
|
|
|
|
$output = str_replace( "\n", '', $output );
|
2006-11-01 06:34:42 +00:00
|
|
|
return $output;
|
|
|
|
}
|
|
|
|
|
|
|
|
static function tokenizeCoords( $count, $lineNum ) {
|
|
|
|
$coords = array();
|
|
|
|
for ( $i = 0; $i < $count; $i++ ) {
|
|
|
|
$coord = strtok( " \t" );
|
|
|
|
if ( $coord === false ) {
|
|
|
|
return self::error( 'imagemap_missing_coord', $lineNum );
|
|
|
|
}
|
|
|
|
if ( !is_numeric( $coord ) || $coord > 1e9 || $coord < 0 ) {
|
|
|
|
return self::error( 'imagemap_invalid_coord', $lineNum );
|
|
|
|
}
|
|
|
|
$coords[$i] = $coord;
|
|
|
|
}
|
|
|
|
return $coords;
|
|
|
|
}
|
|
|
|
|
|
|
|
static function error( $name, $line = false ) {
|
2007-08-07 21:48:01 +00:00
|
|
|
return '<p class="error">' . wfMsgForContent( $name, $line ) . '</p>';
|
2006-11-01 06:34:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-06-29 01:36:09 +00:00
|
|
|
|