MediaWiki  1.29.2
ImageMap_body.php
Go to the documentation of this file.
1 <?php
21 class 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', array( 'ImageMap', 'render' ) );
35  }
36 
43  public static function render( $input, $params, $parser ) {
44  global $wgUrlProtocols, $wgNoFollowLinks;
45  $config = ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
46 
47  $lines = explode( "\n", $input );
48 
49  $first = true;
50  $lineNum = 0;
51  $mapHTML = '';
52  $links = array();
53 
54  // Define canonical desc types to allow i18n of 'imagemap_desc_types'
55  $descTypesCanonical = 'top-right, bottom-right, bottom-left, top-left, none';
56  $descType = self::BOTTOM_RIGHT;
57  $defaultLinkAttribs = false;
58  $realmap = true;
59  $extLinks = array();
60  foreach ( $lines as $line ) {
61  ++$lineNum;
62  $externLink = false;
63 
64  $line = trim( $line );
65  if ( $line == '' || $line[0] == '#' ) {
66  continue;
67  }
68 
69  if ( $first ) {
70  $first = false;
71 
72  // The first line should have an image specification on it
73  // Extract it and render the HTML
74  $bits = explode( '|', $line, 2 );
75  if ( count( $bits ) == 1 ) {
76  $image = $bits[0];
77  $options = '';
78  } else {
79  list( $image, $options ) = $bits;
80  }
81  $imageTitle = Title::newFromText( $image );
82  if ( !$imageTitle || !$imageTitle->inNamespace( NS_FILE ) ) {
83  return self::error( 'imagemap_no_image' );
84  }
85  if ( wfIsBadImage( $imageTitle->getDBkey(), $parser->mTitle ) ) {
86  return self::error( 'imagemap_bad_image' );
87  }
88  // Parse the options so we can use links and the like in the caption
89  $parsedOptions = $parser->recursiveTagParse( $options );
90  $imageHTML = $parser->makeImage( $imageTitle, $parsedOptions );
91  $parser->replaceLinkHolders( $imageHTML );
92  $imageHTML = $parser->mStripState->unstripBoth( $imageHTML );
93  $imageHTML = Sanitizer::normalizeCharReferences( $imageHTML );
94 
95  $domDoc = new DOMDocument();
97  $ok = $domDoc->loadXML( $imageHTML );
99  if ( !$ok ) {
100  return self::error( 'imagemap_invalid_image' );
101  }
102  $xpath = new DOMXPath( $domDoc );
103  $imgs = $xpath->query( '//img' );
104  if ( !$imgs->length ) {
105  return self::error( 'imagemap_invalid_image' );
106  }
107  $imageNode = $imgs->item( 0 );
108  $thumbWidth = $imageNode->getAttribute( 'width' );
109  $thumbHeight = $imageNode->getAttribute( 'height' );
110 
111  $imageObj = wfFindFile( $imageTitle );
112  if ( !$imageObj || !$imageObj->exists() ) {
113  return self::error( 'imagemap_invalid_image' );
114  }
115  // Add the linear dimensions to avoid inaccuracy in the scale
116  // factor when one is much larger than the other
117  // (sx+sy)/(x+y) = s
118  $denominator = $imageObj->getWidth() + $imageObj->getHeight();
119  $numerator = $thumbWidth + $thumbHeight;
120  if ( $denominator <= 0 || $numerator <= 0 ) {
121  return self::error( 'imagemap_invalid_image' );
122  }
123  $scale = $numerator / $denominator;
124  continue;
125  }
126 
127  // Handle desc spec
128  $cmd = strtok( $line, " \t" );
129  if ( $cmd == 'desc' ) {
130  $typesText = wfMessage( 'imagemap_desc_types' )->inContentLanguage()->text();
131  if ( $descTypesCanonical != $typesText ) {
132  // i18n desc types exists
133  $typesText = $descTypesCanonical . ', ' . $typesText;
134  }
135  $types = array_map( 'trim', explode( ',', $typesText ) );
136  $type = trim( strtok( '' ) );
137  $descType = array_search( $type, $types );
138  if ( $descType > 4 ) {
139  // A localized descType is used. Subtract 5 to reach the canonical desc type.
140  $descType = $descType - 5;
141  }
142  // <0? In theory never, but paranoia...
143  if ( $descType === false || $descType < 0 ) {
144  return self::error( 'imagemap_invalid_desc', $typesText );
145  }
146  continue;
147  }
148 
149  $title = false;
150  // Find the link
151  $link = trim( strstr( $line, '[' ) );
152  $m = array();
153  if ( preg_match( '/^ \[\[ ([^|]*+) \| ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) {
154  $title = Title::newFromText( $m[1] );
155  $alt = trim( $m[2] );
156  } elseif ( preg_match( '/^ \[\[ ([^\]]*+) \]\] \w* $ /x', $link, $m ) ) {
157  $title = Title::newFromText( $m[1] );
158  if ( is_null( $title ) ) {
159  return self::error( 'imagemap_invalid_title', $lineNum );
160  }
161  $alt = $title->getFullText();
162  } elseif ( in_array( substr( $link, 1, strpos( $link, '//' ) + 1 ), $wgUrlProtocols ) || in_array( substr( $link, 1, strpos( $link, ':' ) ), $wgUrlProtocols ) ) {
163  if ( preg_match( '/^ \[ ([^\s]*+) \s ([^\]]*+) \] \w* $ /x', $link, $m ) ) {
164  $title = $m[1];
165  $alt = trim( $m[2] );
166  $externLink = true;
167  } elseif ( preg_match( '/^ \[ ([^\]]*+) \] \w* $ /x', $link, $m ) ) {
168  $title = $alt = trim( $m[1] );
169  $externLink = true;
170  }
171  } else {
172  return self::error( 'imagemap_no_link', $lineNum );
173  }
174  if ( !$title ) {
175  return self::error( 'imagemap_invalid_title', $lineNum );
176  }
177 
178  $shapeSpec = substr( $line, 0, -strlen( $link ) );
179 
180  // Tokenize shape spec
181  $shape = strtok( $shapeSpec, " \t" );
182  switch ( $shape ) {
183  case 'default':
184  $coords = array();
185  break;
186  case 'rect':
187  $coords = self::tokenizeCoords( 4, $lineNum );
188  if ( !is_array( $coords ) ) {
189  return $coords;
190  }
191  break;
192  case 'circle':
193  $coords = self::tokenizeCoords( 3, $lineNum );
194  if ( !is_array( $coords ) ) {
195  return $coords;
196  }
197  break;
198  case 'poly':
199  $coords = array();
200  $coord = strtok( " \t" );
201  while ( $coord !== false ) {
202  $coords[] = $coord;
203  $coord = strtok( " \t" );
204  }
205  if ( !count( $coords ) ) {
206  return self::error( 'imagemap_missing_coord', $lineNum );
207  }
208  if ( count( $coords ) % 2 !== 0 ) {
209  return self::error( 'imagemap_poly_odd', $lineNum );
210  }
211  break;
212  default:
213  return self::error( 'imagemap_unrecognised_shape', $lineNum );
214  }
215 
216  // Scale the coords using the size of the source image
217  foreach ( $coords as $i => $c ) {
218  $coords[$i] = intval( round( $c * $scale ) );
219  }
220 
221  // Construct the area tag
222  $attribs = array();
223  if ( $externLink ) {
224  $attribs['href'] = $title;
225  $attribs['class'] = 'plainlinks';
226  if ( $wgNoFollowLinks ) {
227  $attribs['rel'] = 'nofollow';
228  }
229  } elseif ( $title->getFragment() != '' && $title->getPrefixedDBkey() == '' ) {
230  // XXX: kluge to handle [[#Fragment]] links, should really fix getLocalURL()
231  // in Title.php to return an empty string in this case
232  $attribs['href'] = $title->getFragmentForURL();
233  } else {
234  $attribs['href'] = $title->getLocalURL() . $title->getFragmentForURL();
235  }
236  if ( $shape != 'default' ) {
237  $attribs['shape'] = $shape;
238  }
239  if ( $coords ) {
240  $attribs['coords'] = implode( ',', $coords );
241  }
242  if ( $alt != '' ) {
243  if ( $shape != 'default' ) {
244  $attribs['alt'] = $alt;
245  }
246  $attribs['title'] = $alt;
247  }
248  if ( $shape == 'default' ) {
249  $defaultLinkAttribs = $attribs;
250  } else {
251  $mapHTML .= Xml::element( 'area', $attribs ) . "\n";
252  }
253  if ( $externLink ) {
254  $extLinks[] = $title;
255  } else {
256  $links[] = $title;
257  }
258  }
259 
260  if ( $first ) {
261  return self::error( 'imagemap_no_image' );
262  }
263 
264  if ( $mapHTML == '' ) {
265  // no areas defined, default only. It's not a real imagemap, so we do not need some tags
266  $realmap = false;
267  }
268 
269  if ( $realmap ) {
270  // Construct the map
271  // Add random number to avoid breaking cached HTML fragments that are
272  // later joined together on the one page (bug 16471)
273  $mapName = "ImageMap_" . ++self::$id . '_' . mt_rand( 0, 0x7fffffff );
274  $mapHTML = "<map name=\"$mapName\">\n$mapHTML</map>\n";
275 
276  // Alter the image tag
277  $imageNode->setAttribute( 'usemap', "#$mapName" );
278  }
279 
280  // Add a surrounding div, remove the default link to the description page
281  $anchor = $imageNode->parentNode;
282  $parent = $anchor->parentNode;
283  $div = $parent->insertBefore( new DOMElement( 'div' ), $anchor );
284  $div->setAttribute( 'class', 'noresize' );
285  if ( $defaultLinkAttribs ) {
286  $defaultAnchor = $div->appendChild( new DOMElement( 'a' ) );
287  foreach ( $defaultLinkAttribs as $name => $value ) {
288  $defaultAnchor->setAttribute( $name, $value );
289  }
290  $imageParent = $defaultAnchor;
291  } else {
292  $imageParent = $div;
293  }
294 
295  // Add the map HTML to the div
296  // We used to add it before the div, but that made tidy unhappy
297  if ( $mapHTML != '' ) {
298  $mapDoc = new DOMDocument();
299  $mapDoc->loadXML( $mapHTML );
300  $mapNode = $domDoc->importNode( $mapDoc->documentElement, true );
301  $div->appendChild( $mapNode );
302  }
303 
304  $imageParent->appendChild( $imageNode->cloneNode( true ) );
305  $parent->removeChild( $anchor );
306 
307  // Determine whether a "magnify" link is present
308  $xpath = new DOMXPath( $domDoc );
309  $magnify = $xpath->query( '//div[@class="magnify"]' );
310  if ( !$magnify->length && $descType != self::NONE ) {
311  // Add image description link
312  if ( $descType == self::TOP_LEFT || $descType == self::BOTTOM_LEFT ) {
313  $marginLeft = 0;
314  } else {
315  $marginLeft = $thumbWidth - 20;
316  }
317  if ( $descType == self::TOP_LEFT || $descType == self::TOP_RIGHT ) {
318  $marginTop = -$thumbHeight;
319  // 1px hack for IE, to stop it poking out the top
320  $marginTop += 1;
321  } else {
322  $marginTop = -20;
323  }
324  $div->setAttribute( 'style', "height: {$thumbHeight}px; width: {$thumbWidth}px; " );
325  $descWrapper = $div->appendChild( new DOMElement( 'div' ) );
326  $descWrapper->setAttribute( 'style',
327  "margin-left: {$marginLeft}px; " .
328  "margin-top: {$marginTop}px; " .
329  "text-align: left;"
330  );
331 
332  $descAnchor = $descWrapper->appendChild( new DOMElement( 'a' ) );
333  $descAnchor->setAttribute( 'href', $imageTitle->getLocalURL() );
334  $descAnchor->setAttribute(
335  'title',
336  wfMessage( 'imagemap_description' )->inContentLanguage()->text()
337  );
338  $descImg = $descAnchor->appendChild( new DOMElement( 'img' ) );
339  $descImg->setAttribute(
340  'alt',
341  wfMessage( 'imagemap_description' )->inContentLanguage()->text()
342  );
343  $url = $config->get( 'ExtensionAssetsPath' ) . '/ImageMap/desc-20.png';
344  $descImg->setAttribute(
345  'src',
346  OutputPage::transformResourcePath( $config, $url )
347  );
348  $descImg->setAttribute( 'style', 'border: none;' );
349  }
350 
351  // Output the result
352  // We use saveXML() not saveHTML() because then we get XHTML-compliant output.
353  // The disadvantage is that we have to strip out the DTD
354  $output = preg_replace( '/<\?xml[^?]*\?>/', '', $domDoc->saveXML( null, LIBXML_NOEMPTYTAG ) );
355 
356  // Register links
357  foreach ( $links as $title ) {
358  if ( $title->isExternal() || $title->getNamespace() == NS_SPECIAL ) {
359  // Don't register special or interwiki links...
360  } elseif ( $title->getNamespace() == NS_MEDIA ) {
361  // Regular Media: links are recorded as image usages
362  $parser->mOutput->addImage( $title->getDBkey() );
363  } else {
364  // Plain ol' link
365  $parser->mOutput->addLink( $title );
366  }
367  }
368  foreach ( $extLinks as $title ) {
369  $parser->mOutput->addExternalLink( $title );
370  }
371  // Armour output against broken parser
372  $output = str_replace( "\n", '', $output );
373  return $output;
374  }
375 
381  static function tokenizeCoords( $count, $lineNum ) {
382  $coords = array();
383  for ( $i = 0; $i < $count; $i++ ) {
384  $coord = strtok( " \t" );
385  if ( $coord === false ) {
386  return self::error( 'imagemap_missing_coord', $lineNum );
387  }
388  if ( !is_numeric( $coord ) || $coord > 1e9 || $coord < 0 ) {
389  return self::error( 'imagemap_invalid_coord', $lineNum );
390  }
391  $coords[$i] = $coord;
392  }
393  return $coords;
394  }
395 
401  static function error( $name, $line = false ) {
402  return '<p class="error">' . wfMessage( $name, $line )->text() . '</p>';
403  }
404 }
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:265
ImageMap\TOP_RIGHT
const TOP_RIGHT
Definition: ImageMap_body.php:24
captcha-old.count
count
Definition: captcha-old.py:225
text
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
wfSuppressWarnings
wfSuppressWarnings( $end=false)
Reference-counted warning suppression.
Definition: GlobalFunctions.php:1974
NS_FILE
const NS_FILE
Definition: Defines.php:68
$params
$params
Definition: styleTest.css.php:40
ImageMap
Definition: ImageMap_body.php:21
ImageMap\$id
static $id
Definition: ImageMap_body.php:22
$name
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:304
$type
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition: hooks.txt:2536
php
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
NS_SPECIAL
const NS_SPECIAL
Definition: Defines.php:51
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:934
wfRestoreWarnings
wfRestoreWarnings()
Definition: GlobalFunctions.php:1982
$input
if(is_array( $mode)) switch( $mode) $input
Definition: postprocess-phan.php:141
$attribs
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:1956
ConfigFactory\getDefaultInstance
static getDefaultInstance()
Definition: ConfigFactory.php:51
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
$lines
$lines
Definition: router.php:67
$parser
do that in ParserLimitReportFormat instead $parser
Definition: hooks.txt:2536
$output
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object & $output
Definition: hooks.txt:1049
global
when a variable name is used in a it is silently declared as a new masking the global
Definition: design.txt:93
ImageMap\tokenizeCoords
static tokenizeCoords( $count, $lineNum)
Definition: ImageMap_body.php:381
list
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
$image
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:783
ImageMap\error
static error( $name, $line=false)
Definition: ImageMap_body.php:401
ImageMap\BOTTOM_RIGHT
const BOTTOM_RIGHT
Definition: ImageMap_body.php:25
ImageMap\TOP_LEFT
const TOP_LEFT
Definition: ImageMap_body.php:27
$line
$line
Definition: cdb.php:58
wfIsBadImage
wfIsBadImage( $name, $contextTitle=false, $blacklist=null)
Determine if an image exists on the 'bad image list'.
Definition: GlobalFunctions.php:3496
$value
$value
Definition: styleTest.css.php:45
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:50
ImageMap\NONE
const NONE
Definition: ImageMap_body.php:28
wfFindFile
wfFindFile( $title, $options=[])
Find a file.
Definition: GlobalFunctions.php:3101
as
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
$link
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2929
wfMessage
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 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
ImageMap\onParserFirstCallInit
static onParserFirstCallInit(Parser &$parser)
Definition: ImageMap_body.php:33
ImageMap\render
static render( $input, $params, $parser)
Definition: ImageMap_body.php:43
ImageMap\BOTTOM_LEFT
const BOTTOM_LEFT
Definition: ImageMap_body.php:26
$options
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1049
array
the array() calling protocol came about after MediaWiki 1.4rc1.
OutputPage\transformResourcePath
static transformResourcePath(Config $config, $path)
Transform path to web-accessible static resource.
Definition: OutputPage.php:3759