This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * ResourceLoader module for generated and embedded images. |
||
4 | * |
||
5 | * This program is free software; you can redistribute it and/or modify |
||
6 | * it under the terms of the GNU General Public License as published by |
||
7 | * the Free Software Foundation; either version 2 of the License, or |
||
8 | * (at your option) any later version. |
||
9 | * |
||
10 | * This program is distributed in the hope that it will be useful, |
||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||
13 | * GNU General Public License for more details. |
||
14 | * |
||
15 | * You should have received a copy of the GNU General Public License along |
||
16 | * with this program; if not, write to the Free Software Foundation, Inc., |
||
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||
18 | * http://www.gnu.org/copyleft/gpl.html |
||
19 | * |
||
20 | * @file |
||
21 | * @author Trevor Parscal |
||
22 | */ |
||
23 | |||
24 | /** |
||
25 | * ResourceLoader module for generated and embedded images. |
||
26 | * |
||
27 | * @since 1.25 |
||
28 | */ |
||
29 | class ResourceLoaderImageModule extends ResourceLoaderModule { |
||
30 | |||
31 | protected $definition = null; |
||
32 | |||
33 | /** |
||
34 | * Local base path, see __construct() |
||
35 | * @var string |
||
36 | */ |
||
37 | protected $localBasePath = ''; |
||
38 | |||
39 | protected $origin = self::ORIGIN_CORE_SITEWIDE; |
||
40 | |||
41 | protected $images = []; |
||
42 | protected $variants = []; |
||
43 | protected $prefix = null; |
||
44 | protected $selectorWithoutVariant = '.{prefix}-{name}'; |
||
45 | protected $selectorWithVariant = '.{prefix}-{name}-{variant}'; |
||
46 | protected $targets = [ 'desktop', 'mobile' ]; |
||
47 | |||
48 | /** @var string Position on the page to load this module at */ |
||
49 | protected $position = 'bottom'; |
||
50 | |||
51 | /** |
||
52 | * Constructs a new module from an options array. |
||
53 | * |
||
54 | * @param array $options List of options; if not given or empty, an empty module will be |
||
55 | * constructed |
||
56 | * @param string $localBasePath Base path to prepend to all local paths in $options. Defaults |
||
57 | * to $IP |
||
58 | * |
||
59 | * Below is a description for the $options array: |
||
60 | * @par Construction options: |
||
61 | * @code |
||
62 | * [ |
||
63 | * // Base path to prepend to all local paths in $options. Defaults to $IP |
||
64 | * 'localBasePath' => [base path], |
||
65 | * // Path to JSON file that contains any of the settings below |
||
66 | * 'data' => [file path string] |
||
67 | * // CSS class prefix to use in all style rules |
||
68 | * 'prefix' => [CSS class prefix], |
||
69 | * // Alternatively: Format of CSS selector to use in all style rules |
||
70 | * 'selector' => [CSS selector template, variables: {prefix} {name} {variant}], |
||
71 | * // Alternatively: When using variants |
||
72 | * 'selectorWithoutVariant' => [CSS selector template, variables: {prefix} {name}], |
||
73 | * 'selectorWithVariant' => [CSS selector template, variables: {prefix} {name} {variant}], |
||
74 | * // List of variants that may be used for the image files |
||
75 | * 'variants' => [ |
||
76 | * [theme name] => [ |
||
77 | * [variant name] => [ |
||
78 | * 'color' => [color string, e.g. '#ffff00'], |
||
79 | * 'global' => [boolean, if true, this variant is available |
||
80 | * for all images of this type], |
||
81 | * ], |
||
82 | * ... |
||
83 | * ], |
||
84 | * ... |
||
85 | * ], |
||
86 | * // List of image files and their options |
||
87 | * 'images' => [ |
||
88 | * [theme name] => [ |
||
89 | * [icon name] => [ |
||
90 | * 'file' => [file path string or array whose values are file path strings |
||
91 | * and whose keys are 'default', 'ltr', 'rtl', a single |
||
92 | * language code like 'en', or a list of language codes like |
||
93 | * 'en,de,ar'], |
||
94 | * 'variants' => [array of variant name strings, variants |
||
95 | * available for this image], |
||
96 | * ], |
||
97 | * ... |
||
98 | * ], |
||
99 | * ... |
||
100 | * ], |
||
101 | * ] |
||
102 | * @endcode |
||
103 | * @throws InvalidArgumentException |
||
104 | */ |
||
105 | public function __construct( $options = [], $localBasePath = null ) { |
||
106 | $this->localBasePath = self::extractLocalBasePath( $options, $localBasePath ); |
||
107 | |||
108 | $this->definition = $options; |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Parse definition and external JSON data, if referenced. |
||
113 | */ |
||
114 | protected function loadFromDefinition() { |
||
115 | if ( $this->definition === null ) { |
||
116 | return; |
||
117 | } |
||
118 | |||
119 | $options = $this->definition; |
||
120 | $this->definition = null; |
||
121 | |||
122 | if ( isset( $options['data'] ) ) { |
||
123 | $dataPath = $this->localBasePath . '/' . $options['data']; |
||
124 | $data = json_decode( file_get_contents( $dataPath ), true ); |
||
125 | $options = array_merge( $data, $options ); |
||
126 | } |
||
127 | |||
128 | // Accepted combinations: |
||
129 | // * prefix |
||
130 | // * selector |
||
131 | // * selectorWithoutVariant + selectorWithVariant |
||
132 | // * prefix + selector |
||
133 | // * prefix + selectorWithoutVariant + selectorWithVariant |
||
134 | |||
135 | $prefix = isset( $options['prefix'] ) && $options['prefix']; |
||
136 | $selector = isset( $options['selector'] ) && $options['selector']; |
||
137 | $selectorWithoutVariant = isset( $options['selectorWithoutVariant'] ) |
||
138 | && $options['selectorWithoutVariant']; |
||
139 | $selectorWithVariant = isset( $options['selectorWithVariant'] ) |
||
140 | && $options['selectorWithVariant']; |
||
141 | |||
142 | if ( $selectorWithoutVariant && !$selectorWithVariant ) { |
||
143 | throw new InvalidArgumentException( |
||
144 | "Given 'selectorWithoutVariant' but no 'selectorWithVariant'." |
||
145 | ); |
||
146 | } |
||
147 | if ( $selectorWithVariant && !$selectorWithoutVariant ) { |
||
148 | throw new InvalidArgumentException( |
||
149 | "Given 'selectorWithVariant' but no 'selectorWithoutVariant'." |
||
150 | ); |
||
151 | } |
||
152 | if ( $selector && $selectorWithVariant ) { |
||
153 | throw new InvalidArgumentException( |
||
154 | "Incompatible 'selector' and 'selectorWithVariant'+'selectorWithoutVariant' given." |
||
155 | ); |
||
156 | } |
||
157 | if ( !$prefix && !$selector && !$selectorWithVariant ) { |
||
158 | throw new InvalidArgumentException( |
||
159 | "None of 'prefix', 'selector' or 'selectorWithVariant'+'selectorWithoutVariant' given." |
||
160 | ); |
||
161 | } |
||
162 | |||
163 | foreach ( $options as $member => $option ) { |
||
164 | switch ( $member ) { |
||
165 | case 'images': |
||
166 | case 'variants': |
||
167 | if ( !is_array( $option ) ) { |
||
168 | throw new InvalidArgumentException( |
||
169 | "Invalid list error. '$option' given, array expected." |
||
170 | ); |
||
171 | } |
||
172 | if ( !isset( $option['default'] ) ) { |
||
173 | // Backwards compatibility |
||
174 | $option = [ 'default' => $option ]; |
||
175 | } |
||
176 | foreach ( $option as $skin => $data ) { |
||
177 | if ( !is_array( $option ) ) { |
||
178 | throw new InvalidArgumentException( |
||
179 | "Invalid list error. '$option' given, array expected." |
||
180 | ); |
||
181 | } |
||
182 | } |
||
183 | $this->{$member} = $option; |
||
184 | break; |
||
185 | |||
186 | case 'position': |
||
187 | case 'prefix': |
||
188 | case 'selectorWithoutVariant': |
||
189 | case 'selectorWithVariant': |
||
190 | $this->{$member} = (string)$option; |
||
191 | break; |
||
192 | |||
193 | case 'selector': |
||
194 | $this->selectorWithoutVariant = $this->selectorWithVariant = (string)$option; |
||
195 | } |
||
196 | } |
||
197 | } |
||
198 | |||
199 | /** |
||
200 | * Get CSS class prefix used by this module. |
||
201 | * @return string |
||
202 | */ |
||
203 | public function getPrefix() { |
||
204 | $this->loadFromDefinition(); |
||
205 | return $this->prefix; |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Get CSS selector templates used by this module. |
||
210 | * @return string |
||
211 | */ |
||
212 | public function getSelectors() { |
||
213 | $this->loadFromDefinition(); |
||
214 | return [ |
||
215 | 'selectorWithoutVariant' => $this->selectorWithoutVariant, |
||
216 | 'selectorWithVariant' => $this->selectorWithVariant, |
||
217 | ]; |
||
218 | } |
||
219 | |||
220 | /** |
||
221 | * Get a ResourceLoaderImage object for given image. |
||
222 | * @param string $name Image name |
||
223 | * @param ResourceLoaderContext $context |
||
224 | * @return ResourceLoaderImage|null |
||
225 | */ |
||
226 | public function getImage( $name, ResourceLoaderContext $context ) { |
||
227 | $this->loadFromDefinition(); |
||
228 | $images = $this->getImages( $context ); |
||
229 | return isset( $images[$name] ) ? $images[$name] : null; |
||
230 | } |
||
231 | |||
232 | /** |
||
233 | * Get ResourceLoaderImage objects for all images. |
||
234 | * @param ResourceLoaderContext $context |
||
235 | * @return ResourceLoaderImage[] Array keyed by image name |
||
236 | */ |
||
237 | public function getImages( ResourceLoaderContext $context ) { |
||
238 | $skin = $context->getSkin(); |
||
239 | if ( !isset( $this->imageObjects ) ) { |
||
240 | $this->loadFromDefinition(); |
||
241 | $this->imageObjects = []; |
||
0 ignored issues
–
show
|
|||
242 | } |
||
243 | if ( !isset( $this->imageObjects[$skin] ) ) { |
||
244 | $this->imageObjects[$skin] = []; |
||
245 | View Code Duplication | if ( !isset( $this->images[$skin] ) ) { |
|
246 | $this->images[$skin] = isset( $this->images['default'] ) ? |
||
247 | $this->images['default'] : |
||
248 | []; |
||
249 | } |
||
250 | foreach ( $this->images[$skin] as $name => $options ) { |
||
251 | $fileDescriptor = is_string( $options ) ? $options : $options['file']; |
||
252 | |||
253 | $allowedVariants = array_merge( |
||
254 | is_array( $options ) && isset( $options['variants'] ) ? $options['variants'] : [], |
||
255 | $this->getGlobalVariants( $context ) |
||
256 | ); |
||
257 | if ( isset( $this->variants[$skin] ) ) { |
||
258 | $variantConfig = array_intersect_key( |
||
259 | $this->variants[$skin], |
||
260 | array_fill_keys( $allowedVariants, true ) |
||
261 | ); |
||
262 | } else { |
||
263 | $variantConfig = []; |
||
264 | } |
||
265 | |||
266 | $image = new ResourceLoaderImage( |
||
267 | $name, |
||
268 | $this->getName(), |
||
269 | $fileDescriptor, |
||
270 | $this->localBasePath, |
||
271 | $variantConfig |
||
272 | ); |
||
273 | $this->imageObjects[$skin][$image->getName()] = $image; |
||
274 | } |
||
275 | } |
||
276 | |||
277 | return $this->imageObjects[$skin]; |
||
278 | } |
||
279 | |||
280 | /** |
||
281 | * Get list of variants in this module that are 'global', i.e., available |
||
282 | * for every image regardless of image options. |
||
283 | * @param ResourceLoaderContext $context |
||
284 | * @return string[] |
||
285 | */ |
||
286 | public function getGlobalVariants( ResourceLoaderContext $context ) { |
||
287 | $skin = $context->getSkin(); |
||
288 | if ( !isset( $this->globalVariants ) ) { |
||
289 | $this->loadFromDefinition(); |
||
290 | $this->globalVariants = []; |
||
0 ignored issues
–
show
The property
globalVariants does not seem to exist. Did you mean variants ?
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. ![]() |
|||
291 | } |
||
292 | if ( !isset( $this->globalVariants[$skin] ) ) { |
||
0 ignored issues
–
show
The property
globalVariants does not seem to exist. Did you mean variants ?
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. ![]() |
|||
293 | $this->globalVariants[$skin] = []; |
||
0 ignored issues
–
show
The property
globalVariants does not seem to exist. Did you mean variants ?
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. ![]() |
|||
294 | View Code Duplication | if ( !isset( $this->variants[$skin] ) ) { |
|
295 | $this->variants[$skin] = isset( $this->variants['default'] ) ? |
||
296 | $this->variants['default'] : |
||
297 | []; |
||
298 | } |
||
299 | foreach ( $this->variants[$skin] as $name => $config ) { |
||
300 | if ( isset( $config['global'] ) && $config['global'] ) { |
||
301 | $this->globalVariants[$skin][] = $name; |
||
0 ignored issues
–
show
The property
globalVariants does not seem to exist. Did you mean variants ?
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. ![]() |
|||
302 | } |
||
303 | } |
||
304 | } |
||
305 | |||
306 | return $this->globalVariants[$skin]; |
||
0 ignored issues
–
show
The property
globalVariants does not seem to exist. Did you mean variants ?
An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name. If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading. ![]() |
|||
307 | } |
||
308 | |||
309 | /** |
||
310 | * @param ResourceLoaderContext $context |
||
311 | * @return array |
||
312 | */ |
||
313 | public function getStyles( ResourceLoaderContext $context ) { |
||
314 | $this->loadFromDefinition(); |
||
315 | |||
316 | // Build CSS rules |
||
317 | $rules = []; |
||
318 | $script = $context->getResourceLoader()->getLoadScript( $this->getSource() ); |
||
319 | $selectors = $this->getSelectors(); |
||
320 | |||
321 | foreach ( $this->getImages( $context ) as $name => $image ) { |
||
322 | $declarations = $this->getCssDeclarations( |
||
323 | $image->getDataUri( $context, null, 'original' ), |
||
0 ignored issues
–
show
|
|||
324 | $image->getUrl( $context, $script, null, 'rasterized' ) |
||
325 | ); |
||
326 | $declarations = implode( "\n\t", $declarations ); |
||
327 | $selector = strtr( |
||
328 | $selectors['selectorWithoutVariant'], |
||
329 | [ |
||
330 | '{prefix}' => $this->getPrefix(), |
||
331 | '{name}' => $name, |
||
332 | '{variant}' => '', |
||
333 | ] |
||
334 | ); |
||
335 | $rules[] = "$selector {\n\t$declarations\n}"; |
||
336 | |||
337 | foreach ( $image->getVariants() as $variant ) { |
||
338 | $declarations = $this->getCssDeclarations( |
||
339 | $image->getDataUri( $context, $variant, 'original' ), |
||
0 ignored issues
–
show
|
|||
340 | $image->getUrl( $context, $script, $variant, 'rasterized' ) |
||
341 | ); |
||
342 | $declarations = implode( "\n\t", $declarations ); |
||
343 | $selector = strtr( |
||
344 | $selectors['selectorWithVariant'], |
||
345 | [ |
||
346 | '{prefix}' => $this->getPrefix(), |
||
347 | '{name}' => $name, |
||
348 | '{variant}' => $variant, |
||
349 | ] |
||
350 | ); |
||
351 | $rules[] = "$selector {\n\t$declarations\n}"; |
||
352 | } |
||
353 | } |
||
354 | |||
355 | $style = implode( "\n", $rules ); |
||
356 | return [ 'all' => $style ]; |
||
357 | } |
||
358 | |||
359 | /** |
||
360 | * SVG support using a transparent gradient to guarantee cross-browser |
||
361 | * compatibility (browsers able to understand gradient syntax support also SVG). |
||
362 | * http://pauginer.tumblr.com/post/36614680636/invisible-gradient-technique |
||
363 | * |
||
364 | * Keep synchronized with the .background-image-svg LESS mixin in |
||
365 | * /resources/src/mediawiki.less/mediawiki.mixins.less. |
||
366 | * |
||
367 | * @param string $primary Primary URI |
||
368 | * @param string $fallback Fallback URI |
||
369 | * @return string[] CSS declarations to use given URIs as background-image |
||
370 | */ |
||
371 | protected function getCssDeclarations( $primary, $fallback ) { |
||
372 | return [ |
||
373 | "background-image: url($fallback);", |
||
374 | "background-image: linear-gradient(transparent, transparent), url($primary);", |
||
375 | // Do not serve SVG to Opera 12, bad rendering with border-radius or background-size (T87504) |
||
376 | "background-image: -o-linear-gradient(transparent, transparent), url($fallback);", |
||
377 | ]; |
||
378 | } |
||
379 | |||
380 | /** |
||
381 | * @return bool |
||
382 | */ |
||
383 | public function supportsURLLoading() { |
||
384 | return false; |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * Get the definition summary for this module. |
||
389 | * |
||
390 | * @param ResourceLoaderContext $context |
||
391 | * @return array |
||
392 | */ |
||
393 | public function getDefinitionSummary( ResourceLoaderContext $context ) { |
||
394 | $this->loadFromDefinition(); |
||
395 | $summary = parent::getDefinitionSummary( $context ); |
||
396 | |||
397 | $options = []; |
||
398 | foreach ( [ |
||
399 | 'localBasePath', |
||
400 | 'images', |
||
401 | 'variants', |
||
402 | 'prefix', |
||
403 | 'selectorWithoutVariant', |
||
404 | 'selectorWithVariant', |
||
405 | ] as $member ) { |
||
406 | $options[$member] = $this->{$member}; |
||
407 | }; |
||
408 | |||
409 | $summary[] = [ |
||
410 | 'options' => $options, |
||
411 | 'fileHashes' => $this->getFileHashes( $context ), |
||
412 | ]; |
||
413 | return $summary; |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * Helper method for getDefinitionSummary. |
||
418 | */ |
||
419 | protected function getFileHashes( ResourceLoaderContext $context ) { |
||
420 | $this->loadFromDefinition(); |
||
421 | $files = []; |
||
422 | foreach ( $this->getImages( $context ) as $name => $image ) { |
||
423 | $files[] = $image->getPath( $context ); |
||
424 | } |
||
425 | $files = array_values( array_unique( $files ) ); |
||
426 | return array_map( [ __CLASS__, 'safeFileHash' ], $files ); |
||
427 | } |
||
428 | |||
429 | /** |
||
430 | * Extract a local base path from module definition information. |
||
431 | * |
||
432 | * @param array $options Module definition |
||
433 | * @param string $localBasePath Path to use if not provided in module definition. Defaults |
||
434 | * to $IP |
||
435 | * @return string Local base path |
||
436 | */ |
||
437 | public static function extractLocalBasePath( $options, $localBasePath = null ) { |
||
438 | global $IP; |
||
439 | |||
440 | if ( $localBasePath === null ) { |
||
441 | $localBasePath = $IP; |
||
442 | } |
||
443 | |||
444 | if ( array_key_exists( 'localBasePath', $options ) ) { |
||
445 | $localBasePath = (string)$options['localBasePath']; |
||
446 | } |
||
447 | |||
448 | return $localBasePath; |
||
449 | } |
||
450 | |||
451 | /** |
||
452 | * @return string |
||
453 | */ |
||
454 | public function getPosition() { |
||
455 | $this->loadFromDefinition(); |
||
456 | return $this->position; |
||
457 | } |
||
458 | |||
459 | /** |
||
460 | * @return string |
||
461 | */ |
||
462 | public function getType() { |
||
463 | return self::LOAD_STYLES; |
||
464 | } |
||
465 | } |
||
466 |
In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:
Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion: