Add DROP INDEX support to DatabaseSqlite::replaceVars method
[lhc/web/wiklou.git] / includes / media / MediaHandler.php
1 <?php
2 /**
3 * Media-handling base classes and generic functionality.
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 * @ingroup Media
22 */
23
24 /**
25 * Base media handler class
26 *
27 * @ingroup Media
28 */
29 abstract class MediaHandler {
30 const TRANSFORM_LATER = 1;
31 const METADATA_GOOD = true;
32 const METADATA_BAD = false;
33 const METADATA_COMPATIBLE = 2; // for old but backwards compatible.
34 /**
35 * Instance cache
36 */
37 static $handlers = array();
38
39 /**
40 * Get a MediaHandler for a given MIME type from the instance cache
41 *
42 * @param $type string
43 *
44 * @return MediaHandler
45 */
46 static function getHandler( $type ) {
47 global $wgMediaHandlers;
48 if ( !isset( $wgMediaHandlers[$type] ) ) {
49 wfDebug( __METHOD__ . ": no handler found for $type.\n" );
50 return false;
51 }
52 $class = $wgMediaHandlers[$type];
53 if ( !isset( self::$handlers[$class] ) ) {
54 self::$handlers[$class] = new $class;
55 if ( !self::$handlers[$class]->isEnabled() ) {
56 self::$handlers[$class] = false;
57 }
58 }
59 return self::$handlers[$class];
60 }
61
62 /**
63 * Get an associative array mapping magic word IDs to parameter names.
64 * Will be used by the parser to identify parameters.
65 */
66 abstract 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 $name
74 * @param $value
75 */
76 abstract function validateParam( $name, $value );
77
78 /**
79 * Merge a parameter array into a string appropriate for inclusion in filenames
80 *
81 * @param $params array Array of parameters that have been through normaliseParams.
82 * @return String
83 */
84 abstract function makeParamString( $params );
85
86 /**
87 * Parse a param string made with makeParamString back into an array
88 *
89 * @param $str string The parameter string without file name (e.g. 122px)
90 * @return Array|Boolean Array of parameters or false on failure.
91 */
92 abstract function parseParamString( $str );
93
94 /**
95 * Changes the parameter array as necessary, ready for transformation.
96 * Should be idempotent.
97 * Returns false if the parameters are unacceptable and the transform should fail
98 * @param $image
99 * @param $params
100 */
101 abstract function normaliseParams( $image, &$params );
102
103 /**
104 * Get an image size array like that returned by getimagesize(), or false if it
105 * can't be determined.
106 *
107 * @param $image File: the image object, or false if there isn't one
108 * @param string $path the filename
109 * @return Array Follow the format of PHP getimagesize() internal function. See http://www.php.net/getimagesize
110 */
111 abstract function getImageSize( $image, $path );
112
113 /**
114 * Get handler-specific metadata which will be saved in the img_metadata field.
115 *
116 * @param $image File: the image object, or false if there isn't one.
117 * Warning, FSFile::getPropsFromPath might pass an (object)array() instead (!)
118 * @param string $path the filename
119 * @return String
120 */
121 function getMetadata( $image, $path ) {
122 return '';
123 }
124
125 /**
126 * Get metadata version.
127 *
128 * This is not used for validating metadata, this is used for the api when returning
129 * metadata, since api content formats should stay the same over time, and so things
130 * using ForiegnApiRepo can keep backwards compatibility
131 *
132 * All core media handlers share a common version number, and extensions can
133 * use the GetMetadataVersion hook to append to the array (they should append a unique
134 * string so not to get confusing). If there was a media handler named 'foo' with metadata
135 * version 3 it might add to the end of the array the element 'foo=3'. if the core metadata
136 * version is 2, the end version string would look like '2;foo=3'.
137 *
138 * @return string version string
139 */
140 static function getMetadataVersion() {
141 $version = Array( '2' ); // core metadata version
142 wfRunHooks( 'GetMetadataVersion', Array( &$version ) );
143 return implode( ';', $version );
144 }
145
146 /**
147 * Convert metadata version.
148 *
149 * By default just returns $metadata, but can be used to allow
150 * media handlers to convert between metadata versions.
151 *
152 * @param $metadata Mixed String or Array metadata array (serialized if string)
153 * @param $version Integer target version
154 * @return Array serialized metadata in specified version, or $metadata on fail.
155 */
156 function convertMetadataVersion( $metadata, $version = 1 ) {
157 if ( !is_array( $metadata ) ) {
158
159 //unserialize to keep return parameter consistent.
160 wfSuppressWarnings();
161 $ret = unserialize( $metadata );
162 wfRestoreWarnings();
163 return $ret;
164 }
165 return $metadata;
166 }
167
168 /**
169 * Get a string describing the type of metadata, for display purposes.
170 *
171 * @return string
172 */
173 function getMetadataType( $image ) {
174 return false;
175 }
176
177 /**
178 * Check if the metadata string is valid for this handler.
179 * If it returns MediaHandler::METADATA_BAD (or false), Image
180 * will reload the metadata from the file and update the database.
181 * MediaHandler::METADATA_GOOD for if the metadata is a-ok,
182 * MediaHanlder::METADATA_COMPATIBLE if metadata is old but backwards
183 * compatible (which may or may not trigger a metadata reload).
184 * @return bool
185 */
186 function isMetadataValid( $image, $metadata ) {
187 return self::METADATA_GOOD;
188 }
189
190 /**
191 * Get a MediaTransformOutput object representing an alternate of the transformed
192 * output which will call an intermediary thumbnail assist script.
193 *
194 * Used when the repository has a thumbnailScriptUrl option configured.
195 *
196 * Return false to fall back to the regular getTransform().
197 * @return bool
198 */
199 function getScriptedTransform( $image, $script, $params ) {
200 return false;
201 }
202
203 /**
204 * Get a MediaTransformOutput object representing the transformed output. Does not
205 * actually do the transform.
206 *
207 * @param $image File: the image object
208 * @param string $dstPath filesystem destination path
209 * @param string $dstUrl Destination URL to use in output HTML
210 * @param array $params Arbitrary set of parameters validated by $this->validateParam()
211 * @return MediaTransformOutput
212 */
213 final function getTransform( $image, $dstPath, $dstUrl, $params ) {
214 return $this->doTransform( $image, $dstPath, $dstUrl, $params, self::TRANSFORM_LATER );
215 }
216
217 /**
218 * Get a MediaTransformOutput object representing the transformed output. Does the
219 * transform unless $flags contains self::TRANSFORM_LATER.
220 *
221 * @param $image File: the image object
222 * @param string $dstPath filesystem destination path
223 * @param string $dstUrl destination URL to use in output HTML
224 * @param array $params arbitrary set of parameters validated by $this->validateParam()
225 * Note: These parameters have *not* gone through $this->normaliseParams()
226 * @param $flags Integer: a bitfield, may contain self::TRANSFORM_LATER
227 *
228 * @return MediaTransformOutput
229 */
230 abstract function doTransform( $image, $dstPath, $dstUrl, $params, $flags = 0 );
231
232 /**
233 * Get the thumbnail extension and MIME type for a given source MIME type
234 *
235 * @param String $ext Extension of original file
236 * @param String $mime Mime type of original file
237 * @param Array $params Handler specific rendering parameters
238 * @return array thumbnail extension and MIME type
239 */
240 function getThumbType( $ext, $mime, $params = null ) {
241 $magic = MimeMagic::singleton();
242 if ( !$ext || $magic->isMatchingExtension( $ext, $mime ) === false ) {
243 // The extension is not valid for this mime type and we do
244 // recognize the mime type
245 $extensions = $magic->getExtensionsForType( $mime );
246 if ( $extensions ) {
247 return array( strtok( $extensions, ' ' ), $mime );
248 }
249 }
250
251 // The extension is correct (true) or the mime type is unknown to
252 // MediaWiki (null)
253 return array( $ext, $mime );
254 }
255
256 /**
257 * Get useful response headers for GET/HEAD requests for a file with the given metadata
258 * @param $metadata mixed Result of the getMetadata() function of this handler for a file
259 * @return Array
260 */
261 public function getStreamHeaders( $metadata ) {
262 return array();
263 }
264
265 /**
266 * True if the handled types can be transformed
267 * @return bool
268 */
269 function canRender( $file ) {
270 return true;
271 }
272
273 /**
274 * True if handled types cannot be displayed directly in a browser
275 * but can be rendered
276 * @return bool
277 */
278 function mustRender( $file ) {
279 return false;
280 }
281
282 /**
283 * True if the type has multi-page capabilities
284 * @return bool
285 */
286 function isMultiPage( $file ) {
287 return false;
288 }
289
290 /**
291 * Page count for a multi-page document, false if unsupported or unknown
292 * @return bool
293 */
294 function pageCount( $file ) {
295 return false;
296 }
297
298 /**
299 * The material is vectorized and thus scaling is lossless
300 * @return bool
301 */
302 function isVectorized( $file ) {
303 return false;
304 }
305
306 /**
307 * The material is an image, and is animated.
308 * In particular, video material need not return true.
309 * @note Before 1.20, this was a method of ImageHandler only
310 * @return bool
311 */
312 function isAnimatedImage( $file ) {
313 return false;
314 }
315
316 /**
317 * If the material is animated, we can animate the thumbnail
318 * @since 1.20
319 * @return bool If material is not animated, handler may return any value.
320 */
321 function canAnimateThumbnail( $file ) {
322 return true;
323 }
324
325 /**
326 * False if the handler is disabled for all files
327 * @return bool
328 */
329 function isEnabled() {
330 return true;
331 }
332
333 /**
334 * Get an associative array of page dimensions
335 * Currently "width" and "height" are understood, but this might be
336 * expanded in the future.
337 * Returns false if unknown.
338 *
339 * It is expected that handlers for paged media (e.g. DjVuHandler)
340 * will override this method so that it gives the correct results
341 * for each specific page of the file, using the $page argument.
342 *
343 * @note For non-paged media, use getImageSize.
344 *
345 * @param $image File
346 * @param $page What page to get dimensions of
347 * @return array|bool
348 */
349 function getPageDimensions( $image, $page ) {
350 $gis = $this->getImageSize( $image, $image->getLocalRefPath() );
351 if ( $gis ) {
352 return array(
353 'width' => $gis[0],
354 'height' => $gis[1]
355 );
356 } else {
357 return false;
358 }
359 }
360
361 /**
362 * Generic getter for text layer.
363 * Currently overloaded by PDF and DjVu handlers
364 * @return bool
365 */
366 function getPageText( $image, $page ) {
367 return false;
368 }
369
370 /**
371 * Get an array structure that looks like this:
372 *
373 * array(
374 * 'visible' => array(
375 * 'Human-readable name' => 'Human readable value',
376 * ...
377 * ),
378 * 'collapsed' => array(
379 * 'Human-readable name' => 'Human readable value',
380 * ...
381 * )
382 * )
383 * The UI will format this into a table where the visible fields are always
384 * visible, and the collapsed fields are optionally visible.
385 *
386 * The function should return false if there is no metadata to display.
387 */
388
389 /**
390 * @todo FIXME: I don't really like this interface, it's not very flexible
391 * I think the media handler should generate HTML instead. It can do
392 * all the formatting according to some standard. That makes it possible
393 * to do things like visual indication of grouped and chained streams
394 * in ogg container files.
395 * @return bool
396 */
397 function formatMetadata( $image ) {
398 return false;
399 }
400
401 /** sorts the visible/invisible field.
402 * Split off from ImageHandler::formatMetadata, as used by more than
403 * one type of handler.
404 *
405 * This is used by the media handlers that use the FormatMetadata class
406 *
407 * @param array $metadataArray metadata array
408 * @return array for use displaying metadata.
409 */
410 function formatMetadataHelper( $metadataArray ) {
411 $result = array(
412 'visible' => array(),
413 'collapsed' => array()
414 );
415
416 $formatted = FormatMetadata::getFormattedData( $metadataArray );
417 // Sort fields into visible and collapsed
418 $visibleFields = $this->visibleMetadataFields();
419 foreach ( $formatted as $name => $value ) {
420 $tag = strtolower( $name );
421 self::addMeta( $result,
422 in_array( $tag, $visibleFields ) ? 'visible' : 'collapsed',
423 'exif',
424 $tag,
425 $value
426 );
427 }
428 return $result;
429 }
430
431 /**
432 * Get a list of metadata items which should be displayed when
433 * the metadata table is collapsed.
434 *
435 * @return array of strings
436 * @access protected
437 */
438 function visibleMetadataFields() {
439 $fields = array();
440 $lines = explode( "\n", wfMessage( 'metadata-fields' )->inContentLanguage()->text() );
441 foreach ( $lines as $line ) {
442 $matches = array();
443 if ( preg_match( '/^\\*\s*(.*?)\s*$/', $line, $matches ) ) {
444 $fields[] = $matches[1];
445 }
446 }
447 $fields = array_map( 'strtolower', $fields );
448 return $fields;
449 }
450
451 /**
452 * This is used to generate an array element for each metadata value
453 * That array is then used to generate the table of metadata values
454 * on the image page
455 *
456 * @param &$array Array An array containing elements for each type of visibility
457 * and each of those elements being an array of metadata items. This function adds
458 * a value to that array.
459 * @param string $visibility ('visible' or 'collapsed') if this value is hidden
460 * by default.
461 * @param string $type type of metadata tag (currently always 'exif')
462 * @param string $id the name of the metadata tag (like 'artist' for example).
463 * its name in the table displayed is the message "$type-$id" (Ex exif-artist ).
464 * @param string $value thingy goes into a wikitext table; it used to be escaped but
465 * that was incompatible with previous practise of customized display
466 * with wikitext formatting via messages such as 'exif-model-value'.
467 * So the escaping is taken back out, but generally this seems a confusing
468 * interface.
469 * @param string $param value to pass to the message for the name of the field
470 * as $1. Currently this parameter doesn't seem to ever be used.
471 *
472 * Note, everything here is passed through the parser later on (!)
473 */
474 protected static function addMeta( &$array, $visibility, $type, $id, $value, $param = false ) {
475 $msg = wfMessage( "$type-$id", $param );
476 if ( $msg->exists() ) {
477 $name = $msg->text();
478 } else {
479 // This is for future compatibility when using instant commons.
480 // So as to not display as ugly a name if a new metadata
481 // property is defined that we don't know about
482 // (not a major issue since such a property would be collapsed
483 // by default).
484 wfDebug( __METHOD__ . ' Unknown metadata name: ' . $id . "\n" );
485 $name = wfEscapeWikiText( $id );
486 }
487 $array[$visibility][] = array(
488 'id' => "$type-$id",
489 'name' => $name,
490 'value' => $value
491 );
492 }
493
494 /**
495 * Used instead of getLongDesc if there is no handler registered for file.
496 *
497 * @param $file File
498 * @return string
499 */
500 function getShortDesc( $file ) {
501 global $wgLang;
502 return htmlspecialchars( $wgLang->formatSize( $file->getSize() ) );
503 }
504
505 /**
506 * Short description. Shown on Special:Search results.
507 *
508 * @param $file File
509 * @return string
510 */
511 function getLongDesc( $file ) {
512 global $wgLang;
513 return wfMessage( 'file-info', htmlspecialchars( $wgLang->formatSize( $file->getSize() ) ),
514 $file->getMimeType() )->parse();
515 }
516
517 /**
518 * Long description. Shown under image on image description page surounded by ().
519 *
520 * @param $file File
521 * @return string
522 */
523 static function getGeneralShortDesc( $file ) {
524 global $wgLang;
525 return $wgLang->formatSize( $file->getSize() );
526 }
527
528 /**
529 * Used instead of getShortDesc if there is no handler registered for file.
530 *
531 * @param $file File
532 * @return string
533 */
534 static function getGeneralLongDesc( $file ) {
535 global $wgLang;
536 return wfMessage( 'file-info', $wgLang->formatSize( $file->getSize() ),
537 $file->getMimeType() )->parse();
538 }
539
540 /**
541 * Calculate the largest thumbnail width for a given original file size
542 * such that the thumbnail's height is at most $maxHeight.
543 * @param $boxWidth Integer Width of the thumbnail box.
544 * @param $boxHeight Integer Height of the thumbnail box.
545 * @param $maxHeight Integer Maximum height expected for the thumbnail.
546 * @return Integer.
547 */
548 public static function fitBoxWidth( $boxWidth, $boxHeight, $maxHeight ) {
549 $idealWidth = $boxWidth * $maxHeight / $boxHeight;
550 $roundedUp = ceil( $idealWidth );
551 if ( round( $roundedUp * $boxHeight / $boxWidth ) > $maxHeight ) {
552 return floor( $idealWidth );
553 } else {
554 return $roundedUp;
555 }
556 }
557
558 /**
559 * Shown in file history box on image description page.
560 *
561 * @param File $file
562 * @return String Dimensions
563 */
564 function getDimensionsString( $file ) {
565 return '';
566 }
567
568 /**
569 * Modify the parser object post-transform.
570 *
571 * This is often used to do $parser->addOutputHook(),
572 * in order to add some javascript to render a viewer.
573 * See TimedMediaHandler or OggHandler for an example.
574 *
575 * @param Parser $parser
576 * @param File $file
577 */
578 function parserTransformHook( $parser, $file ) {}
579
580 /**
581 * File validation hook called on upload.
582 *
583 * If the file at the given local path is not valid, or its MIME type does not
584 * match the handler class, a Status object should be returned containing
585 * relevant errors.
586 *
587 * @param string $fileName The local path to the file.
588 * @return Status object
589 */
590 function verifyUpload( $fileName ) {
591 return Status::newGood();
592 }
593
594 /**
595 * Check for zero-sized thumbnails. These can be generated when
596 * no disk space is available or some other error occurs
597 *
598 * @param string $dstPath The location of the suspect file
599 * @param int $retval Return value of some shell process, file will be deleted if this is non-zero
600 * @return bool True if removed, false otherwise
601 */
602 function removeBadFile( $dstPath, $retval = 0 ) {
603 if ( file_exists( $dstPath ) ) {
604 $thumbstat = stat( $dstPath );
605 if ( $thumbstat['size'] == 0 || $retval != 0 ) {
606 $result = unlink( $dstPath );
607
608 if ( $result ) {
609 wfDebugLog( 'thumbnail',
610 sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() succeeded',
611 $thumbstat['size'], $dstPath ) );
612 } else {
613 wfDebugLog( 'thumbnail',
614 sprintf( 'Removing bad %d-byte thumbnail "%s". unlink() failed',
615 $thumbstat['size'], $dstPath ) );
616 }
617 return true;
618 }
619 }
620 return false;
621 }
622
623 /**
624 * Remove files from the purge list.
625 *
626 * This is used by some video handlers to prevent ?action=purge
627 * from removing a transcoded video, which is expensive to
628 * regenerate.
629 *
630 * @see LocalFile::purgeThumbnails
631 *
632 * @param array $files
633 * @param array $options Purge options. Currently will always be
634 * an array with a single key 'forThumbRefresh' set to true.
635 */
636 public function filterThumbnailPurgeList( &$files, $options ) {
637 // Do nothing
638 }
639
640 /*
641 * True if the handler can rotate the media
642 * @since 1.21
643 * @return bool
644 */
645 public static function canRotate() {
646 return false;
647 }
648
649 /**
650 * On supporting image formats, try to read out the low-level orientation
651 * of the file and return the angle that the file needs to be rotated to
652 * be viewed.
653 *
654 * This information is only useful when manipulating the original file;
655 * the width and height we normally work with is logical, and will match
656 * any produced output views.
657 *
658 * For files we don't know, we return 0.
659 *
660 * @param $file File
661 * @return int 0, 90, 180 or 270
662 */
663 public function getRotation( $file ) {
664 return 0;
665 }
666
667 }