[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Main file for extension ImageMap. 4 * 5 * @file 6 * @ingroup Extensions 7 * 8 * Syntax: 9 * <imagemap> 10 * Image:Foo.jpg | 100px | picture of a foo 11 * 12 * rect 0 0 50 50 [[Foo type A]] 13 * circle 50 50 20 [[Foo type B]] 14 * 15 * desc bottom-left 16 * </imagemap> 17 * 18 * Coordinates are relative to the source image, not the thumbnail 19 * 20 */ 21 22 class ImageMap { 23 static public $id = 0; 24 25 const TOP_RIGHT = 0; 26 const BOTTOM_RIGHT = 1; 27 const BOTTOM_LEFT = 2; 28 const TOP_LEFT = 3; 29 const NONE = 4; 30 31 /** 32 * @param $input 33 * @param $params 34 * @param $parser Parser 35 * @return array|mixed|string 36 */ 37 public static function render( $input, $params, $parser ) { 38 global $wgExtensionAssetsPath, $wgUrlProtocols, $wgNoFollowLinks; 39 40 $lines = explode( "\n", $input ); 41 42 $first = true; 43 $lineNum = 0; 44 $mapHTML = ''; 45 $links = array(); 46 47 # Define canonical desc types to allow i18n of 'imagemap_desc_types' 48 $descTypesCanonical = 'top-right, bottom-right, bottom-left, top-left, none'; 49 $descType = self::BOTTOM_RIGHT; 50 $defaultLinkAttribs = false; 51 $realmap = true; 52 $extLinks = array(); 53 foreach ( $lines as $line ) { 54 ++$lineNum; 55 $externLink = false; 56 57 $line = trim( $line ); 58 if ( $line == '' || $line[0] == '#' ) { 59 continue; 60 } 61 62 if ( $first ) { 63 $first = false; 64 65 # The first line should have an image specification on it 66 # Extract it and render the HTML 67 $bits = explode( '|', $line, 2 ); 68 if ( count( $bits ) == 1 ) { 69 $image = $bits[0]; 70 $options = ''; 71 } else { 72 list( $image, $options ) = $bits; 73 } 74 $imageTitle = Title::newFromText( $image ); 75 if ( !$imageTitle || $imageTitle->getNamespace() != NS_IMAGE ) { 76 return self::error( 'imagemap_no_image' ); 77 } 78 if ( wfIsBadImage( $imageTitle->getDBkey(), $parser->mTitle ) ) { 79 return self::error( 'imagemap_bad_image' ); 80 } 81 // Parse the options so we can use links and the like in the caption 82 $parsedOptions = $parser->recursiveTagParse( $options ); 83 $imageHTML = $parser->makeImage( $imageTitle, $parsedOptions ); 84 $parser->replaceLinkHolders( $imageHTML ); 85 $imageHTML = $parser->mStripState->unstripBoth( $imageHTML ); 86 $imageHTML = Sanitizer::normalizeCharReferences( $imageHTML ); 87 88 $domDoc = new DOMDocument(); 89 wfSuppressWarnings(); 90 $ok = $domDoc->loadXML( $imageHTML ); 91 wfRestoreWarnings(); 92 if ( !$ok ) { 93 return self::error( 'imagemap_invalid_image' ); 94 } 95 $xpath = new DOMXPath( $domDoc ); 96 $imgs = $xpath->query( '//img' ); 97 if ( !$imgs->length ) { 98 return self::error( 'imagemap_invalid_image' ); 99 } 100 $imageNode = $imgs->item( 0 ); 101 $thumbWidth = $imageNode->getAttribute( 'width' ); 102 $thumbHeight = $imageNode->getAttribute( 'height' ); 103 104 $imageObj = wfFindFile( $imageTitle ); 105 if ( !$imageObj || !$imageObj->exists() ) { 106 return self::error( 'imagemap_invalid_image' ); 107 } 108 # Add the linear dimensions to avoid inaccuracy in the scale 109 # factor when one is much larger than the other 110 # (sx+sy)/(x+y) = s 111 $denominator = $imageObj->getWidth() + $imageObj->getHeight(); 112 $numerator = $thumbWidth + $thumbHeight; 113 if ( $denominator <= 0 || $numerator <= 0 ) { 114 return self::error( 'imagemap_invalid_image' ); 115 } 116 $scale = $numerator / $denominator; 117 continue; 118 } 119 120 # Handle desc spec 121 $cmd = strtok( $line, " \t" ); 122 if ( $cmd == 'desc' ) { 123 $typesText = wfMessage( 'imagemap_desc_types' )->inContentLanguage()->text(); 124 if ( $descTypesCanonical != $typesText ) { 125 // i18n desc types exists 126 $typesText = $descTypesCanonical . ', ' . $typesText; 127 } 128 $types = array_map( 'trim', explode( ',', $typesText ) ); 129 $type = trim( strtok( '' ) ); 130 $descType = array_search( $type, $types ); 131 if ( $descType > 4 ) { 132 // A localized descType is used. Subtract 5 to reach the canonical desc type. 133 $descType = $descType - 5; 134 } 135 if ( $descType === false || $descType < 0 ) { // <0? In theory never, but paranoia... 136 return self::error( 'imagemap_invalid_desc', $typesText ); 137 } 138 continue; 139 } 140 141 # Find the link 142 $link = trim( strstr( $line, '[' ) ); 143 $m = array(); 144 if ( preg_match( '/^ \[\[ ([^|]*+) \| ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) { 145 $title = Title::newFromText( $m[1] ); 146 $alt = trim( $m[2] ); 147 } elseif ( preg_match( '/^ \[\[ ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) { 148 $title = Title::newFromText( $m[1] ); 149 if ( is_null( $title ) ) { 150 return self::error( 'imagemap_invalid_title', $lineNum ); 151 } 152 $alt = $title->getFullText(); 153 } elseif ( in_array( substr( $link, 1, strpos( $link, '//' ) + 1 ), $wgUrlProtocols ) || in_array( substr( $link, 1, strpos( $link, ':' ) ), $wgUrlProtocols ) ) { 154 if ( preg_match( '/^ \[ ([^\s]*+) \s ([^\]]*+) \] \w* $ /x', $link, $m ) ) { 155 $title = $m[1]; 156 $alt = trim( $m[2] ); 157 $externLink = true; 158 } elseif ( preg_match( '/^ \[ ([^\]]*+) \] \w* $ /x', $link, $m ) ) { 159 $title = $alt = trim( $m[1] ); 160 $externLink = true; 161 } 162 } else { 163 return self::error( 'imagemap_no_link', $lineNum ); 164 } 165 if ( !$title ) { 166 return self::error( 'imagemap_invalid_title', $lineNum ); 167 } 168 169 $shapeSpec = substr( $line, 0, -strlen( $link ) ); 170 171 # Tokenize shape spec 172 $shape = strtok( $shapeSpec, " \t" ); 173 switch ( $shape ) { 174 case 'default': 175 $coords = array(); 176 break; 177 case 'rect': 178 $coords = self::tokenizeCoords( 4, $lineNum ); 179 if ( !is_array( $coords ) ) { 180 return $coords; 181 } 182 break; 183 case 'circle': 184 $coords = self::tokenizeCoords( 3, $lineNum ); 185 if ( !is_array( $coords ) ) { 186 return $coords; 187 } 188 break; 189 case 'poly': 190 $coords = array(); 191 $coord = strtok( " \t" ); 192 while ( $coord !== false ) { 193 $coords[] = $coord; 194 $coord = strtok( " \t" ); 195 } 196 if ( !count( $coords ) ) { 197 return self::error( 'imagemap_missing_coord', $lineNum ); 198 } 199 if ( count( $coords ) % 2 !== 0 ) { 200 return self::error( 'imagemap_poly_odd', $lineNum ); 201 } 202 break; 203 default: 204 return self::error( 'imagemap_unrecognised_shape', $lineNum ); 205 } 206 207 # Scale the coords using the size of the source image 208 foreach ( $coords as $i => $c ) { 209 $coords[$i] = intval( round( $c * $scale ) ); 210 } 211 212 # Construct the area tag 213 $attribs = array(); 214 if ( $externLink ) { 215 $attribs['href'] = $title; 216 $attribs['class'] = 'plainlinks'; 217 if ( $wgNoFollowLinks ) { 218 $attribs['rel'] = 'nofollow'; 219 } 220 } elseif ( $title->getFragment() != '' && $title->getPrefixedDBkey() == '' ) { 221 # XXX: kluge to handle [[#Fragment]] links, should really fix getLocalURL() 222 # in Title.php to return an empty string in this case 223 $attribs['href'] = $title->getFragmentForURL(); 224 } else { 225 $attribs['href'] = $title->getLocalURL() . $title->getFragmentForURL(); 226 } 227 if ( $shape != 'default' ) { 228 $attribs['shape'] = $shape; 229 } 230 if ( $coords ) { 231 $attribs['coords'] = implode( ',', $coords ); 232 } 233 if ( $alt != '' ) { 234 if ( $shape != 'default' ) { 235 $attribs['alt'] = $alt; 236 } 237 $attribs['title'] = $alt; 238 } 239 if ( $shape == 'default' ) { 240 $defaultLinkAttribs = $attribs; 241 } else { 242 $mapHTML .= Xml::element( 'area', $attribs ) . "\n"; 243 } 244 if ( $externLink ) { 245 $extLinks[] = $title; 246 } else { 247 $links[] = $title; 248 } 249 } 250 251 if ( $first ) { 252 return self::error( 'imagemap_no_image' ); 253 } 254 255 if ( $mapHTML == '' ) { 256 // no areas defined, default only. It's not a real imagemap, so we do not need some tags 257 $realmap = false; 258 } 259 260 if ( $realmap ) { 261 # Construct the map 262 # Add random number to avoid breaking cached HTML fragments that are 263 # later joined together on the one page (bug 16471) 264 $mapName = "ImageMap_" . ++self::$id . '_' . mt_rand( 0, 0x7fffffff ); 265 $mapHTML = "<map name=\"$mapName\">\n$mapHTML</map>\n"; 266 267 # Alter the image tag 268 $imageNode->setAttribute( 'usemap', "#$mapName" ); 269 } 270 271 # Add a surrounding div, remove the default link to the description page 272 $anchor = $imageNode->parentNode; 273 $parent = $anchor->parentNode; 274 $div = $parent->insertBefore( new DOMElement( 'div' ), $anchor ); 275 $div->setAttribute( 'class', 'noresize' ); 276 if ( $defaultLinkAttribs ) { 277 $defaultAnchor = $div->appendChild( new DOMElement( 'a' ) ); 278 foreach ( $defaultLinkAttribs as $name => $value ) { 279 $defaultAnchor->setAttribute( $name, $value ); 280 } 281 $imageParent = $defaultAnchor; 282 } else { 283 $imageParent = $div; 284 } 285 286 # Add the map HTML to the div 287 # We used to add it before the div, but that made tidy unhappy 288 if ( $mapHTML != '' ) { 289 $mapDoc = new DOMDocument(); 290 $mapDoc->loadXML( $mapHTML ); 291 $mapNode = $domDoc->importNode( $mapDoc->documentElement, true ); 292 $div->appendChild( $mapNode ); 293 } 294 295 $imageParent->appendChild( $imageNode->cloneNode( true ) ); 296 $parent->removeChild( $anchor ); 297 298 # Determine whether a "magnify" link is present 299 $xpath = new DOMXPath( $domDoc ); 300 $magnify = $xpath->query( '//div[@class="magnify"]' ); 301 if ( !$magnify->length && $descType != self::NONE ) { 302 # Add image description link 303 if ( $descType == self::TOP_LEFT || $descType == self::BOTTOM_LEFT ) { 304 $marginLeft = 0; 305 } else { 306 $marginLeft = $thumbWidth - 20; 307 } 308 if ( $descType == self::TOP_LEFT || $descType == self::TOP_RIGHT ) { 309 $marginTop = -$thumbHeight; 310 // 1px hack for IE, to stop it poking out the top 311 $marginTop += 1; 312 } else { 313 $marginTop = -20; 314 } 315 $div->setAttribute( 'style', "height: {$thumbHeight}px; width: {$thumbWidth}px; " ); 316 $descWrapper = $div->appendChild( new DOMElement( 'div' ) ); 317 $descWrapper->setAttribute( 'style', 318 "margin-left: {$marginLeft}px; " . 319 "margin-top: {$marginTop}px; " . 320 "text-align: left;" 321 ); 322 323 $descAnchor = $descWrapper->appendChild( new DOMElement( 'a' ) ); 324 $descAnchor->setAttribute( 'href', $imageTitle->getLocalURL() ); 325 $descAnchor->setAttribute( 326 'title', 327 wfMessage( 'imagemap_description' )->inContentLanguage()->text() 328 ); 329 $descImg = $descAnchor->appendChild( new DOMElement( 'img' ) ); 330 $descImg->setAttribute( 331 'alt', 332 wfMessage( 'imagemap_description' )->inContentLanguage()->text() 333 ); 334 $descImg->setAttribute( 'src', "$wgExtensionAssetsPath/ImageMap/desc-20.png" ); 335 $descImg->setAttribute( 'style', 'border: none;' ); 336 } 337 338 # Output the result 339 # We use saveXML() not saveHTML() because then we get XHTML-compliant output. 340 # The disadvantage is that we have to strip out the DTD 341 $output = preg_replace( '/<\?xml[^?]*\?>/', '', $domDoc->saveXML() ); 342 343 # Register links 344 foreach ( $links as $title ) { 345 if ( $title->isExternal() || $title->getNamespace() == NS_SPECIAL ) { 346 // Don't register special or interwiki links... 347 } elseif ( $title->getNamespace() == NS_MEDIA ) { 348 // Regular Media: links are recorded as image usages 349 $parser->mOutput->addImage( $title->getDBkey() ); 350 } else { 351 // Plain ol' link 352 $parser->mOutput->addLink( $title ); 353 } 354 } 355 if ( isset( $extLinks ) ) { 356 foreach ( $extLinks as $title ) { 357 $parser->mOutput->addExternalLink( $title ); 358 } 359 } 360 # Armour output against broken parser 361 $output = str_replace( "\n", '', $output ); 362 return $output; 363 } 364 365 /** 366 * @param $count int 367 * @param $lineNum int|string 368 * @return array|string 369 */ 370 static function tokenizeCoords( $count, $lineNum ) { 371 $coords = array(); 372 for ( $i = 0; $i < $count; $i++ ) { 373 $coord = strtok( " \t" ); 374 if ( $coord === false ) { 375 return self::error( 'imagemap_missing_coord', $lineNum ); 376 } 377 if ( !is_numeric( $coord ) || $coord > 1e9 || $coord < 0 ) { 378 return self::error( 'imagemap_invalid_coord', $lineNum ); 379 } 380 $coords[$i] = $coord; 381 } 382 return $coords; 383 } 384 385 /** 386 * @param $name string 387 * @param $line string|int|bool 388 * @return string 389 */ 390 static function error( $name, $line = false ) { 391 return '<p class="error">' . wfMessage( $name, $line )->text() . '</p>'; 392 } 393 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |