Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
14.97% |
28 / 187 |
|
1.79% |
1 / 56 |
CRAP | |
0.00% |
0 / 1 |
MediaHandler | |
14.97% |
28 / 187 |
|
1.79% |
1 / 56 |
5525.53 | |
0.00% |
0 / 1 |
getHandler | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
getParamMap | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
validateParam | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
makeParamString | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
parseParamString | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
normaliseParams | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getImageSize | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getSizeAndMetadata | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMetadata | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
useLegacyMetadata | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
6 | |||
hasMostDerivedMethod | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getSizeAndMetadataWithFallback | |
0.00% |
0 / 39 |
|
0.00% |
0 / 1 |
182 | |||
getMetadataVersion | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
convertMetadataVersion | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMetadataType | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isMetadataValid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isFileMetadataValid | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getCommonMetaArray | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getScriptedTransform | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getTransform | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
doTransform | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
getThumbType | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 | |||
canRender | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
mustRender | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isMultiPage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
pageCount | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isVectorized | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isAnimatedImage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
canAnimateThumbnail | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isEnabled | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageDimensions | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageText | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getEntireText | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
formatMetadata | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
formatMetadataHelper | |
92.00% |
23 / 25 |
|
0.00% |
0 / 1 |
7.03 | |||
formatTag | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
visibleMetadataFields | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
addMeta | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
getShortDesc | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getLongDesc | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGeneralShortDesc | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getGeneralLongDesc | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
fitBoxWidth | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
getDimensionsString | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
parserTransformHook | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
verifyUpload | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
removeBadFile | |
0.00% |
0 / 13 |
|
0.00% |
0 / 1 |
30 | |||
filterThumbnailPurgeList | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
canRotate | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getRotation | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
logErrorForExternalProcess | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
getAvailableLanguages | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getMatchedLanguage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getDefaultRenderLanguage | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getLength | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
isExpensiveToThumbnail | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
supportsBucketing | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
sanitizeParamsForBucketing | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getWarningConfig | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
getPageRangesByDimensions | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
56 | |||
getContentHeaders | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
useSplitMetadata | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | /** |
3 | * This program is free software; you can redistribute it and/or modify |
4 | * it under the terms of the GNU General Public License as published by |
5 | * the Free Software Foundation; either version 2 of the License, or |
6 | * (at your option) any later version. |
7 | * |
8 | * This program is distributed in the hope that it will be useful, |
9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
11 | * GNU General Public License for more details. |
12 | * |
13 | * You should have received a copy of the GNU General Public License along |
14 | * with this program; if not, write to the Free Software Foundation, Inc., |
15 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
16 | * http://www.gnu.org/copyleft/gpl.html |
17 | * |
18 | * @file |
19 | */ |
20 | |
21 | use MediaWiki\Context\IContextSource; |
22 | use MediaWiki\HookContainer\HookRunner; |
23 | use MediaWiki\MediaWikiServices; |
24 | use MediaWiki\Parser\Parser; |
25 | use MediaWiki\Status\Status; |
26 | |
27 | /** |
28 | * @defgroup Media Media |
29 | * |
30 | * Media handlers and other classes relating to Multimedia support, |
31 | * with the exception of FileRepo and FileBackend, which have their own groups. |
32 | */ |
33 | |
34 | /** |
35 | * Base media handler class |
36 | * |
37 | * @stable to extend |
38 | * @ingroup Media |
39 | */ |
40 | abstract class MediaHandler { |
41 | public const TRANSFORM_LATER = 1; |
42 | public const METADATA_GOOD = true; |
43 | public const METADATA_BAD = false; |
44 | public const METADATA_COMPATIBLE = 2; // for old but backwards compatible. |
45 | /** |
46 | * Max length of error logged by logErrorForExternalProcess() |
47 | */ |
48 | private const MAX_ERR_LOG_SIZE = 65535; |
49 | |
50 | /** |
51 | * Get a MediaHandler for a given MIME type from the instance cache |
52 | * |
53 | * @param string $type |
54 | * @return MediaHandler|false |
55 | */ |
56 | public static function getHandler( $type ) { |
57 | return MediaWikiServices::getInstance() |
58 | ->getMediaHandlerFactory()->getHandler( $type ); |
59 | } |
60 | |
61 | /** |
62 | * Get an associative array mapping magic word IDs to parameter names. |
63 | * Will be used by the parser to identify parameters. |
64 | * @return string[] |
65 | */ |
66 | abstract public function getParamMap(); |
67 | |
68 | /** |
69 | * Validate a thumbnail parameter at parse time. |
70 | * Return true to accept the parameter, and false to reject it. |
71 | * If you return false, the parser will do something quiet and forgiving. |
72 | * |
73 | * @param string $name |
74 | * @param mixed $value |
75 | * @return bool |
76 | */ |
77 | abstract public function validateParam( $name, $value ); |
78 | |
79 | /** |
80 | * Merge a parameter array into a string appropriate for inclusion in filenames |
81 | * |
82 | * @param array $params Array of parameters that have been through normaliseParams. |
83 | * @return string |
84 | */ |
85 | abstract public function makeParamString( $params ); |
86 | |
87 | /** |
88 | * Parse a param string made with makeParamString back into an array |
89 | * |
90 | * @param string $str The parameter string without file name (e.g. 122px) |
91 | * @return array|false Array of parameters or false on failure. |
92 | */ |
93 | abstract public function parseParamString( $str ); |
94 | |
95 | /** |
96 | * Changes the parameter array as necessary, ready for transformation. |
97 | * Should be idempotent. |
98 | * Returns false if the parameters are unacceptable and the transform should fail |
99 | * @param File $image |
100 | * @param array &$params |
101 | * @return bool |
102 | */ |
103 | abstract public function normaliseParams( $image, &$params ); |
104 | |
105 | /** |
106 | * Get an image size array like that returned by getimagesize(), or false if it |
107 | * can't be determined. |
108 | * |
109 | * This function is used for determining the width, height and bitdepth directly |
110 | * from an image. The results are stored in the database in the img_width, |
111 | * img_height, img_bits fields. |
112 | * |
113 | * @note If this is a multipage file, return the width and height of the |
114 | * first page. |
115 | * |
116 | * @deprecated since 1.37, override getSizeAndMetadata instead |
117 | * |
118 | * @param File|FSFile|false $image The image object, or false if there isn't one. |
119 | * Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!) |
120 | * @param string $path The filename |
121 | * @return array|false Follow the format of PHP getimagesize() internal function. |
122 | * See https://www.php.net/getimagesize. MediaWiki will only ever use the |
123 | * first two array keys (the width and height), and the 'bits' associative |
124 | * key. All other array keys are ignored. Returning a 'bits' key is optional |
125 | * as not all formats have a notion of "bitdepth". Returns false on failure. |
126 | */ |
127 | public function getImageSize( $image, $path ) { |
128 | return false; |
129 | } |
130 | |
131 | /** |
132 | * Get image size information and metadata array. |
133 | * |
134 | * If this returns null, the caller will fall back to getImageSize() and |
135 | * getMetadata(). |
136 | * |
137 | * If getImageSize() or getMetadata() are implemented in the most derived |
138 | * class, they will be used instead of this function. To override this |
139 | * behaviour, override useLegacyMetadata(). |
140 | * |
141 | * @stable to override |
142 | * @since 1.37 |
143 | * |
144 | * @param MediaHandlerState $state An object for saving process-local state. |
145 | * This is normally a File object which will be passed back to other |
146 | * MediaHandler methods like pageCount(), if they are called in the same |
147 | * request. The handler can use this object to save its state. |
148 | * @param string $path The filename |
149 | * @return array|null Null to fall back to getImageSize(), or an array with |
150 | * the following keys. All keys are optional. |
151 | * - width: The width. If multipage, return the first page width. (optional) |
152 | * - height: The height. If multipage, return the first page height. (optional) |
153 | * - bits: The number of bits for each color (optional) |
154 | * - metadata: A JSON-serializable array of metadata (optional) |
155 | */ |
156 | public function getSizeAndMetadata( $state, $path ) { |
157 | return null; |
158 | } |
159 | |
160 | /** |
161 | * Get handler-specific metadata which will be saved in the img_metadata field. |
162 | * @deprecated since 1.37 override getSizeAndMetadata() instead |
163 | * |
164 | * @param File|FSFile|false $image The image object, or false if there isn't one. |
165 | * Warning, FSFile::getPropsFromPath might pass an FSFile instead of File (!) |
166 | * @param string $path The filename |
167 | * @return string A string of metadata in php serialized form (Run through serialize()) |
168 | */ |
169 | public function getMetadata( $image, $path ) { |
170 | return ''; |
171 | } |
172 | |
173 | /** |
174 | * If this returns true, the new method getSizeAndMetadata() will not be |
175 | * called. The legacy methods getMetadata() and getImageSize() will be used |
176 | * instead. |
177 | * |
178 | * @since 1.37 |
179 | * @stable to override |
180 | * @return bool |
181 | */ |
182 | protected function useLegacyMetadata() { |
183 | return $this->hasMostDerivedMethod( 'getMetadata' ) |
184 | || $this->hasMostDerivedMethod( 'getImageSize' ); |
185 | } |
186 | |
187 | /** |
188 | * Check whether a method is implemented in the most derived class. |
189 | * |
190 | * @since 1.37 |
191 | * @param string $name |
192 | * @return bool |
193 | */ |
194 | protected function hasMostDerivedMethod( $name ) { |
195 | $rc = new ReflectionClass( $this ); |
196 | $rm = new ReflectionMethod( $this, $name ); |
197 | return $rm->getDeclaringClass()->getName() === $rc->getName(); |
198 | } |
199 | |
200 | /** |
201 | * Get the metadata array and the image size, with b/c fallback. |
202 | * |
203 | * The legacy methods will be used if useLegacyMetadata() returns true or |
204 | * if getSizeAndMetadata() returns null. |
205 | * |
206 | * Absent metadata will be normalized to an empty array. Absent width and |
207 | * height will be normalized to zero. |
208 | * |
209 | * @param File|FSFile $file This must be a File or FSFile to support the |
210 | * legacy methods. When the legacy methods are removed, this will be |
211 | * narrowed to MediaHandlerState. |
212 | * @param string $path |
213 | * @return array|false|null False on failure, or an array with the following keys: |
214 | * - width: The width. If multipage, return the first page width. |
215 | * - height: The height. If multipage, return the first page height. |
216 | * - bits: The number of bits for each color (optional) |
217 | * - metadata: A JSON-serializable array of metadata |
218 | * @since 1.37 |
219 | */ |
220 | final public function getSizeAndMetadataWithFallback( $file, $path ) { |
221 | if ( !$this->useLegacyMetadata() ) { |
222 | if ( $file instanceof MediaHandlerState ) { |
223 | $state = $file; |
224 | } else { |
225 | $state = new TrivialMediaHandlerState; |
226 | } |
227 | $info = $this->getSizeAndMetadata( $state, $path ); |
228 | if ( $info === false ) { |
229 | return false; |
230 | } |
231 | if ( $info !== null ) { |
232 | $info += [ 'width' => 0, 'height' => 0, 'metadata' => [] ]; |
233 | if ( !is_array( $info['metadata'] ) ) { |
234 | throw new InvalidArgumentException( 'Media handler ' . |
235 | static::class . ' returned ' . gettype( $info['metadata'] ) . |
236 | ' for metadata, should be array' ); |
237 | } |
238 | return $info; |
239 | } |
240 | } |
241 | |
242 | $blob = $this->getMetadata( $file, $path ); |
243 | // @phan-suppress-next-line PhanParamTooMany |
244 | $size = $this->getImageSize( |
245 | $file, |
246 | $path, |
247 | $blob // Secret TimedMediaHandler parameter |
248 | ); |
249 | if ( $blob === false && $size === false ) { |
250 | return false; |
251 | } |
252 | if ( $size ) { |
253 | $info = [ |
254 | 'width' => $size[0] ?? 0, |
255 | 'height' => $size[1] ?? 0 |
256 | ]; |
257 | if ( isset( $size['bits'] ) ) { |
258 | $info['bits'] = $size['bits']; |
259 | } |
260 | } else { |
261 | $info = [ 'width' => 0, 'height' => 0 ]; |
262 | } |
263 | if ( $blob !== false ) { |
264 | // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged |
265 | $metadata = @unserialize( $blob ); |
266 | if ( $metadata === false ) { |
267 | // Unserialize error |
268 | $metadata = [ '_error' => $blob ]; |
269 | } elseif ( !is_array( $metadata ) ) { |
270 | $metadata = []; |
271 | } |
272 | $info['metadata'] = $metadata; |
273 | } else { |
274 | $info['metadata'] = []; |
275 | } |
276 | return $info; |
277 | } |
278 | |
279 | /** |
280 | * Get metadata version. |
281 | * |
282 | * This is not used for validating metadata, this is used for the api when returning |
283 | * metadata, since api content formats should stay the same over time, and so things |
284 | * using ForeignApiRepo can keep backwards compatibility |
285 | * |
286 | * All core media handlers share a common version number, and extensions can |
287 | * use the GetMetadataVersion hook to append to the array (they should append a unique |
288 | * string so not to get confusing). If there was a media handler named 'foo' with metadata |
289 | * version 3 it might add to the end of the array the element 'foo=3'. if the core metadata |
290 | * version is 2, the end version string would look like '2;foo=3'. |
291 | * |
292 | * @stable to override |
293 | * |
294 | * @return string Version string |
295 | */ |
296 | public static function getMetadataVersion() { |
297 | $version = [ '2' ]; // core metadata version |
298 | ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onGetMetadataVersion( $version ); |
299 | |
300 | return implode( ';', $version ); |
301 | } |
302 | |
303 | /** |
304 | * Convert metadata version. |
305 | * |
306 | * By default just returns $metadata, but can be used to allow |
307 | * media handlers to convert between metadata versions. |
308 | * @stable to override |
309 | * |
310 | * @param array $metadata Metadata array |
311 | * @param int|string $version Target version |
312 | * @return array Serialized metadata in specified version, or $metadata on fail. |
313 | */ |
314 | public function convertMetadataVersion( $metadata, $version = 1 ) { |
315 | return $metadata; |
316 | } |
317 | |
318 | /** |
319 | * Get a string describing the type of metadata, for display purposes. |
320 | * @stable to override |
321 | * |
322 | * @note This method is currently unused. |
323 | * @param File $image |
324 | * @return string|false |
325 | */ |
326 | public function getMetadataType( $image ) { |
327 | return false; |
328 | } |
329 | |
330 | /** |
331 | * Check if the metadata string is valid for this handler. |
332 | * If it returns MediaHandler::METADATA_BAD (or false), Image |
333 | * will reload the metadata from the file and update the database. |
334 | * MediaHandler::METADATA_GOOD for if the metadata is a-ok, |
335 | * MediaHandler::METADATA_COMPATIBLE if metadata is old but backwards |
336 | * compatible (which may or may not trigger a metadata reload). |
337 | * |
338 | * @note Returning self::METADATA_BAD will trigger a metadata reload from |
339 | * file on page view. Always returning this from a broken file, or suddenly |
340 | * triggering as bad metadata for a large number of files can cause |
341 | * performance problems. |
342 | * |
343 | * @deprecated since 1.37 use isFileMetadataValid |
344 | * @param File $image |
345 | * @param string $metadata The metadata in serialized form |
346 | * @return bool|int |
347 | */ |
348 | public function isMetadataValid( $image, $metadata ) { |
349 | return self::METADATA_GOOD; |
350 | } |
351 | |
352 | /** |
353 | * Check if the metadata is valid for this handler. |
354 | * If it returns MediaHandler::METADATA_BAD (or false), Image |
355 | * will reload the metadata from the file and update the database. |
356 | * MediaHandler::METADATA_GOOD for if the metadata is a-ok, |
357 | * MediaHandler::METADATA_COMPATIBLE if metadata is old but backwards |
358 | * compatible (which may or may not trigger a metadata reload). |
359 | * |
360 | * @note Returning self::METADATA_BAD will trigger a metadata reload from |
361 | * file on page view. Always returning this from a broken file, or suddenly |
362 | * triggering as bad metadata for a large number of files can cause |
363 | * performance problems. |
364 | * |
365 | * This was introduced in 1.37 to replace isMetadataValid(), which took a |
366 | * serialized string as a parameter. Handlers overriding this method are |
367 | * expected to use accessors to get the metadata out of the File. The |
368 | * reasons for the change were to get rid of serialization, and to allow |
369 | * handlers to partially load metadata with getMetadataItem(). For example |
370 | * a handler could just validate a version number. |
371 | * |
372 | * @stable to override |
373 | * @since 1.37 |
374 | * @param File $image |
375 | * @return bool|int |
376 | */ |
377 | public function isFileMetadataValid( $image ) { |
378 | return self::METADATA_GOOD; |
379 | } |
380 | |
381 | /** |
382 | * Get an array of standard (FormatMetadata type) metadata values. |
383 | * |
384 | * The returned data is largely the same as that from getMetadata(), |
385 | * but formatted in a standard, stable, handler-independent way. |
386 | * The idea being that some values like ImageDescription or Artist |
387 | * are universal and should be retrievable in a handler generic way. |
388 | * |
389 | * The specific properties are the type of properties that can be |
390 | * handled by the FormatMetadata class. These values are exposed to the |
391 | * user via the filemetadata parser function. |
392 | * |
393 | * Details of the response format of this function can be found at |
394 | * https://www.mediawiki.org/wiki/Manual:File_metadata_handling |
395 | * tl/dr: the response is an associative array of |
396 | * properties keyed by name, but the value can be complex. You probably |
397 | * want to call one of the FormatMetadata::flatten* functions on the |
398 | * property values before using them, or call |
399 | * FormatMetadata::getFormattedData() on the full response array, which |
400 | * transforms all values into prettified, human-readable text. |
401 | * |
402 | * Subclasses overriding this function must return a value which is a |
403 | * valid API response fragment (all associative array keys are valid |
404 | * XML tagnames). |
405 | * |
406 | * Note, if the file simply has no metadata, but the handler supports |
407 | * this interface, it should return an empty array, not false. |
408 | * |
409 | * @stable to override |
410 | * |
411 | * @param File $file |
412 | * @return array|false False if interface not supported |
413 | * @since 1.23 |
414 | */ |
415 | public function getCommonMetaArray( File $file ) { |
416 | return false; |
417 | } |
418 | |
419 | /** |
420 | * Get a MediaTransformOutput object representing an alternate of the transformed |
421 | * output which will call an intermediary thumbnail assist script. |
422 | * |
423 | * Used when the repository has a thumbnailScriptUrl option configured. |
424 | * |
425 | * Return false to fall back to the regular getTransform(). |
426 | * |
427 | * @stable to override |
428 | * |
429 | * @param File $image |
430 | * @param string $script |
431 | * @param array $params |
432 | * @return ThumbnailImage|false |
433 | */ |
434 | public function getScriptedTransform( $image, $script, $params ) { |
435 | return false; |
436 | } |
437 | |
438 | /** |
439 | * Get a MediaTransformOutput object representing the transformed output. Does not |
440 | * actually do the transform. |
441 | * |
442 | * @stable to override |
443 | * |
444 | * @param File $image |
445 | * @param string $dstPath Filesystem destination path |
446 | * @param string $dstUrl Destination URL to use in output HTML |
447 | * @param array $params Arbitrary set of parameters validated by $this->validateParam() |
448 | * @return MediaTransformOutput |
449 | */ |
450 | final public function getTransform( $image, $dstPath, $dstUrl, $params ) { |
451 | return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER ); |
452 | } |
453 | |
454 | /** |
455 | * Get a MediaTransformOutput object representing the transformed output. Does the |
456 | * transform unless $flags contains self::TRANSFORM_LATER. |
457 | * |
458 | * @stable to override |
459 | * |
460 | * @param File $image |
461 | * @param string $dstPath Filesystem destination path |
462 | * @param string $dstUrl Destination URL to use in output HTML |
463 | * @param array $params Arbitrary set of parameters validated by $this->validateParam() |
464 | * Note: These parameters have *not* gone through $this->normaliseParams() |
465 | * @param int $flags A bitfield, may contain self::TRANSFORM_LATER |
466 | * @return MediaTransformOutput |
467 | */ |
468 | abstract public function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 ); |
469 | |
470 | /** |
471 | * Get the thumbnail extension and MIME type for a given source MIME type |
472 | * |
473 | * @stable to override |
474 | * |
475 | * @param string $ext Extension of original file |
476 | * @param string $mime MIME type of original file |
477 | * @param array|null $params Handler specific rendering parameters |
478 | * @return array Thumbnail extension and MIME type |
479 | */ |
480 | public function getThumbType( $ext, $mime, $params = null ) { |
481 | $magic = MediaWikiServices::getInstance()->getMimeAnalyzer(); |
482 | if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) { |
483 | // The extension is not valid for this MIME type and we do |
484 | // recognize the MIME type |
485 | $knownExt = $magic->getExtensionFromMimeTypeOrNull( $mime ); |
486 | if ( $knownExt !== null ) { |
487 | return [ $knownExt, $mime ]; |
488 | } |
489 | } |
490 | |
491 | // The extension is correct (true) or the MIME type is unknown to |
492 | // MediaWiki (null) |
493 | return [ $ext, $mime ]; |
494 | } |
495 | |
496 | /** |
497 | * True if the handled types can be transformed |
498 | * |
499 | * @stable to override |
500 | * |
501 | * @param File $file |
502 | * @return bool |
503 | */ |
504 | public function canRender( $file ) { |
505 | return true; |
506 | } |
507 | |
508 | /** |
509 | * True if handled types cannot be displayed directly in a browser |
510 | * but can be rendered |
511 | * |
512 | * @stable to override |
513 | * |
514 | * @param File $file |
515 | * @return bool |
516 | */ |
517 | public function mustRender( $file ) { |
518 | return false; |
519 | } |
520 | |
521 | /** |
522 | * True if the type has multi-page capabilities |
523 | * |
524 | * @stable to override |
525 | * |
526 | * @param File $file |
527 | * @return bool |
528 | */ |
529 | public function isMultiPage( $file ) { |
530 | return false; |
531 | } |
532 | |
533 | /** |
534 | * Page count for a multi-page document, false if unsupported or unknown |
535 | * |
536 | * @stable to override |
537 | * |
538 | * @param File $file |
539 | * @return int|false |
540 | */ |
541 | public function pageCount( File $file ) { |
542 | return false; |
543 | } |
544 | |
545 | /** |
546 | * The material is vectorized and thus scaling is lossless |
547 | * |
548 | * @stable to override |
549 | * |
550 | * @param File $file |
551 | * @return bool |
552 | */ |
553 | public function isVectorized( $file ) { |
554 | return false; |
555 | } |
556 | |
557 | /** |
558 | * The material is an image, and is animated. |
559 | * In particular, video material need not return true. |
560 | * @note Before 1.20, this was a method of ImageHandler only |
561 | * |
562 | * @stable to override |
563 | * |
564 | * @param File $file |
565 | * @return bool |
566 | */ |
567 | public function isAnimatedImage( $file ) { |
568 | return false; |
569 | } |
570 | |
571 | /** |
572 | * If the material is animated, we can animate the thumbnail |
573 | * @since 1.20 |
574 | * |
575 | * @stable to override |
576 | * |
577 | * @param File $file |
578 | * @return bool If material is not animated, handler may return any value. |
579 | */ |
580 | public function canAnimateThumbnail( $file ) { |
581 | return true; |
582 | } |
583 | |
584 | /** |
585 | * False if the handler is disabled for all files |
586 | * @stable to override |
587 | * |
588 | * @return bool |
589 | */ |
590 | public function isEnabled() { |
591 | return true; |
592 | } |
593 | |
594 | /** |
595 | * Get an associative array of page dimensions |
596 | * Currently "width" and "height" are understood, but this might be |
597 | * expanded in the future. |
598 | * Returns false if unknown. |
599 | * |
600 | * For a single page document format (!isMultipage()), this should return |
601 | * false. |
602 | * |
603 | * @note For non-paged media, use getImageSize. |
604 | * |
605 | * @stable to override |
606 | * |
607 | * @param File $image |
608 | * @param int $page What page to get dimensions of |
609 | * @return array|false |
610 | */ |
611 | public function getPageDimensions( File $image, $page ) { |
612 | return false; |
613 | } |
614 | |
615 | /** |
616 | * Generic getter for text layer. |
617 | * Currently overloaded by PDF and DjVu handlers |
618 | * @stable to override |
619 | * |
620 | * @param File $image |
621 | * @param int $page Page number to get information for |
622 | * @return string|false Page text or false when no text found or if |
623 | * unsupported. |
624 | */ |
625 | public function getPageText( File $image, $page ) { |
626 | return false; |
627 | } |
628 | |
629 | /** |
630 | * Get the text of the entire document. |
631 | * @param File $file |
632 | * @return string|false The text of the document or false if unsupported. |
633 | */ |
634 | public function getEntireText( File $file ) { |
635 | $numPages = $file->pageCount(); |
636 | if ( !$numPages ) { |
637 | // Not a multipage document |
638 | return $this->getPageText( $file, 1 ); |
639 | } |
640 | $document = ''; |
641 | for ( $i = 1; $i <= $numPages; $i++ ) { |
642 | $curPage = $this->getPageText( $file, $i ); |
643 | if ( is_string( $curPage ) ) { |
644 | $document .= $curPage . "\n"; |
645 | } |
646 | } |
647 | if ( $document !== '' ) { |
648 | return $document; |
649 | } |
650 | return false; |
651 | } |
652 | |
653 | /** |
654 | * Get an array structure that looks like this: |
655 | * |
656 | * [ |
657 | * 'visible' => [ |
658 | * 'Human-readable name' => 'Human readable value', |
659 | * ... |
660 | * ], |
661 | * 'collapsed' => [ |
662 | * 'Human-readable name' => 'Human readable value', |
663 | * ... |
664 | * ] |
665 | * ] |
666 | * The UI will format this into a table where the visible fields are always |
667 | * visible, and the collapsed fields are optionally visible. |
668 | * |
669 | * The function should return false if there is no metadata to display. |
670 | */ |
671 | |
672 | /** |
673 | * @todo FIXME: This interface is not very flexible. The media handler |
674 | * should generate HTML instead. It can do all the formatting according |
675 | * to some standard. That makes it possible to do things like visual |
676 | * indication of grouped and chained streams in ogg container files. |
677 | * @stable to override |
678 | * |
679 | * @param File $image |
680 | * @param IContextSource|false $context |
681 | * @return array|false |
682 | */ |
683 | public function formatMetadata( $image, $context = false ) { |
684 | return false; |
685 | } |
686 | |
687 | /** sorts the visible/invisible field. |
688 | * Split off from ImageHandler::formatMetadata, as used by more than |
689 | * one type of handler. |
690 | * |
691 | * This is used by the media handlers that use the FormatMetadata class |
692 | * |
693 | * @stable to override |
694 | * |
695 | * @param array $metadataArray |
696 | * @param IContextSource|false $context |
697 | * @return array[] Array for use displaying metadata. |
698 | */ |
699 | protected function formatMetadataHelper( $metadataArray, $context = false ) { |
700 | $result = [ |
701 | 'visible' => [], |
702 | 'collapsed' => [] |
703 | ]; |
704 | |
705 | // Allow this MediaHandler to override formatting on certain values |
706 | foreach ( $metadataArray as $tag => $vals ) { |
707 | $v = $this->formatTag( $tag, $vals, $context ); |
708 | if ( $v === false ) { |
709 | // Use default formatting |
710 | continue; |
711 | } |
712 | if ( $v === null ) { |
713 | // Remove this tag, don't format it for display |
714 | unset( $metadataArray[$tag] ); |
715 | } else { |
716 | // Allow subclass to override default formatting. |
717 | $metadataArray[$tag] = [ '_formatted' => $v ]; |
718 | if ( isset( $v['_type'] ) ) { |
719 | $metadataArray[$tag]['_type'] = $v['_type']; |
720 | unset( $metadataArray[$tag]['_formatted']['_type'] ); |
721 | } |
722 | } |
723 | } |
724 | |
725 | $formatted = FormatMetadata::getFormattedData( $metadataArray, $context ); |
726 | // Sort fields into visible and collapsed |
727 | $visibleFields = $this->visibleMetadataFields(); |
728 | foreach ( $formatted as $name => $value ) { |
729 | $tag = strtolower( $name ); |
730 | self::addMeta( $result, |
731 | in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed', |
732 | 'exif', |
733 | $tag, |
734 | $value |
735 | ); |
736 | } |
737 | |
738 | return $result; |
739 | } |
740 | |
741 | /** |
742 | * Override default formatting for the given metadata field. |
743 | * |
744 | * @stable to override |
745 | * |
746 | * @param string $key The metadata field key |
747 | * @param string|array $vals The unformatted value of this metadata field |
748 | * @param IContextSource|false $context Context to use (optional) |
749 | * @return false|null|string|array False to use default formatting, null |
750 | * to remove this tag from the formatted list; otherwise return |
751 | * a formatted HTML string (or array of them). |
752 | */ |
753 | protected function formatTag( string $key, $vals, $context = false ) { |
754 | return false; // Use default formatting |
755 | } |
756 | |
757 | /** |
758 | * Get a list of metadata items which should be displayed when |
759 | * the metadata table is collapsed. |
760 | * |
761 | * @stable to override |
762 | * |
763 | * @return string[] |
764 | */ |
765 | protected function visibleMetadataFields() { |
766 | return FormatMetadata::getVisibleFields(); |
767 | } |
768 | |
769 | /** |
770 | * This is used to generate an array element for each metadata value |
771 | * That array is then used to generate the table of metadata values |
772 | * on the image page |
773 | * |
774 | * @param array &$array An array containing elements for each type of visibility |
775 | * and each of those elements being an array of metadata items. This function adds |
776 | * a value to that array. |
777 | * @param string $visibility ('visible' or 'collapsed') if this value is hidden |
778 | * by default. |
779 | * @param string $type Type of metadata tag (currently always 'exif') |
780 | * @param string $id The name of the metadata tag (like 'artist' for example). |
781 | * its name in the table displayed is the message "$type-$id" (Ex exif-artist ). |
782 | * @param string $value Thingy goes into a wikitext table; it used to be escaped but |
783 | * that was incompatible with previous practise of customized display |
784 | * with wikitext formatting via messages such as 'exif-model-value'. |
785 | * So the escaping is taken back out, but generally this seems a confusing |
786 | * interface. |
787 | * @param bool|string $param Value to pass to the message for the name of the field |
788 | * as $1. Currently this parameter doesn't seem to ever be used. |
789 | * |
790 | * Note, everything here is passed through the parser later on (!) |
791 | */ |
792 | protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) { |
793 | $msg = wfMessage( "$type-$id", $param ); |
794 | if ( $msg->exists() ) { |
795 | $name = $msg->text(); |
796 | } else { |
797 | // This is for future compatibility when using instant commons. |
798 | // So as to not display as ugly a name if a new metadata |
799 | // property is defined that we don't know about |
800 | // (not a major issue since such a property would be collapsed |
801 | // by default). |
802 | wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id ); |
803 | $name = wfEscapeWikiText( $id ); |
804 | } |
805 | $array[$visibility][] = [ |
806 | 'id' => "$type-$id", |
807 | 'name' => $name, |
808 | 'value' => $value |
809 | ]; |
810 | } |
811 | |
812 | /** |
813 | * Short description. Shown on Special:Search results. |
814 | * |
815 | * @stable to override |
816 | * |
817 | * @param File $file |
818 | * @return string |
819 | */ |
820 | public function getShortDesc( $file ) { |
821 | return self::getGeneralShortDesc( $file ); |
822 | } |
823 | |
824 | /** |
825 | * Long description. Shown under image on image description page surrounded by (). |
826 | * |
827 | * @stable to override |
828 | * |
829 | * @param File $file |
830 | * @return string |
831 | */ |
832 | public function getLongDesc( $file ) { |
833 | return self::getGeneralLongDesc( $file ); |
834 | } |
835 | |
836 | /** |
837 | * Used instead of getShortDesc if there is no handler registered for file. |
838 | * |
839 | * @param File $file |
840 | * @return string |
841 | */ |
842 | public static function getGeneralShortDesc( $file ) { |
843 | global $wgLang; |
844 | |
845 | return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) ); |
846 | } |
847 | |
848 | /** |
849 | * Used instead of getLongDesc if there is no handler registered for file. |
850 | * |
851 | * @param File $file |
852 | * @return string |
853 | */ |
854 | public static function getGeneralLongDesc( $file ) { |
855 | return wfMessage( 'file-info' )->sizeParams( $file->getSize() ) |
856 | ->params( '<span class="mime-type">' . $file->getMimeType() . '</span>' )->parse(); |
857 | } |
858 | |
859 | /** |
860 | * Calculate the largest thumbnail width for a given original file size |
861 | * such that the thumbnail's height is at most $maxHeight. |
862 | * @param int $boxWidth Width of the thumbnail box. |
863 | * @param int $boxHeight Height of the thumbnail box. |
864 | * @param int $maxHeight Maximum height expected for the thumbnail. |
865 | * @return int |
866 | */ |
867 | public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) { |
868 | $idealWidth = $boxWidth * $maxHeight / $boxHeight; |
869 | $roundedUp = ceil( $idealWidth ); |
870 | if ( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) { |
871 | return (int)floor( $idealWidth ); |
872 | } |
873 | return $roundedUp; |
874 | } |
875 | |
876 | /** |
877 | * Shown in file history box on image description page. |
878 | * |
879 | * @stable to override |
880 | * |
881 | * @param File $file |
882 | * @return string Dimensions |
883 | */ |
884 | public function getDimensionsString( $file ) { |
885 | return ''; |
886 | } |
887 | |
888 | /** |
889 | * Modify the parser object post-transform. |
890 | * |
891 | * This is often used to do $parser->addOutputHook(), |
892 | * in order to add some javascript to render a viewer. |
893 | * See TimedMediaHandler or OggHandler for an example. |
894 | * |
895 | * @stable to override |
896 | * |
897 | * @param Parser $parser |
898 | * @param File $file |
899 | */ |
900 | public function parserTransformHook( $parser, $file ) { |
901 | } |
902 | |
903 | /** |
904 | * File validation hook called on upload. |
905 | * |
906 | * If the file at the given local path is not valid, or its MIME type does not |
907 | * match the handler class, a Status object should be returned containing |
908 | * relevant errors. |
909 | * |
910 | * @stable to override |
911 | * |
912 | * @param string $fileName The local path to the file. |
913 | * @return Status |
914 | */ |
915 | public function verifyUpload( $fileName ) { |
916 | return Status::newGood(); |
917 | } |
918 | |
919 | /** |
920 | * Check for zero-sized thumbnails. These can be generated when |
921 | * no disk space is available or some other error occurs |
922 | * |
923 | * @stable to override |
924 | * |
925 | * @param string $dstPath The location of the suspect file |
926 | * @param int $retval Return value of some shell process, file will be deleted if this is non-zero |
927 | * @return bool True if removed, false otherwise |
928 | */ |
929 | public function removeBadFile( $dstPath, $retval = 0 ) { |
930 | if ( file_exists( $dstPath ) ) { |
931 | $thumbstat = stat( $dstPath ); |
932 | if ( $thumbstat['size'] == 0 || $retval != 0 ) { |
933 | $result = unlink( $dstPath ); |
934 | |
935 | if ( $result ) { |
936 | wfDebugLog( 'thumbnail', |
937 | sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded', |
938 | $thumbstat['size'], $dstPath ) ); |
939 | } else { |
940 | wfDebugLog( 'thumbnail', |
941 | sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed', |
942 | $thumbstat['size'], $dstPath ) ); |
943 | } |
944 | |
945 | return true; |
946 | } |
947 | } |
948 | |
949 | return false; |
950 | } |
951 | |
952 | /** |
953 | * Remove files from the purge list. |
954 | * |
955 | * This is used by some video handlers to prevent ?action=purge |
956 | * from removing a transcoded video, which is expensive to |
957 | * regenerate. |
958 | * |
959 | * @see LocalFile::purgeThumbnails |
960 | * @stable to override |
961 | * |
962 | * @param array &$files |
963 | * @param array $options Purge options. Currently will always be |
964 | * an array with a single key 'forThumbRefresh' set to true. |
965 | */ |
966 | public function filterThumbnailPurgeList( &$files, $options ) { |
967 | // Do nothing |
968 | } |
969 | |
970 | /** |
971 | * True if the handler can rotate the media |
972 | * @since 1.24 non-static. From 1.21-1.23 was static |
973 | * @stable to override |
974 | * |
975 | * @return bool |
976 | */ |
977 | public function canRotate() { |
978 | return false; |
979 | } |
980 | |
981 | /** |
982 | * On supporting image formats, try to read out the low-level orientation |
983 | * of the file and return the angle that the file needs to be rotated to |
984 | * be viewed. |
985 | * |
986 | * This information is only useful when manipulating the original file; |
987 | * the width and height we normally work with is logical, and will match |
988 | * any produced output views. |
989 | * |
990 | * For files we don't know, we return 0. |
991 | * |
992 | * @stable to override |
993 | * |
994 | * @param File $file |
995 | * @return int 0, 90, 180 or 270 |
996 | */ |
997 | public function getRotation( $file ) { |
998 | return 0; |
999 | } |
1000 | |
1001 | /** |
1002 | * Log an error that occurred in an external process |
1003 | * |
1004 | * Moved from BitmapHandler to MediaHandler with MediaWiki 1.23 |
1005 | * |
1006 | * @since 1.23 |
1007 | * @param int $retval |
1008 | * @param string $err Error reported by command. Anything longer than |
1009 | * MediaHandler::MAX_ERR_LOG_SIZE is stripped off. |
1010 | * @param string $cmd |
1011 | */ |
1012 | protected function logErrorForExternalProcess( $retval, $err, $cmd ) { |
1013 | # Keep error output limited (T59985) |
1014 | $errMessage = trim( substr( $err, 0, self::MAX_ERR_LOG_SIZE ) ); |
1015 | |
1016 | wfDebugLog( 'thumbnail', |
1017 | sprintf( 'thumbnail failed on %s: error %d "%s" from "%s"', |
1018 | wfHostname(), $retval, $errMessage, $cmd ) ); |
1019 | } |
1020 | |
1021 | /** |
1022 | * Get list of languages file can be viewed in. |
1023 | * |
1024 | * @stable to override |
1025 | * |
1026 | * @param File $file |
1027 | * @return string[] Array of IETF language codes, or empty array if unsupported. |
1028 | * @since 1.23 |
1029 | */ |
1030 | public function getAvailableLanguages( File $file ) { |
1031 | return []; |
1032 | } |
1033 | |
1034 | /** |
1035 | * When overridden in a descendant class, returns a language code most suiting |
1036 | * |
1037 | * @stable to override |
1038 | * |
1039 | * @since 1.32 |
1040 | * |
1041 | * @param string $userPreferredLanguage IETF Language code requested |
1042 | * @param string[] $availableLanguages IETF Languages present in the file |
1043 | * @return string|null IETF Language code picked or null if not supported/available |
1044 | */ |
1045 | public function getMatchedLanguage( $userPreferredLanguage, array $availableLanguages ) { |
1046 | return null; |
1047 | } |
1048 | |
1049 | /** |
1050 | * On file types that support renderings in multiple languages, |
1051 | * which language is used by default if unspecified. |
1052 | * |
1053 | * If getAvailableLanguages returns a non-empty array, this must return |
1054 | * a valid language code. Otherwise can return null if files of this |
1055 | * type do not support alternative language renderings. |
1056 | * It can also return 'und' for explicitly requesting an undetermined language |
1057 | * |
1058 | * @stable to override |
1059 | * |
1060 | * @param File $file |
1061 | * @return string|null IETF Language code or null if multi-language not supported for filetype. |
1062 | * @since 1.23 |
1063 | */ |
1064 | public function getDefaultRenderLanguage( File $file ) { |
1065 | return null; |
1066 | } |
1067 | |
1068 | /** |
1069 | * If it's an audio file, return the length of the file. Otherwise 0. |
1070 | * |
1071 | * File::getLength() existed for a long time, but was calling a method |
1072 | * that only existed in some subclasses of this class (The TMH ones). |
1073 | * |
1074 | * @stable to override |
1075 | * |
1076 | * @param File $file |
1077 | * @return float Length in seconds |
1078 | * @since 1.23 |
1079 | */ |
1080 | public function getLength( $file ) { |
1081 | return 0.0; |
1082 | } |
1083 | |
1084 | /** |
1085 | * True if creating thumbnails from the file is large or otherwise resource-intensive. |
1086 | * @stable to override |
1087 | * |
1088 | * @param File $file |
1089 | * @return bool |
1090 | */ |
1091 | public function isExpensiveToThumbnail( $file ) { |
1092 | return false; |
1093 | } |
1094 | |
1095 | /** |
1096 | * Returns whether or not this handler supports the chained generation of thumbnails according |
1097 | * to buckets |
1098 | * @stable to override |
1099 | * |
1100 | * @return bool |
1101 | * @since 1.24 |
1102 | */ |
1103 | public function supportsBucketing() { |
1104 | return false; |
1105 | } |
1106 | |
1107 | /** |
1108 | * Returns a normalised params array for which parameters have been cleaned up for bucketing |
1109 | * purposes |
1110 | * @stable to override |
1111 | * |
1112 | * @param array $params |
1113 | * @return array |
1114 | */ |
1115 | public function sanitizeParamsForBucketing( $params ) { |
1116 | return $params; |
1117 | } |
1118 | |
1119 | /** |
1120 | * Gets configuration for the file warning message. Return value of |
1121 | * the following structure: |
1122 | * [ |
1123 | * // Required, module with messages loaded for the client |
1124 | * 'module' => 'example.filewarning.messages', |
1125 | * // Required, array of names of messages |
1126 | * 'messages' => [ |
1127 | * // Required, main warning message |
1128 | * 'main' => 'example-filewarning-main', |
1129 | * // Optional, header for warning dialog |
1130 | * 'header' => 'example-filewarning-header', |
1131 | * // Optional, footer for warning dialog |
1132 | * 'footer' => 'example-filewarning-footer', |
1133 | * // Optional, text for more-information link (see below) |
1134 | * 'info' => 'example-filewarning-info', |
1135 | * ], |
1136 | * // Optional, link for more information |
1137 | * 'link' => 'http://example.com', |
1138 | * ] |
1139 | * |
1140 | * Returns null if no warning is necessary. |
1141 | * @stable to override |
1142 | * @param File $file |
1143 | * @return array|null |
1144 | */ |
1145 | public function getWarningConfig( $file ) { |
1146 | return null; |
1147 | } |
1148 | |
1149 | /** |
1150 | * Converts a dimensions array about a potentially multipage document from an |
1151 | * exhaustive list of ordered page numbers to a list of page ranges |
1152 | * @param array[] $pagesByDimensions |
1153 | * @return string |
1154 | * @since 1.30 |
1155 | */ |
1156 | public static function getPageRangesByDimensions( $pagesByDimensions ) { |
1157 | $pageRangesByDimensions = []; |
1158 | |
1159 | foreach ( $pagesByDimensions as $dimensions => $pageList ) { |
1160 | $ranges = []; |
1161 | $firstPage = $pageList[0]; |
1162 | $lastPage = $firstPage - 1; |
1163 | |
1164 | foreach ( $pageList as $page ) { |
1165 | if ( $page > $lastPage + 1 ) { |
1166 | if ( $firstPage !== $lastPage ) { |
1167 | $ranges[] = "$firstPage-$lastPage"; |
1168 | } else { |
1169 | $ranges[] = "$firstPage"; |
1170 | } |
1171 | |
1172 | $firstPage = $page; |
1173 | } |
1174 | |
1175 | $lastPage = $page; |
1176 | } |
1177 | |
1178 | if ( $firstPage != $lastPage ) { |
1179 | $ranges[] = "$firstPage-$lastPage"; |
1180 | } else { |
1181 | $ranges[] = "$firstPage"; |
1182 | } |
1183 | |
1184 | $pageRangesByDimensions[ $dimensions ] = $ranges; |
1185 | } |
1186 | |
1187 | $dimensionsString = []; |
1188 | foreach ( $pageRangesByDimensions as $dimensions => $pageRanges ) { |
1189 | $dimensionsString[] = "$dimensions:" . implode( ',', $pageRanges ); |
1190 | } |
1191 | |
1192 | return implode( '/', $dimensionsString ); |
1193 | } |
1194 | |
1195 | /** |
1196 | * Get useful response headers for GET/HEAD requests for a file with the given metadata |
1197 | * @stable to override |
1198 | * |
1199 | * @param array $metadata Contains this handler's unserialized getMetadata() for a file |
1200 | * @return array |
1201 | * @since 1.30 |
1202 | */ |
1203 | public function getContentHeaders( $metadata ) { |
1204 | return [ 'X-Content-Dimensions' => '' ]; // T175689 |
1205 | } |
1206 | |
1207 | /** |
1208 | * If this returns true, LocalFile may split metadata up and store its |
1209 | * constituent items separately. This only makes sense if the handler calls |
1210 | * File::getMetadataItem() or File::getMetadataItems() instead of |
1211 | * requesting the whole array at once. |
1212 | * |
1213 | * @return bool |
1214 | */ |
1215 | public function useSplitMetadata() { |
1216 | return false; |
1217 | } |
1218 | } |