FU r102073:
[lhc/web/wiklou.git] / includes / filerepo / File.php
index d03016f..4dffbfe 100644 (file)
@@ -30,7 +30,14 @@ abstract class File {
        const DELETED_COMMENT = 2;
        const DELETED_USER = 4;
        const DELETED_RESTRICTED = 8;
-       const RENDER_NOW = 1;
+
+       /** Force rendering in the current process */
+       const RENDER_NOW   = 1;
+       /**
+        * Force rendering even if thumbnail already exist and using RENDER_NOW
+        * I.e. you have to pass both flags: File::RENDER_NOW | File::RENDER_FORCE 
+        */
+       const RENDER_FORCE = 2;
 
        const DELETE_SOURCE = 1;
 
@@ -54,12 +61,12 @@ abstract class File {
         */
 
        /**
-        * @var LocalRepo
+        * @var FileRepo|false
         */
        var $repo;
 
        /**
-        * @var Title
+        * @var Title|false
         */
        var $title;
 
@@ -70,14 +77,63 @@ abstract class File {
         */
        protected $handler;
 
+       protected $url, $extension, $name, $path, $hashPath, $pageCount, $transformScript;
+
        /**
-        * Call this constructor from child classes
+        * @var bool
+        */
+       protected $canRender, $isSafeFile;
+
+       /**
+        * @var string Required Repository class type
+        */
+       protected $repoClass = 'FileRepo';
+
+       /**
+        * Call this constructor from child classes.
+        * 
+        * Both $title and $repo are optional, though some functions
+        * may return false or throw exceptions if they are not set.
+        * Most subclasses will want to call assertRepoDefined() here.
+        *
+        * @param $title Title|string|false
+        * @param $repo FileRepo|false
         */
        function __construct( $title, $repo ) {
+               if ( $title !== false ) { // subclasses may not use MW titles
+                       $title = self::normalizeTitle( $title, 'exception' );
+               }
                $this->title = $title;
                $this->repo = $repo;
        }
 
+       /**
+        * Given a string or Title object return either a
+        * valid Title object with namespace NS_FILE or null
+        * @param $title Title|string
+        * @param $exception string|false Use 'exception' to throw an error on bad titles
+        * @return Title|null
+        */
+       static function normalizeTitle( $title, $exception = false ) {
+               $ret = $title;
+               if ( $ret instanceof Title ) {
+                       # Normalize NS_MEDIA -> NS_FILE
+                       if ( $ret->getNamespace() == NS_MEDIA ) {
+                               $ret = Title::makeTitleSafe( NS_FILE, $ret->getDBkey() );
+                       # Sanity check the title namespace
+                       } elseif ( $ret->getNamespace() !== NS_FILE ) {
+                               $ret = null;
+                       }
+               } else {
+                       # Convert strings to Title objects
+                       $ret = Title::makeTitleSafe( NS_FILE, (string)$ret );
+               }
+               if ( !$ret && $exception !== false ) {
+                       throw new MWException( "`$title` is not a valid file title." );
+               }
+               return $ret;
+       }
+
        function __get( $name ) {
                $function = array( $this, 'get' . ucfirst( $name ) );
                if ( !is_callable( $function ) ) {
@@ -117,7 +173,7 @@ abstract class File {
         *
         * @param $old File Old file
         * @param $new string New name
-        * 
+        *
         * @return bool|null
         */
        static function checkExtensionCompatibility( File $old, $new ) {
@@ -158,6 +214,7 @@ abstract class File {
         */
        public function getName() {
                if ( !isset( $this->name ) ) {
+                       $this->assertRepoDefined();
                        $this->name = $this->repo->getNameFromTitle( $this->title );
                }
                return $this->name;
@@ -179,7 +236,7 @@ abstract class File {
 
        /**
         * Return the associated title object
-        * @return Title
+        * @return Title|false
         */
        public function getTitle() { return $this->title; }
 
@@ -202,6 +259,7 @@ abstract class File {
         */
        public function getUrl() {
                if ( !isset( $this->url ) ) {
+                       $this->assertRepoDefined();
                        $this->url = $this->repo->getZoneUrl( 'public' ) . '/' . $this->getUrlRel();
                }
                return $this->url;
@@ -215,7 +273,14 @@ abstract class File {
         * @return String
         */
        public function getFullUrl() {
-               return wfExpandUrl( $this->getUrl() );
+               return wfExpandUrl( $this->getUrl(), PROTO_RELATIVE );
+       }
+
+       /**
+        * @return string
+        */
+       public function getCanonicalUrl() {
+               return wfExpandUrl( $this->getUrl(), PROTO_CANONICAL );
        }
 
        /**
@@ -249,23 +314,12 @@ abstract class File {
        */
        public function getPath() {
                if ( !isset( $this->path ) ) {
-                       $this->path = $this->repo->getZonePath('public') . '/' . $this->getRel();
+                       $this->assertRepoDefined();
+                       $this->path = $this->repo->getZonePath( 'public' ) . '/' . $this->getRel();
                }
                return $this->path;
        }
 
-       /**
-        * Alias for getPath()
-        *
-        * @deprecated since 1.18 Use getPath().
-        *
-        * @return string
-        */
-       public function getFullPath() {
-               wfDeprecated( __METHOD__ );
-               return $this->getPath();
-       }
-
        /**
         * Return the width of the image. Returns false if the width is unknown
         * or undefined.
@@ -288,6 +342,8 @@ abstract class File {
         * STUB
         * Overridden by LocalFile, UnregisteredLocalFile
         *
+        * @param $page int
+        *
         * @return false|number
         */
        public function getHeight( $page = 1 ) {
@@ -471,7 +527,7 @@ abstract class File {
 
        /**
         * Accessor for __get()
-        * 
+        *
         * @return bool
         */
        protected function getIsSafeFile() {
@@ -648,6 +704,55 @@ abstract class File {
                return $thumb->getUrl();
        }
 
+       /**
+        * Do the work of a transform (from an original into a thumb).
+        * Contains filesystem-specific functions.
+        *
+        * @param $thumbName string: the name of the thumbnail file.
+        * @param $thumbUrl string: the URL of the thumbnail file.
+        * @param $params Array: an associative array of handler-specific parameters.
+        *                Typical keys are width, height and page.
+        * @param $flags Integer: a bitfield, may contain self::RENDER_NOW to force rendering
+        *
+        * @return MediaTransformOutput | false
+        */
+       protected function maybeDoTransform( $thumbName, $thumbUrl, $params, $flags = 0 ) {
+               global $wgIgnoreImageErrors, $wgThumbnailEpoch;
+
+               $thumbPath = $this->getThumbPath( $thumbName );
+               if ( $this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
+                       wfDebug( __METHOD__ . " transformation deferred." );
+                       return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+               }
+
+               wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
+               $this->migrateThumbFile( $thumbName );
+               if ( file_exists( $thumbPath ) && !($flags & self::RENDER_FORCE) ) { 
+                       $thumbTime = filemtime( $thumbPath );
+                       if ( $thumbTime !== FALSE &&
+                            gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) { 
+
+                               return $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+                       }
+               } elseif( $flags & self::RENDER_FORCE ) {
+                       wfDebug( __METHOD__ . " forcing rendering per flag File::RENDER_FORCE\n" ); 
+               }
+               $thumb = $this->handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
+
+               // Ignore errors if requested
+               if ( !$thumb ) {
+                       $thumb = null;
+               } elseif ( $thumb->isError() ) {
+                       $this->lastError = $thumb->toText();
+                       if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
+                               $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
+                       }
+               }
+
+               return $thumb;
+       }
+
+
        /**
         * Transform a media file
         *
@@ -657,7 +762,7 @@ abstract class File {
         * @return MediaTransformOutput | false
         */
        function transform( $params, $flags = 0 ) {
-               global $wgUseSquid, $wgIgnoreImageErrors, $wgThumbnailEpoch, $wgServer;
+               global $wgUseSquid;
 
                wfProfileIn( __METHOD__ );
                do {
@@ -670,7 +775,7 @@ abstract class File {
                        // Get the descriptionUrl to embed it as comment into the thumbnail. Bug 19791.
                        $descriptionUrl =  $this->getDescriptionUrl();
                        if ( $descriptionUrl ) {
-                               $params['descriptionUrl'] = $wgServer . $descriptionUrl;
+                               $params['descriptionUrl'] = wfExpandUrl( $descriptionUrl, PROTO_CANONICAL );
                        }
 
                        $script = $this->getTransformScript();
@@ -685,36 +790,9 @@ abstract class File {
                        $normalisedParams = $params;
                        $this->handler->normaliseParams( $this, $normalisedParams );
                        $thumbName = $this->thumbName( $normalisedParams );
-                       $thumbPath = $this->getThumbPath( $thumbName );
                        $thumbUrl = $this->getThumbUrl( $thumbName );
 
-                       if ( $this->repo && $this->repo->canTransformVia404() && !($flags & self::RENDER_NOW ) ) {
-                               $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
-                               break;
-                       }
-
-                       wfDebug( __METHOD__.": Doing stat for $thumbPath\n" );
-                       $this->migrateThumbFile( $thumbName );
-                       if ( file_exists( $thumbPath )) {
-                               $thumbTime = filemtime( $thumbPath );
-                               if ( $thumbTime !== FALSE &&
-                                       gmdate( 'YmdHis', $thumbTime ) >= $wgThumbnailEpoch ) {
-
-                                       $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
-                                       break;
-                               }
-                       }
-                       $thumb = $this->handler->doTransform( $this, $thumbPath, $thumbUrl, $params );
-
-                       // Ignore errors if requested
-                       if ( !$thumb ) {
-                               $thumb = null;
-                       } elseif ( $thumb->isError() ) {
-                               $this->lastError = $thumb->toText();
-                               if ( $wgIgnoreImageErrors && !($flags & self::RENDER_NOW) ) {
-                                       $thumb = $this->handler->getTransform( $this, $thumbPath, $thumbUrl, $params );
-                               }
-                       }
+                       $thumb = $this->maybeDoTransform( $thumbName, $thumbUrl, $params, $flags );
 
                        // Purge. Useful in the event of Core -> Squid connection failure or squid
                        // purge collisions from elsewhere during failure. Don't keep triggering for
@@ -862,6 +940,7 @@ abstract class File {
         */
        function getHashPath() {
                if ( !isset( $this->hashPath ) ) {
+                       $this->assertRepoDefined();
                        $this->hashPath = $this->repo->getHashPath( $this->getName() );
                }
                return $this->hashPath;
@@ -886,9 +965,9 @@ abstract class File {
        }
 
        /**
-        * Get the relative path for an archive file
+        * Get the relative path for an archived file
         *
-        * @param $suffix bool
+        * @param $suffix bool|string if not false, the name of an archived thumbnail file
         *
         * @return string
         */
@@ -903,24 +982,59 @@ abstract class File {
        }
 
        /**
-        * Get the path of the archive directory, or a particular file if $suffix is specified
+        * Get the relative path for an archived file's thumbs directory
+        * or a specific thumb if the $suffix is given.
+        *
+        * @param $archiveName string the timestamped name of an archived image
+        * @param $suffix bool|string if not false, the name of a thumbnail file
+        *
+        * @return string
+        */
+       function getArchiveThumbRel( $archiveName, $suffix = false ) {
+               $path = 'archive/' . $this->getHashPath() . $archiveName . "/";
+               if ( $suffix === false ) {
+                       $path = substr( $path, 0, -1 );
+               } else {
+                       $path .= $suffix;
+               }
+               return $path;
+       }
+
+       /**
+        * Get the path of the archived file.
         *
-        * @param $suffix bool
+        * @param $suffix bool|string if not false, the name of an archived file.
         *
         * @return string
         */
        function getArchivePath( $suffix = false ) {
+               $this->assertRepoDefined();
                return $this->repo->getZonePath( 'public' ) . '/' . $this->getArchiveRel( $suffix );
        }
 
+       /**
+        * Get the path of the archived file's thumbs, or a particular thumb if $suffix is specified
+        *
+        * @param $archiveName string the timestamped name of an archived image
+        * @param $suffix bool|string if not false, the name of a thumbnail file
+        *
+        * @return string
+        */
+       function getArchiveThumbPath( $archiveName, $suffix = false ) {
+               $this->assertRepoDefined();
+               return $this->repo->getZonePath( 'thumb' ) . '/' .
+                       $this->getArchiveThumbRel( $archiveName, $suffix );
+       }
+
        /**
         * Get the path of the thumbnail directory, or a particular file if $suffix is specified
         *
-        * @param $suffix bool
+        * @param $suffix bool|string if not false, the name of a thumbnail file
         *
         * @return string
         */
        function getThumbPath( $suffix = false ) {
+               $this->assertRepoDefined();
                $path = $this->repo->getZonePath( 'thumb' ) . '/' . $this->getRel();
                if ( $suffix !== false ) {
                        $path .= '/' . $suffix;
@@ -931,12 +1045,33 @@ abstract class File {
        /**
         * Get the URL of the archive directory, or a particular file if $suffix is specified
         *
-        * @param $suffix bool
+        * @param $suffix bool|string if not false, the name of an archived file
         *
         * @return string
         */
        function getArchiveUrl( $suffix = false ) {
-               $path = $this->repo->getZoneUrl('public') . '/archive/' . $this->getHashPath();
+               $this->assertRepoDefined();
+               $path = $this->repo->getZoneUrl( 'public' ) . '/archive/' . $this->getHashPath();
+               if ( $suffix === false ) {
+                       $path = substr( $path, 0, -1 );
+               } else {
+                       $path .= rawurlencode( $suffix );
+               }
+               return $path;
+       }
+
+       /**
+        * Get the URL of the archived file's thumbs, or a particular thumb if $suffix is specified
+        *
+        * @param $archiveName string the timestamped name of an archived image
+        * @param $suffix bool|string if not false, the name of a thumbnail file
+        *
+        * @return string
+        */
+       function getArchiveThumbUrl( $archiveName, $suffix = false ) {
+               $this->assertRepoDefined();
+               $path = $this->repo->getZoneUrl( 'thumb' ) . '/archive/' .
+                       $this->getHashPath() . rawurlencode( $archiveName ) . "/";
                if ( $suffix === false ) {
                        $path = substr( $path, 0, -1 );
                } else {
@@ -948,11 +1083,12 @@ abstract class File {
        /**
         * Get the URL of the thumbnail directory, or a particular file if $suffix is specified
         *
-        * @param $suffix bool
+        * @param $suffix bool|string if not false, the name of a thumbnail file
         *
         * @return path
         */
        function getThumbUrl( $suffix = false ) {
+               $this->assertRepoDefined();
                $path = $this->repo->getZoneUrl('thumb') . '/' . $this->getUrlRel();
                if ( $suffix !== false ) {
                        $path .= '/' . rawurlencode( $suffix );
@@ -961,13 +1097,14 @@ abstract class File {
        }
 
        /**
-        * Get the virtual URL for an archive file or directory
+        * Get the virtual URL for an archived file's thumbs, or a specific thumb.
         *
-        * @param bool|string $suffix
+        * @param $suffix bool|string if not false, the name of a thumbnail file
         *
         * @return string
         */
        function getArchiveVirtualUrl( $suffix = false ) {
+               $this->assertRepoDefined();
                $path = $this->repo->getVirtualUrl() . '/public/archive/' . $this->getHashPath();
                if ( $suffix === false ) {
                        $path = substr( $path, 0, -1 );
@@ -980,11 +1117,12 @@ abstract class File {
        /**
         * Get the virtual URL for a thumbnail file or directory
         *
-        * @param $suffix bool
+        * @param $suffix bool|string if not false, the name of a thumbnail file
         *
         * @return string
         */
        function getThumbVirtualUrl( $suffix = false ) {
+               $this->assertRepoDefined();
                $path = $this->repo->getVirtualUrl() . '/thumb/' . $this->getUrlRel();
                if ( $suffix !== false ) {
                        $path .= '/' . rawurlencode( $suffix );
@@ -995,11 +1133,12 @@ abstract class File {
        /**
         * Get the virtual URL for the file itself
         *
-        * @param $suffix bool
+        * @param $suffix bool|string if not false, the name of a thumbnail file
         *
         * @return string
         */
        function getVirtualUrl( $suffix = false ) {
+               $this->assertRepoDefined();
                $path = $this->repo->getVirtualUrl() . '/public/' . $this->getUrlRel();
                if ( $suffix !== false ) {
                        $path .= '/' . rawurlencode( $suffix );
@@ -1011,6 +1150,7 @@ abstract class File {
         * @return bool
         */
        function isHashed() {
+               $this->assertRepoDefined();
                return $this->repo->isHashed();
        }
 
@@ -1090,7 +1230,7 @@ abstract class File {
        /**
         * Returns the repository
         *
-        * @return FileRepo
+        * @return FileRepo|false
         */
        function getRepo() {
                return $this->repo;
@@ -1255,7 +1395,11 @@ abstract class File {
         * @return string
         */
        function getDescriptionUrl() {
-               return $this->repo->getDescriptionUrl( $this->getName() );
+               if ( $this->repo ) {
+                       return $this->repo->getDescriptionUrl( $this->getName() );
+               } else {
+                       return false;
+               }
        }
 
        /**
@@ -1265,7 +1409,7 @@ abstract class File {
         */
        function getDescriptionText() {
                global $wgMemc, $wgLang;
-               if ( !$this->repo->fetchDescription ) {
+               if ( !$this->repo || !$this->repo->fetchDescription ) {
                        return false;
                }
                $renderUrl = $this->repo->getDescriptionRenderUrl( $this->getName(), $wgLang->getCode() );
@@ -1345,9 +1489,10 @@ abstract class File {
         * field of this file, if it's marked as deleted.
         * STUB
         * @param $field Integer
+        * @param $user User object to check, or null to use $wgUser
         * @return Boolean
         */
-       function userCan( $field ) {
+       function userCan( $field, User $user = null ) {
                return true;
        }
 
@@ -1515,6 +1660,26 @@ abstract class File {
        function isMissing() {
                return false;
        }
+
+       /**
+        * Assert that $this->repo is set to a valid FileRepo instance
+        * @throws MWException
+        */
+       protected function assertRepoDefined() {
+               if ( !( $this->repo instanceof $this->repoClass ) ) {
+                       throw new MWException( "A {$this->repoClass} object is not set for this File.\n" );
+               }
+       }
+
+       /**
+        * Assert that $this->title is set to a Title
+        * @throws MWException
+        */
+       protected function assertTitleDefined() {
+               if ( !( $this->title instanceof Title ) ) {
+                       throw new MWException( "A Title object is not set for this File.\n" );
+               }
+       }
 }
 /**
  * Aliases for backwards compatibility with 1.6