MediaWiki  1.27.2
ResourceLoaderImage.php
Go to the documentation of this file.
1 <?php
29 
34  protected static $fileTypes = [
35  'svg' => 'image/svg+xml',
36  'png' => 'image/png',
37  'gif' => 'image/gif',
38  'jpg' => 'image/jpg',
39  ];
40 
49  public function __construct( $name, $module, $descriptor, $basePath, $variants ) {
50  $this->name = $name;
51  $this->module = $module;
52  $this->descriptor = $descriptor;
53  $this->basePath = $basePath;
54  $this->variants = $variants;
55 
56  // Expand shorthands:
57  // array( "en,de,fr" => "foo.svg" )
58  // → array( "en" => "foo.svg", "de" => "foo.svg", "fr" => "foo.svg" )
59  if ( is_array( $this->descriptor ) && isset( $this->descriptor['lang'] ) ) {
60  foreach ( array_keys( $this->descriptor['lang'] ) as $langList ) {
61  if ( strpos( $langList, ',' ) !== false ) {
62  $this->descriptor['lang'] += array_fill_keys(
63  explode( ',', $langList ),
64  $this->descriptor['lang'][$langList]
65  );
66  unset( $this->descriptor['lang'][$langList] );
67  }
68  }
69  }
70 
71  // Ensure that all files have common extension.
72  $extensions = [];
73  $descriptor = (array)$descriptor;
74  array_walk_recursive( $descriptor, function ( $path ) use ( &$extensions ) {
75  $extensions[] = pathinfo( $path, PATHINFO_EXTENSION );
76  } );
77  $extensions = array_unique( $extensions );
78  if ( count( $extensions ) !== 1 ) {
79  throw new InvalidArgumentException(
80  "File type for different image files of '$name' not the same"
81  );
82  }
83  $ext = $extensions[0];
84  if ( !isset( self::$fileTypes[$ext] ) ) {
85  throw new InvalidArgumentException(
86  "Invalid file type for image files of '$name' (valid: svg, png, gif, jpg)"
87  );
88  }
89  $this->extension = $ext;
90  }
91 
97  public function getName() {
98  return $this->name;
99  }
100 
106  public function getModule() {
107  return $this->module;
108  }
109 
115  public function getVariants() {
116  return array_keys( $this->variants );
117  }
118 
126  $desc = $this->descriptor;
127  if ( is_string( $desc ) ) {
128  return $this->basePath . '/' . $desc;
129  } elseif ( isset( $desc['lang'][$context->getLanguage()] ) ) {
130  return $this->basePath . '/' . $desc['lang'][$context->getLanguage()];
131  } elseif ( isset( $desc[$context->getDirection()] ) ) {
132  return $this->basePath . '/' . $desc[$context->getDirection()];
133  } else {
134  return $this->basePath . '/' . $desc['default'];
135  }
136  }
137 
144  public function getExtension( $format = 'original' ) {
145  if ( $format === 'rasterized' && $this->extension === 'svg' ) {
146  return 'png';
147  } else {
148  return $this->extension;
149  }
150  }
151 
158  public function getMimeType( $format = 'original' ) {
159  $ext = $this->getExtension( $format );
160  return self::$fileTypes[$ext];
161  }
162 
172  public function getUrl( ResourceLoaderContext $context, $script, $variant, $format ) {
173  $query = [
174  'modules' => $this->getModule(),
175  'image' => $this->getName(),
176  'variant' => $variant,
177  'format' => $format,
178  'lang' => $context->getLanguage(),
179  'version' => $context->getVersion(),
180  ];
181 
182  return wfAppendQuery( $script, $query );
183  }
184 
193  public function getDataUri( ResourceLoaderContext $context, $variant, $format ) {
194  $type = $this->getMimeType( $format );
195  $contents = $this->getImageData( $context, $variant, $format );
196  return CSSMin::encodeStringAsDataURI( $contents, $type );
197  }
198 
215  public function getImageData( ResourceLoaderContext $context, $variant = false, $format = false ) {
216  if ( $variant === false ) {
217  $variant = $context->getVariant();
218  }
219  if ( $format === false ) {
220  $format = $context->getFormat();
221  }
222 
223  $path = $this->getPath( $context );
224  if ( !file_exists( $path ) ) {
225  throw new MWException( "File '$path' does not exist" );
226  }
227 
228  if ( $this->getExtension() !== 'svg' ) {
229  return file_get_contents( $path );
230  }
231 
232  if ( $variant && isset( $this->variants[$variant] ) ) {
233  $data = $this->variantize( $this->variants[$variant], $context );
234  } else {
235  $data = file_get_contents( $path );
236  }
237 
238  if ( $format === 'rasterized' ) {
239  $data = $this->rasterize( $data );
240  if ( !$data ) {
241  wfDebugLog( 'ResourceLoaderImage', __METHOD__ . " failed to rasterize for $path" );
242  }
243  }
244 
245  return $data;
246  }
247 
257  $format = $context->getFormat();
258  $mime = $this->getMimeType( $format );
259  $filename = $this->getName() . '.' . $this->getExtension( $format );
260 
261  header( 'Content-Type: ' . $mime );
262  header( 'Content-Disposition: ' .
263  FileBackend::makeContentDisposition( 'inline', $filename ) );
264  }
265 
273  protected function variantize( $variantConf, ResourceLoaderContext $context ) {
274  $dom = new DomDocument;
275  $dom->loadXML( file_get_contents( $this->getPath( $context ) ) );
276  $root = $dom->documentElement;
277  $wrapper = $dom->createElement( 'g' );
278  while ( $root->firstChild ) {
279  $wrapper->appendChild( $root->firstChild );
280  }
281  $root->appendChild( $wrapper );
282  $wrapper->setAttribute( 'fill', $variantConf['color'] );
283  return $dom->saveXML();
284  }
285 
296  protected function massageSvgPathdata( $svg ) {
297  $dom = new DomDocument;
298  $dom->loadXML( $svg );
299  foreach ( $dom->getElementsByTagName( 'path' ) as $node ) {
300  $pathData = $node->getAttribute( 'd' );
301  // Make sure there is at least one space between numbers, and that leading zero is not omitted.
302  // rsvg has issues with syntax like "M-1-2" and "M.445.483" and especially "M-.445-.483".
303  $pathData = preg_replace( '/(-?)(\d*\.\d+|\d+)/', ' ${1}0$2 ', $pathData );
304  // Strip unnecessary leading zeroes for prettiness, not strictly necessary
305  $pathData = preg_replace( '/([ -])0(\d)/', '$1$2', $pathData );
306  $node->setAttribute( 'd', $pathData );
307  }
308  return $dom->saveXML();
309  }
310 
317  protected function rasterize( $svg ) {
337 
338  $svg = $this->massageSvgPathdata( $svg );
339 
340  // Sometimes this might be 'rsvg-secure'. Long as it's rsvg.
341  if ( strpos( $wgSVGConverter, 'rsvg' ) === 0 ) {
342  $command = 'rsvg-convert';
343  if ( $wgSVGConverterPath ) {
344  $command = wfEscapeShellArg( "$wgSVGConverterPath/" ) . $command;
345  }
346 
347  $process = proc_open(
348  $command,
349  [ 0 => [ 'pipe', 'r' ], 1 => [ 'pipe', 'w' ] ],
350  $pipes
351  );
352 
353  if ( is_resource( $process ) ) {
354  fwrite( $pipes[0], $svg );
355  fclose( $pipes[0] );
356  $png = stream_get_contents( $pipes[1] );
357  fclose( $pipes[1] );
358  proc_close( $process );
359 
360  return $png ?: false;
361  }
362  return false;
363 
364  } else {
365  // Write input to and read output from a temporary file
366  $tempFilenameSvg = tempnam( wfTempDir(), 'ResourceLoaderImage' );
367  $tempFilenamePng = tempnam( wfTempDir(), 'ResourceLoaderImage' );
368 
369  file_put_contents( $tempFilenameSvg, $svg );
370 
371  $metadata = SVGMetadataExtractor::getMetadata( $tempFilenameSvg );
372  if ( !isset( $metadata['width'] ) || !isset( $metadata['height'] ) ) {
373  unlink( $tempFilenameSvg );
374  return false;
375  }
376 
377  $handler = new SvgHandler;
378  $res = $handler->rasterize(
379  $tempFilenameSvg,
380  $tempFilenamePng,
381  $metadata['width'],
382  $metadata['height']
383  );
384  unlink( $tempFilenameSvg );
385 
386  $png = null;
387  if ( $res === true ) {
388  $png = file_get_contents( $tempFilenamePng );
389  unlink( $tempFilenamePng );
390  }
391 
392  return $png ?: false;
393  }
394  }
395 }
static getMetadata($filename)
getExtension($format= 'original')
Get the extension of the image.
getMimeType($format= 'original')
Get the MIME type of the image.
the array() calling protocol came about after MediaWiki 1.4rc1.
null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1418
Class encapsulating an image used in a ResourceLoaderImageModule.
$context
Definition: load.php:44
getImageData(ResourceLoaderContext $context, $variant=false, $format=false)
Get actual image data for this image.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
$command
Definition: cdb.php:65
if($ext== 'php'||$ext== 'php5') $mime
Definition: router.php:65
getUrl(ResourceLoaderContext $context, $script, $variant, $format)
Get the load.php URL that will produce this image.
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
$basePath
Definition: exportSites.php:3
wfDebugLog($logGroup, $text, $dest= 'all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
rasterize($svg)
Convert passed image data, which is assumed to be SVG, to PNG.
wfTempDir()
Tries to get the system directory for temporary files.
$wgSVGConverterPath
If not in the executable PATH, specify the SVG converter path.
wfAppendQuery($url, $query)
Append a query string to an existing URL, which may or may not already have query string parameters a...
getDataUri(ResourceLoaderContext $context, $variant, $format)
Get the data: URI that will produce this image.
$res
Definition: database.txt:21
getVariants()
Get the list of variants this image can be converted to.
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
static array $fileTypes
Map of allowed file extensions to their MIME types.
static array __construct($name, $module, $descriptor, $basePath, $variants)
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
getName()
Get name of this image.
sendResponseHeaders(ResourceLoaderContext $context)
Send response headers (using the header() function) that are necessary to correctly serve the image d...
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 name
Definition: design.txt:12
static encodeStringAsDataURI($contents, $type, $ie8Compat=true)
Encode file contents as a data URI with chosen MIME type.
Definition: CSSMin.php:133
static makeContentDisposition($type, $filename= '')
Build a Content-Disposition header value per RFC 6266.
wfEscapeShellArg()
Windows-compatible version of escapeshellarg() Windows doesn't recognise single-quotes in the shell...
getModule()
Get name of the module this image belongs to.
variantize($variantConf, ResourceLoaderContext $context)
Convert this image, which is assumed to be SVG, to given variant.
$extensions
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 set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:762
getPath(ResourceLoaderContext $context)
Get the path to image file for given context.
massageSvgPathdata($svg)
Massage the SVG image data for converters which don't understand some path data syntax.
$wgSVGConverter
Pick a converter defined in $wgSVGConverters.
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 one of or reset 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:2338
Handler for SVG images.
Definition: SVG.php:29
Object passed around to modules which contains information about the state of a specific loader reque...
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310