MediaWiki REL1_33
ImageMap.php
Go to the documentation of this file.
1<?php
21class ImageMap {
22 public static $id = 0;
23
24 const TOP_RIGHT = 0;
25 const BOTTOM_RIGHT = 1;
26 const BOTTOM_LEFT = 2;
27 const TOP_LEFT = 3;
28 const NONE = 4;
29
33 public static function onParserFirstCallInit( Parser &$parser ) {
34 $parser->setHook( 'imagemap', [ 'ImageMap', 'render' ] );
35 }
36
43 public static function render( $input, $params, $parser ) {
45 $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
46
47 $lines = explode( "\n", $input );
48
49 $first = true;
50 $scale = 1;
51 $imageNode = null;
52 $domDoc = null;
53 $thumbWidth = 0;
54 $thumbHeight = 0;
55 $imageTitle = null;
56 $lineNum = 0;
57 $mapHTML = '';
58 $links = [];
59
60 // Define canonical desc types to allow i18n of 'imagemap_desc_types'
61 $descTypesCanonical = 'top-right, bottom-right, bottom-left, top-left, none';
62 $descType = self::BOTTOM_RIGHT;
63 $defaultLinkAttribs = false;
64 $realmap = true;
65 $extLinks = [];
66 foreach ( $lines as $line ) {
67 ++$lineNum;
68 $externLink = false;
69
70 $line = trim( $line );
71 if ( $line == '' || $line[0] == '#' ) {
72 continue;
73 }
74
75 if ( $first ) {
76 $first = false;
77
78 // The first line should have an image specification on it
79 // Extract it and render the HTML
80 $bits = explode( '|', $line, 2 );
81 if ( count( $bits ) == 1 ) {
82 $image = $bits[0];
83 $options = '';
84 } else {
85 list( $image, $options ) = $bits;
86 }
87 $imageTitle = Title::newFromText( $image );
88 if ( !$imageTitle || !$imageTitle->inNamespace( NS_FILE ) ) {
89 return self::error( 'imagemap_no_image' );
90 }
91 if ( wfIsBadImage( $imageTitle->getDBkey(), $parser->mTitle ) ) {
92 return self::error( 'imagemap_bad_image' );
93 }
94 // Parse the options so we can use links and the like in the caption
95 $parsedOptions = $parser->recursiveTagParse( $options );
96 $imageHTML = $parser->makeImage( $imageTitle, $parsedOptions );
97 $parser->replaceLinkHolders( $imageHTML );
98 $imageHTML = $parser->mStripState->unstripBoth( $imageHTML );
99 $imageHTML = Sanitizer::normalizeCharReferences( $imageHTML );
100
101 $domDoc = new DOMDocument();
103 $ok = $domDoc->loadXML( $imageHTML );
105 if ( !$ok ) {
106 return self::error( 'imagemap_invalid_image' );
107 }
108 $xpath = new DOMXPath( $domDoc );
109 $imgs = $xpath->query( '//img' );
110 if ( !$imgs->length ) {
111 return self::error( 'imagemap_invalid_image' );
112 }
113 $imageNode = $imgs->item( 0 );
114 $thumbWidth = $imageNode->getAttribute( 'width' );
115 $thumbHeight = $imageNode->getAttribute( 'height' );
116
117 $imageObj = wfFindFile( $imageTitle );
118 if ( !$imageObj || !$imageObj->exists() ) {
119 return self::error( 'imagemap_invalid_image' );
120 }
121 // Add the linear dimensions to avoid inaccuracy in the scale
122 // factor when one is much larger than the other
123 // (sx+sy)/(x+y) = s
124 $denominator = $imageObj->getWidth() + $imageObj->getHeight();
125 $numerator = $thumbWidth + $thumbHeight;
126 if ( $denominator <= 0 || $numerator <= 0 ) {
127 return self::error( 'imagemap_invalid_image' );
128 }
129 $scale = $numerator / $denominator;
130 continue;
131 }
132
133 // Handle desc spec
134 $cmd = strtok( $line, " \t" );
135 if ( $cmd == 'desc' ) {
136 $typesText = wfMessage( 'imagemap_desc_types' )->inContentLanguage()->text();
137 if ( $descTypesCanonical != $typesText ) {
138 // i18n desc types exists
139 $typesText = $descTypesCanonical . ', ' . $typesText;
140 }
141 $types = array_map( 'trim', explode( ',', $typesText ) );
142 $type = trim( strtok( '' ) );
143 $descType = array_search( $type, $types );
144 if ( $descType > 4 ) {
145 // A localized descType is used. Subtract 5 to reach the canonical desc type.
146 $descType = $descType - 5;
147 }
148 // <0? In theory never, but paranoia...
149 if ( $descType === false || $descType < 0 ) {
150 return self::error( 'imagemap_invalid_desc', $typesText );
151 }
152 continue;
153 }
154
155 $title = false;
156 // Find the link
157 $link = trim( strstr( $line, '[' ) );
158 $m = [];
159 if ( preg_match( '/^ \[\[ ([^|]*+) \| ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) {
160 $title = Title::newFromText( $m[1] );
161 $alt = trim( $m[2] );
162 } elseif ( preg_match( '/^ \[\[ ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) {
163 $title = Title::newFromText( $m[1] );
164 if ( is_null( $title ) ) {
165 return self::error( 'imagemap_invalid_title', $lineNum );
166 }
167 $alt = $title->getFullText();
168 } elseif ( in_array( substr( $link, 1, strpos( $link, '//' ) + 1 ), $wgUrlProtocols )
169 || in_array( substr( $link, 1, strpos( $link, ':' ) ), $wgUrlProtocols )
170 ) {
171 if ( preg_match( '/^ \[ ([^\s]*+) \s ([^\]]*+) \] \w* $ /x', $link, $m ) ) {
172 $title = $m[1];
173 $alt = trim( $m[2] );
174 $externLink = true;
175 } elseif ( preg_match( '/^ \[ ([^\]]*+) \] \w* $ /x', $link, $m ) ) {
176 $title = $alt = trim( $m[1] );
177 $externLink = true;
178 }
179 } else {
180 return self::error( 'imagemap_no_link', $lineNum );
181 }
182 if ( !$title ) {
183 return self::error( 'imagemap_invalid_title', $lineNum );
184 }
185
186 $shapeSpec = substr( $line, 0, -strlen( $link ) );
187
188 // Tokenize shape spec
189 $shape = strtok( $shapeSpec, " \t" );
190 switch ( $shape ) {
191 case 'default':
192 $coords = [];
193 break;
194 case 'rect':
195 $coords = self::tokenizeCoords( 4, $lineNum );
196 if ( !is_array( $coords ) ) {
197 return $coords;
198 }
199 break;
200 case 'circle':
201 $coords = self::tokenizeCoords( 3, $lineNum );
202 if ( !is_array( $coords ) ) {
203 return $coords;
204 }
205 break;
206 case 'poly':
207 $coords = [];
208 $coord = strtok( " \t" );
209 while ( $coord !== false ) {
210 if ( !is_numeric( $coord ) || $coord > 1e9 || $coord < 0 ) {
211 return self::error( 'imagemap_invalid_coord', $lineNum );
212 }
213 $coords[] = $coord;
214 $coord = strtok( " \t" );
215 }
216 if ( !count( $coords ) ) {
217 return self::error( 'imagemap_missing_coord', $lineNum );
218 }
219 if ( count( $coords ) % 2 !== 0 ) {
220 return self::error( 'imagemap_poly_odd', $lineNum );
221 }
222 break;
223 default:
224 return self::error( 'imagemap_unrecognised_shape', $lineNum );
225 }
226
227 // Scale the coords using the size of the source image
228 foreach ( $coords as $i => $c ) {
229 $coords[$i] = (int)round( $c * $scale );
230 }
231
232 // Construct the area tag
233 $attribs = [];
234 if ( $externLink ) {
235 $attribs['href'] = $title;
236 $attribs['class'] = 'plainlinks';
237 if ( $wgNoFollowLinks ) {
238 $attribs['rel'] = 'nofollow';
239 }
240 } elseif ( $title->getFragment() != '' && $title->getPrefixedDBkey() == '' ) {
241 // XXX: kluge to handle [[#Fragment]] links, should really fix getLocalURL()
242 // in Title.php to return an empty string in this case
243 $attribs['href'] = $title->getFragmentForURL();
244 } else {
245 $attribs['href'] = $title->getLocalURL() . $title->getFragmentForURL();
246 }
247 if ( $shape != 'default' ) {
248 $attribs['shape'] = $shape;
249 }
250 if ( $coords ) {
251 $attribs['coords'] = implode( ',', $coords );
252 }
253 if ( $alt != '' ) {
254 if ( $shape != 'default' ) {
255 $attribs['alt'] = $alt;
256 }
257 $attribs['title'] = $alt;
258 }
259 if ( $shape == 'default' ) {
260 $defaultLinkAttribs = $attribs;
261 } else {
262 $mapHTML .= Xml::element( 'area', $attribs ) . "\n";
263 }
264 if ( $externLink ) {
265 $extLinks[] = $title;
266 } else {
267 $links[] = $title;
268 }
269 }
270
271 if ( $first || !$imageNode ) {
272 return self::error( 'imagemap_no_image' );
273 }
274
275 if ( $mapHTML == '' ) {
276 // no areas defined, default only. It's not a real imagemap, so we do not need some tags
277 $realmap = false;
278 }
279
280 if ( $realmap ) {
281 // Construct the map
282 // Add random number to avoid breaking cached HTML fragments that are
283 // later joined together on the one page (bug 16471)
284 $mapName = "ImageMap_" . ++self::$id . '_' . mt_rand( 0, 0x7fffffff );
285 $mapHTML = "<map name=\"$mapName\">\n$mapHTML</map>\n";
286
287 // Alter the image tag
288 $imageNode->setAttribute( 'usemap', "#$mapName" );
289 }
290
291 // Add a surrounding div, remove the default link to the description page
292 $anchor = $imageNode->parentNode;
293 $parent = $anchor->parentNode;
294 $div = $parent->insertBefore( new DOMElement( 'div' ), $anchor );
295 $div->setAttribute( 'class', 'noresize' );
296 if ( $defaultLinkAttribs ) {
297 $defaultAnchor = $div->appendChild( new DOMElement( 'a' ) );
298 foreach ( $defaultLinkAttribs as $name => $value ) {
299 $defaultAnchor->setAttribute( $name, $value );
300 }
301 $imageParent = $defaultAnchor;
302 } else {
303 $imageParent = $div;
304 }
305
306 // Add the map HTML to the div
307 // We used to add it before the div, but that made tidy unhappy
308 if ( $mapHTML != '' ) {
309 $mapDoc = new DOMDocument();
310 $mapDoc->loadXML( $mapHTML );
311 $mapNode = $domDoc->importNode( $mapDoc->documentElement, true );
312 $div->appendChild( $mapNode );
313 }
314
315 $imageParent->appendChild( $imageNode->cloneNode( true ) );
316 $parent->removeChild( $anchor );
317
318 // Determine whether a "magnify" link is present
319 $xpath = new DOMXPath( $domDoc );
320 $magnify = $xpath->query( '//div[@class="magnify"]' );
321 if ( !$magnify->length && $descType != self::NONE ) {
322 // Add image description link
323 if ( $descType == self::TOP_LEFT || $descType == self::BOTTOM_LEFT ) {
324 $marginLeft = 0;
325 } else {
326 $marginLeft = $thumbWidth - 20;
327 }
328 if ( $descType == self::TOP_LEFT || $descType == self::TOP_RIGHT ) {
329 $marginTop = -$thumbHeight;
330 // 1px hack for IE, to stop it poking out the top
331 $marginTop += 1;
332 } else {
333 $marginTop = -20;
334 }
335 $div->setAttribute( 'style', "height: {$thumbHeight}px; width: {$thumbWidth}px; " );
336 $descWrapper = $div->appendChild( new DOMElement( 'div' ) );
337 $descWrapper->setAttribute( 'style',
338 "margin-left: {$marginLeft}px; " .
339 "margin-top: {$marginTop}px; " .
340 "text-align: left;"
341 );
342
343 $descAnchor = $descWrapper->appendChild( new DOMElement( 'a' ) );
344 $descAnchor->setAttribute( 'href', $imageTitle->getLocalURL() );
345 $descAnchor->setAttribute(
346 'title',
347 wfMessage( 'imagemap_description' )->inContentLanguage()->text()
348 );
349 $descImg = $descAnchor->appendChild( new DOMElement( 'img' ) );
350 $descImg->setAttribute(
351 'alt',
352 wfMessage( 'imagemap_description' )->inContentLanguage()->text()
353 );
354 $url = $config->get( 'ExtensionAssetsPath' ) . '/ImageMap/resources/desc-20.png';
355 $descImg->setAttribute(
356 'src',
357 OutputPage::transformResourcePath( $config, $url )
358 );
359 $descImg->setAttribute( 'style', 'border: none;' );
360 }
361
362 // Output the result
363 // We use saveXML() not saveHTML() because then we get XHTML-compliant output.
364 // The disadvantage is that we have to strip out the DTD
365 $output = preg_replace( '/<\?xml[^?]*\?>/', '', $domDoc->saveXML( null, LIBXML_NOEMPTYTAG ) );
366
367 // Register links
368 foreach ( $links as $title ) {
369 if ( $title->isExternal() || $title->getNamespace() == NS_SPECIAL ) {
370 // Don't register special or interwiki links...
371 } elseif ( $title->getNamespace() == NS_MEDIA ) {
372 // Regular Media: links are recorded as image usages
373 $parser->mOutput->addImage( $title->getDBkey() );
374 } else {
375 // Plain ol' link
376 $parser->mOutput->addLink( $title );
377 }
378 }
379 foreach ( $extLinks as $title ) {
380 $parser->mOutput->addExternalLink( $title );
381 }
382 // Armour output against broken parser
383 $output = str_replace( "\n", '', $output );
384 return $output;
385 }
386
392 private static function tokenizeCoords( $count, $lineNum ) {
393 $coords = [];
394 for ( $i = 0; $i < $count; $i++ ) {
395 $coord = strtok( " \t" );
396 if ( $coord === false ) {
397 return self::error( 'imagemap_missing_coord', $lineNum );
398 }
399 if ( !is_numeric( $coord ) || $coord > 1e9 || $coord < 0 ) {
400 return self::error( 'imagemap_invalid_coord', $lineNum );
401 }
402 $coords[$i] = $coord;
403 }
404 return $coords;
405 }
406
412 private static function error( $name, $line = false ) {
413 return '<p class="error">' . wfMessage( $name, $line )->parse() . '</p>';
414 }
415}
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
and that you know you can do these things To protect your we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights These restrictions translate to certain responsibilities for you if you distribute copies of the or if you modify it For if you distribute copies of such a whether gratis or for a you must give the recipients all the rights that you have You must make sure that receive or can get the source code And you must show them these terms so they know their rights We protect your rights with two and(2) offer you this license which gives you legal permission to copy
$wgNoFollowLinks
If true, external URL links in wiki text will be given the rel="nofollow" attribute as a hint to sear...
$wgUrlProtocols
URL schemes that should be recognized as valid by wfParseUrl().
wfFindFile( $title, $options=[])
Find a file.
wfIsBadImage( $name, $contextTitle=false, $blacklist=null)
Determine if an image exists on the 'bad image list'.
$line
Definition cdb.php:59
const BOTTOM_RIGHT
Definition ImageMap.php:25
const TOP_LEFT
Definition ImageMap.php:27
static error( $name, $line=false)
Definition ImageMap.php:412
const BOTTOM_LEFT
Definition ImageMap.php:26
static $id
Definition ImageMap.php:22
const NONE
Definition ImageMap.php:28
static onParserFirstCallInit(Parser &$parser)
Definition ImageMap.php:33
static render( $input, $params, $parser)
Definition ImageMap.php:43
const TOP_RIGHT
Definition ImageMap.php:24
static tokenizeCoords( $count, $lineNum)
Definition ImageMap.php:392
PHP Parser - Processes wiki markup (which uses a more user-friendly syntax, such as "[[link]]" for ma...
Definition Parser.php:69
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition deferred.txt:11
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check $image
Definition hooks.txt:886
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition hooks.txt:1834
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults error
Definition hooks.txt:2644
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition hooks.txt:1999
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:955
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition hooks.txt:3069
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses after processing & $attribs
Definition hooks.txt:2012
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition hooks.txt:2272
const NS_FILE
Definition Defines.php:79
const NS_SPECIAL
Definition Defines.php:62
const NS_MEDIA
Definition Defines.php:61
$parent
if(is_array($mode)) switch( $mode) $input
$lines
Definition router.php:61
$params