4 * Base file class. Do not instantiate.
6 * Implements some public methods and some protected utility functions which
7 * are required by multiple child classes. Contains stub functionality for
8 * unimplemented public methods.
10 * Stub functions which should be overridden are marked with STUB. Some more
11 * concrete functions are also typically overridden by child classes.
14 * NOTE FOR WINDOWS USERS:
15 * To enable EXIF functions, add the folloing lines to the
16 * "Windows extensions" section of php.ini:
18 * extension=extensions/php_mbstring.dll
19 * extension=extensions/php_exif.dll
21 * @addtogroup FileRepo
24 const DELETED_FILE
= 1;
25 const DELETED_COMMENT
= 2;
26 const DELETED_USER
= 4;
27 const DELETED_RESTRICTED
= 8;
30 const DELETE_SOURCE
= 1;
33 * Some member variables can be lazy-initialised using __get(). The
34 * initialisation function for these variables is always a function named
35 * like getVar(), where Var is the variable name with upper-case first
38 * The following variables are initialised in this way in this base class:
39 * name, extension, handler, path, canRender, isSafeFile,
40 * transformScript, hashPath, pageCount, url
42 * Code within this class should generally use the accessor function
43 * directly, since __get() isn't re-entrant and therefore causes bugs that
44 * depend on initialisation order.
48 * The following member variables are not lazy-initialised
50 var $repo, $title, $lastError;
52 function __construct( $title, $repo ) {
53 $this->title
= $title;
57 function __get( $name ) {
58 $function = array( $this, 'get' . ucfirst( $name ) );
59 if ( !is_callable( $function ) ) {
62 $this->$name = call_user_func( $function );
68 * Normalize a file extension to the common form, and ensure it's clean.
69 * Extensions with non-alphanumeric characters will be discarded.
71 * @param $ext string (without the .)
74 static function normalizeExtension( $ext ) {
75 $lower = strtolower( $ext );
81 if( isset( $squish[$lower] ) ) {
82 return $squish[$lower];
83 } elseif( preg_match( '/^[0-9a-z]+$/', $lower ) ) {
91 * Upgrade the database row if there is one
95 function upgradeRow() {}
98 * Split an internet media type into its two components; if not
99 * a two-part name, set the minor type to 'unknown'.
101 * @param $mime "text/html" etc
102 * @return array ("text", "html") etc
104 static function splitMime( $mime ) {
105 if( strpos( $mime, '/' ) !== false ) {
106 return explode( '/', $mime, 2 );
108 return array( $mime, 'unknown' );
113 * Return the name of this file
117 if ( !isset( $this->name
) ) {
118 $this->name
= $this->title
->getDBkey();
124 * Get the file extension, e.g. "svg"
126 function getExtension() {
127 if ( !isset( $this->extension
) ) {
128 $n = strrpos( $this->getName(), '.' );
129 $this->extension
= self
::normalizeExtension(
130 $n ?
substr( $this->getName(), $n +
1 ) : '' );
132 return $this->extension
;
136 * Return the associated title object
139 function getTitle() { return $this->title
; }
142 * Return the URL of the file
146 if ( !isset( $this->url
) ) {
147 $this->url
= $this->repo
->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
152 function getViewURL() {
153 if( $this->mustRender()) {
154 if( $this->canRender() ) {
155 return $this->createThumb( $this->getWidth() );
158 wfDebug(__METHOD__
.': supposed to render '.$this->getName().' ('.$this->getMimeType()."), but can't!\n");
159 return $this->getURL(); #hm... return NULL?
162 return $this->getURL();
167 * Return the full filesystem path to the file. Note that this does
168 * not mean that a file actually exists under that location.
170 * This path depends on whether directory hashing is active or not,
171 * i.e. whether the files are all found in the same directory,
172 * or in hashed paths like /images/3/3c.
174 * May return false if the file is not locally accessible.
179 if ( !isset( $this->path
) ) {
180 $this->path
= $this->repo
->getZonePath('public') . '/' . $this->getRel();
186 * Alias for getPath()
189 function getFullPath() {
190 return $this->getPath();
194 * Return the width of the image. Returns false if the width is unknown
198 * Overridden by LocalFile, UnregisteredLocalFile
201 function getWidth( $page = 1 ) { return false; }
204 * Return the height of the image. Returns false if the height is unknown
208 * Overridden by LocalFile, UnregisteredLocalFile
211 function getHeight( $page = 1 ) { return false; }
214 * Get handler-specific metadata
215 * Overridden by LocalFile, UnregisteredLocalFile
218 function getMetadata() { return false; }
221 * Return the size of the image file, in bytes
222 * Overridden by LocalFile, UnregisteredLocalFile
226 function getSize() { return false; }
229 * Returns the mime type of the file.
230 * Overridden by LocalFile, UnregisteredLocalFile
233 function getMimeType() { return 'unknown/unknown'; }
236 * Return the type of the media in the file.
237 * Use the value returned by this function with the MEDIATYPE_xxx constants.
238 * Overridden by LocalFile,
241 function getMediaType() { return MEDIATYPE_UNKNOWN
; }
244 * Checks if the file can be presented to the browser as a bitmap.
246 * Currently, this checks if the file is an image format
247 * that can be converted to a format
248 * supported by all browsers (namely GIF, PNG and JPEG),
249 * or if it is an SVG image and SVG conversion is enabled.
251 function canRender() {
252 if ( !isset( $this->canRender
) ) {
253 $this->canRender
= $this->getHandler() && $this->handler
->canRender();
255 return $this->canRender
;
259 * Accessor for __get()
261 protected function getCanRender() {
262 return $this->canRender();
266 * Return true if the file is of a type that can't be directly
267 * rendered by typical browsers and needs to be re-rasterized.
269 * This returns true for everything but the bitmap types
270 * supported by all browsers, i.e. JPEG; GIF and PNG. It will
271 * also return true for any non-image formats.
275 function mustRender() {
276 return $this->getHandler() && $this->handler
->mustRender();
280 * Determines if this media file may be shown inline on a page.
282 * This is currently synonymous to canRender(), but this could be
283 * extended to also allow inline display of other media,
284 * like flash animations or videos. If you do so, please keep in mind that
285 * that could be a security risk.
287 function allowInlineDisplay() {
288 return $this->canRender();
292 * Determines if this media file is in a format that is unlikely to
293 * contain viruses or malicious content. It uses the global
294 * $wgTrustedMediaFormats list to determine if the file is safe.
296 * This is used to show a warning on the description page of non-safe files.
297 * It may also be used to disallow direct [[media:...]] links to such files.
299 * Note that this function will always return true if allowInlineDisplay()
300 * or isTrustedFile() is true for this file.
302 function isSafeFile() {
303 if ( !isset( $this->isSafeFile
) ) {
304 $this->isSafeFile
= $this->_getIsSafeFile();
306 return $this->isSafeFile
;
309 /** Accessor for __get() */
310 protected function getIsSafeFile() {
311 return $this->isSafeFile();
314 /** Uncached accessor */
315 protected function _getIsSafeFile() {
316 if ($this->allowInlineDisplay()) return true;
317 if ($this->isTrustedFile()) return true;
319 global $wgTrustedMediaFormats;
321 $type= $this->getMediaType();
322 $mime= $this->getMimeType();
323 #wfDebug("LocalFile::isSafeFile: type= $type, mime= $mime\n");
325 if (!$type ||
$type===MEDIATYPE_UNKNOWN
) return false; #unknown type, not trusted
326 if ( in_array( $type, $wgTrustedMediaFormats) ) return true;
328 if ($mime==="unknown/unknown") return false; #unknown type, not trusted
329 if ( in_array( $mime, $wgTrustedMediaFormats) ) return true;
334 /** Returns true if the file is flagged as trusted. Files flagged that way
335 * can be linked to directly, even if that is not allowed for this type of
338 * This is a dummy function right now and always returns false. It could be
339 * implemented to extract a flag from the database. The trusted flag could be
340 * set on upload, if the user has sufficient privileges, to bypass script-
341 * and html-filters. It may even be coupled with cryptographics signatures
344 function isTrustedFile() {
345 #this could be implemented to check a flag in the databas,
346 #look for signatures, etc
351 * Returns true if file exists in the repository.
353 * Overridden by LocalFile to avoid unnecessary stat calls.
355 * @return boolean Whether file exists in the repository.
359 return $this->getPath() && file_exists( $this->path
);
362 function getTransformScript() {
363 if ( !isset( $this->transformScript
) ) {
364 $this->transformScript
= false;
366 $script = $this->repo
->getThumbScriptUrl();
368 $this->transformScript
= "$script?f=" . urlencode( $this->getName() );
372 return $this->transformScript
;
376 * Get a ThumbnailImage which is the same size as the source
378 function getUnscaledThumb( $page = false ) {
379 $width = $this->getWidth( $page );
381 return $this->iconThumb();
386 'width' => $this->getWidth( $page )
389 $params = array( 'width' => $this->getWidth() );
391 return $this->transform( $params );
395 * Return the file name of a thumbnail with the specified parameters
397 * @param array $params Handler-specific parameters
400 function thumbName( $params ) {
401 if ( !$this->getHandler() ) {
404 $extension = $this->getExtension();
405 list( $thumbExt, $thumbMime ) = $this->handler
->getThumbType( $extension, $this->getMimeType() );
406 $thumbName = $this->handler
->makeParamString( $params ) . '-' . $this->getName();
407 if ( $thumbExt != $extension ) {
408 $thumbName .= ".$thumbExt";
414 * Create a thumbnail of the image having the specified width/height.
415 * The thumbnail will not be created if the width is larger than the
416 * image's width. Let the browser do the scaling in this case.
417 * The thumbnail is stored on disk and is only computed if the thumbnail
418 * file does not exist OR if it is older than the image.
421 * Keeps aspect ratio of original image. If both width and height are
422 * specified, the generated image will be no bigger than width x height,
423 * and will also have correct aspect ratio.
425 * @param integer $width maximum width of the generated thumbnail
426 * @param integer $height maximum height of the image (optional)
429 function createThumb( $width, $height = -1 ) {
430 $params = array( 'width' => $width );
431 if ( $height != -1 ) {
432 $params['height'] = $height;
434 $thumb = $this->transform( $params );
435 if( is_null( $thumb ) ||
$thumb->isError() ) return '';
436 return $thumb->getUrl();
440 * As createThumb, but returns a ThumbnailImage object. This can
441 * provide access to the actual file, the real size of the thumb,
442 * and can produce a convenient <img> tag for you.
444 * For non-image formats, this may return a filetype-specific icon.
446 * @param integer $width maximum width of the generated thumbnail
447 * @param integer $height maximum height of the image (optional)
448 * @param boolean $render True to render the thumbnail if it doesn't exist,
449 * false to just return the URL
451 * @return ThumbnailImage or null on failure
454 * @deprecated use transform()
456 function getThumbnail( $width, $height=-1, $render = true ) {
457 $params = array( 'width' => $width );
458 if ( $height != -1 ) {
459 $params['height'] = $height;
461 $flags = $render ? self
::RENDER_NOW
: 0;
462 return $this->transform( $params, $flags );
466 * Transform a media file
468 * @param array $params An associative array of handler-specific parameters. Typical
469 * keys are width, height and page.
470 * @param integer $flags A bitfield, may contain self::RENDER_NOW to force rendering
471 * @return MediaTransformOutput
473 function transform( $params, $flags = 0 ) {
474 global $wgUseSquid, $wgIgnoreImageErrors;
476 wfProfileIn( __METHOD__
);
478 if ( !$this->getHandler() ||
!$this->handler
->canRender() ) {
479 // not a bitmap or renderable image, don't try.
480 $thumb = $this->iconThumb();
484 $script = $this->getTransformScript();
485 if ( $script && !($flags & self
::RENDER_NOW
) ) {
486 // Use a script to transform on client request
487 $thumb = $this->handler
->getScriptedTransform( $this, $script, $params );
491 $normalisedParams = $params;
492 $this->handler
->normaliseParams( $this, $normalisedParams );
493 $thumbName = $this->thumbName( $normalisedParams );
494 $thumbPath = $this->getThumbPath( $thumbName );
495 $thumbUrl = $this->getThumbUrl( $thumbName );
497 if ( $this->repo
->canTransformVia404() && !($flags & self
::RENDER_NOW
) ) {
498 $thumb = $this->handler
->getTransform( $this, $thumbPath, $thumbUrl, $params );
502 wfDebug( "Doing stat for $thumbPath\n" );
503 $this->migrateThumbFile( $thumbName );
504 if ( file_exists( $thumbPath ) ) {
505 $thumb = $this->handler
->getTransform( $this, $thumbPath, $thumbUrl, $params );
508 $thumb = $this->handler
->doTransform( $this, $thumbPath, $thumbUrl, $params );
510 // Ignore errors if requested
513 } elseif ( $thumb->isError() ) {
514 $this->lastError
= $thumb->toText();
515 if ( $wgIgnoreImageErrors && !($flags & self
::RENDER_NOW
) ) {
516 $thumb = $this->handler
->getTransform( $this, $thumbPath, $thumbUrl, $params );
521 wfPurgeSquidServers( array( $thumbUrl ) );
525 wfProfileOut( __METHOD__
);
530 * Hook into transform() to allow migration of thumbnail files
532 * Overridden by LocalFile
534 function migrateThumbFile() {}
537 * Get a MediaHandler instance for this file
539 function getHandler() {
540 if ( !isset( $this->handler
) ) {
541 $this->handler
= MediaHandler
::getHandler( $this->getMimeType() );
543 return $this->handler
;
547 * Get a ThumbnailImage representing a file type icon
548 * @return ThumbnailImage
550 function iconThumb() {
551 global $wgStylePath, $wgStyleDirectory;
553 $try = array( 'fileicon-' . $this->getExtension() . '.png', 'fileicon.png' );
554 foreach( $try as $icon ) {
555 $path = '/common/images/icons/' . $icon;
556 $filepath = $wgStyleDirectory . $path;
557 if( file_exists( $filepath ) ) {
558 return new ThumbnailImage( $wgStylePath . $path, 120, 120 );
565 * Get last thumbnailing error.
568 function getLastError() {
569 return $this->lastError
;
573 * Get all thumbnail names previously generated for this file
575 * Overridden by LocalFile
577 function getThumbnails() { return array(); }
580 * Purge shared caches such as thumbnails and DB data caching
582 * Overridden by LocalFile
584 function purgeCache( $archiveFiles = array() ) {}
587 * Purge the file description page, but don't go after
588 * pages using the file. Use when modifying file history
589 * but not the current data.
591 function purgeDescription() {
592 $title = $this->getTitle();
594 $title->invalidateCache();
595 $title->purgeSquid();
600 * Purge metadata and all affected pages when the file is created,
601 * deleted, or majorly updated. A set of additional URLs may be
602 * passed to purge, such as specific file files which have changed.
603 * @param $urlArray array
605 function purgeEverything( $urlArr=array() ) {
606 // Delete thumbnails and refresh file metadata cache
608 $this->purgeDescription();
610 // Purge cache of all pages using this file
611 $title = $this->getTitle();
613 $update = new HTMLCacheUpdate( $title, 'imagelinks' );
619 * Return the history of this file, line by line. Starts with current version,
620 * then old versions. Should return an object similar to an image/oldimage
625 * Overridden in LocalFile
627 function nextHistoryLine() {
632 * Reset the history pointer to the first element of the history
635 * Overridden in LocalFile.
637 function resetHistory() {}
640 * Get the filename hash component of the directory including trailing slash,
642 * If the repository is not hashed, returns an empty string.
644 function getHashPath() {
645 if ( !isset( $this->hashPath
) ) {
646 $this->hashPath
= $this->repo
->getHashPath( $this->getName() );
648 return $this->hashPath
;
652 * Get the path of the file relative to the public zone root
655 return $this->getHashPath() . $this->getName();
659 * Get urlencoded relative path of the file
661 function getUrlRel() {
662 return $this->getHashPath() . urlencode( $this->getName() );
665 /** Get the path of the archive directory, or a particular file if $suffix is specified */
666 function getArchivePath( $suffix = false ) {
667 $path = $this->repo
->getZonePath('public') . '/archive/' . $this->getHashPath();
668 if ( $suffix !== false ) {
669 $path .= '/' . $suffix;
674 /** Get the path of the thumbnail directory, or a particular file if $suffix is specified */
675 function getThumbPath( $suffix = false ) {
676 $path = $this->repo
->getZonePath('public') . '/thumb/' . $this->getRel();
677 if ( $suffix !== false ) {
678 $path .= '/' . $suffix;
683 /** Get the URL of the archive directory, or a particular file if $suffix is specified */
684 function getArchiveUrl( $suffix = false ) {
685 $path = $this->repo
->getZoneUrl('public') . '/archive/' . $this->getHashPath();
686 if ( $suffix !== false ) {
687 $path .= '/' . urlencode( $suffix );
692 /** Get the URL of the thumbnail directory, or a particular file if $suffix is specified */
693 function getThumbUrl( $suffix = false ) {
694 $path = $this->repo
->getZoneUrl('public') . '/thumb/' . $this->getUrlRel();
695 if ( $suffix !== false ) {
696 $path .= '/' . urlencode( $suffix );
701 /** Get the virtual URL for an archive file or directory */
702 function getArchiveVirtualUrl( $suffix = false ) {
703 $path = $this->repo
->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
704 if ( $suffix !== false ) {
705 $path .= '/' . urlencode( $suffix );
710 /** Get the virtual URL for a thumbnail file or directory */
711 function getThumbVirtualUrl( $suffix = false ) {
712 $path = $this->repo
->getVirtualUrl() . '/public/thumb/' . $this->getHashPath();
713 if ( $suffix !== false ) {
714 $path .= '/' . urlencode( $suffix );
722 function isHashed() {
723 return $this->repo
->isHashed();
726 function readOnlyError() {
727 throw new MWException( get_class($this) . ': write operations are not supported' );
731 * Record a file upload in the upload log and the image table
733 * Overridden by LocalFile
735 function recordUpload( $oldver, $desc, $license = '', $copyStatus = '', $source = '', $watch = false ) {
736 $this->readOnlyError();
740 * Move or copy a file to its public location. If a file exists at the
741 * destination, move it to an archive. Returns the archive name on success
742 * or an empty string if it was a new file, and a wikitext-formatted
743 * WikiError object on failure.
745 * The archive name should be passed through to recordUpload for database
748 * @param string $sourcePath Local filesystem path to the source image
749 * @param integer $flags A bitwise combination of:
750 * File::DELETE_SOURCE Delete the source file, i.e. move
752 * @return The archive name on success or an empty string if it was a new
753 * file, and a wikitext-formatted WikiError object on failure.
756 * Overridden by LocalFile
758 function publish( $srcPath, $flags = 0 ) {
759 $this->readOnlyError();
763 * Get an array of Title objects which are articles which use this file
764 * Also adds their IDs to the link cache
766 * This is mostly copied from Title::getLinksTo()
768 * @deprecated Use HTMLCacheUpdate, this function uses too much memory
770 function getLinksTo( $options = '' ) {
771 wfProfileIn( __METHOD__
);
773 // Note: use local DB not repo DB, we want to know local links
775 $db = wfGetDB( DB_MASTER
);
777 $db = wfGetDB( DB_SLAVE
);
779 $linkCache =& LinkCache
::singleton();
781 list( $page, $imagelinks ) = $db->tableNamesN( 'page', 'imagelinks' );
782 $encName = $db->addQuotes( $this->getName() );
783 $sql = "SELECT page_namespace,page_title,page_id FROM $page,$imagelinks WHERE page_id=il_from AND il_to=$encName $options";
784 $res = $db->query( $sql, __METHOD__
);
787 if ( $db->numRows( $res ) ) {
788 while ( $row = $db->fetchObject( $res ) ) {
789 if ( $titleObj = Title
::makeTitle( $row->page_namespace
, $row->page_title
) ) {
790 $linkCache->addGoodLinkObj( $row->page_id
, $titleObj );
791 $retVal[] = $titleObj;
795 $db->freeResult( $res );
796 wfProfileOut( __METHOD__
);
800 function getExifData() {
801 if ( !$this->getHandler() ||
$this->handler
->getMetadataType( $this ) != 'exif' ) {
804 $metadata = $this->getMetadata();
808 $exif = unserialize( $metadata );
812 unset( $exif['MEDIAWIKI_EXIF_VERSION'] );
813 $format = new FormatExif( $exif );
815 return $format->getFormattedData();
819 * Returns true if the file comes from the local file repository.
824 return $this->repo
&& $this->repo
->getName() == 'local';
828 * Returns true if the image is an old version
836 * Is this file a "deleted" file in a private archive?
839 function isDeleted( $field ) {
844 * Was this file ever deleted from the wiki?
848 function wasDeleted() {
849 $title = $this->getTitle();
850 return $title && $title->isDeleted() > 0;
854 * Delete all versions of the file.
856 * Moves the files into an archive directory (or deletes them)
857 * and removes the database rows.
859 * Cache purging is done; logging is caller's responsibility.
862 * @return true on success, false on some kind of failure
864 * Overridden by LocalFile
866 function delete( $reason, $suppress=false ) {
867 $this->readOnlyError();
871 * Restore all or specified deleted revisions to the given file.
872 * Permissions and logging are left to the caller.
874 * May throw database exceptions on error.
876 * @param $versions set of record ids of deleted items to restore,
877 * or empty to restore all revisions.
878 * @return the number of file revisions restored if successful,
879 * or false on failure
881 * Overridden by LocalFile
883 function restore( $versions=array(), $Unsuppress=false ) {
884 $this->readOnlyError();
888 * Returns 'true' if this image is a multipage document, e.g. a DJVU
893 function isMultipage() {
894 return $this->getHandler() && $this->handler
->isMultiPage();
898 * Returns the number of pages of a multipage document, or NULL for
899 * documents which aren't multipage documents
901 function pageCount() {
902 if ( !isset( $this->pageCount
) ) {
903 if ( $this->getHandler() && $this->handler
->isMultiPage() ) {
904 $this->pageCount
= $this->handler
->pageCount( $this );
906 $this->pageCount
= false;
909 return $this->pageCount
;
913 * Calculate the height of a thumbnail using the source and destination width
915 static function scaleHeight( $srcWidth, $srcHeight, $dstWidth ) {
916 // Exact integer multiply followed by division
917 if ( $srcWidth == 0 ) {
920 return round( $srcHeight * $dstWidth / $srcWidth );
925 * Get an image size array like that returned by getimagesize(), or false if it
926 * can't be determined.
928 * @param string $fileName The filename
931 function getImageSize( $fileName ) {
932 if ( !$this->getHandler() ) {
935 return $this->handler
->getImageSize( $this, $fileName );
939 * Get the URL of the image description page. May return false if it is
940 * unknown or not applicable.
942 function getDescriptionUrl() {
943 return $this->repo
->getDescriptionUrl( $this->getName() );
947 * Get the HTML text of the description page, if available
949 function getDescriptionText() {
950 if ( !$this->repo
->fetchDescription
) {
953 $renderUrl = $this->repo
->getDescriptionRenderUrl( $this->getName() );
955 wfDebug( "Fetching shared description from $renderUrl\n" );
956 return Http
::get( $renderUrl );
963 * Get the 14-character timestamp of the file upload, or false if
965 function getTimestmap() {
966 $path = $this->getPath();
967 if ( !file_exists( $path ) ) {
970 return wfTimestamp( filemtime( $path ) );
974 * Determine if the current user is allowed to view a particular
975 * field of this file, if it's marked as deleted.
980 function userCan( $field ) {