StaticFile.php

<?php

namespace Lia\Utility;

/**
 * Build headers for static files like .js or .css or image files
 */
class StaticFile {

    /** The absolute file path */
    protected $file;

    /**
     * the intended file extension, in case $file has a weird naming convention
     */
    protected ?string $type;
    /** Array of headers */
    protected $headers = [];
    /** headers for the content type & content length  */
    protected $typeHeaders = [];
    /** headers for the cache-state */
    protected $cacheHeaders = [];
    /** true if request headers indicate the file has been cached on the client */
    public $userHasFileCached = false;

    /**
     * @param $file absolute file path
     */
    public function __construct($file, $type=null){
        $this->file = $file;
        $this->type = $type ?? pathinfo($file, PATHINFO_EXTENSION);
        if (!is_file($file)){
            throw new \Exception("File '{$file}' does not exist");
        }
        
        $this->getHeaders();
    }

    /**
     * @return array of headers for content type & content length
     */
    public function getContentTypeHeaders(){
        if ($this->typeHeaders!=null){
            return $this->typeHeaders;
        }
        $filePath = $this->file;
        $type = $this->getFileMimeType($filePath.'.'.$this->type);
        if ($type===FALSE){
            throw new \Exception("Cannot determine file type from path '{$filePath}', so cannot get content type headers.");
        }
        $headers[] = ['Content-type: '.$type];
        if (is_file($filePath)){
            $headers[] = ['Content-Length: '.filesize($filePath)];
        }
        $this->typeHeaders = $headers;
        return $headers;
    }

    /**
     * @return array of headers for cache-control
     */
    public function getCacheHeaders(){
        if ($this->cacheHeaders!=null){
            return $this->cacheHeaders;
        }

        $headers = [];
        $filePath = $this->file;
        $type = $this->getFileMimeType($filePath.'.'.$this->type);
        if ($type===FALSE){
            throw new \Lia\Exception("Cannot determine file type from path '{$filePath}', so cannot send file headers or file.");
        }
        /** @bug Caching is impossible if apache_request_headers is unavailable **/
        $requestHeaders = function_exists('apache_request_headers') ? \apache_request_headers() : [];
        $mtime = filemtime($filePath);

        if (isset($headers['If-Modified-Since']) 
            && (strtotime($headers['If-Modified-Since']) == $mtime) 
            &&$mtime !==FALSE) 
        {
            $headers[] = ['Last-Modified: '.gmdate('D, d M Y H:i:s', $mtime).' GMT', true, 304];
            $this->userHasFileCached = true;
        } else {
            $headers[] = ['Last-Modified: '.gmdate('D, d M Y H:i:s', $mtime).' GMT', true, 200];
            $headers[] = ['Content-Length: '.filesize($filePath)];
            $headers[] = ['Cache-Control: max-age='.(60*60*24*30)];
            // readfile($filePath);
        }
    
        $this->cacheHeaders = $headers;
        return $headers;
    
    }

    /**
     * @return all headers
     */
    public function getHeaders(){
        if ($this->headers!=null){
            return $this->headers;
        }

        $headers = [];

        $filePath = $this->file;

        $type = $this->getFileMimeType($filePath.'.'.$this->type);

        if ($type===FALSE){
            throw new \Exception("Cannot determine file type from path '{$filePath}', so cannot send file headers or file.");
        }

        /** @bug Caching is impossible if apache_request_headers is unavailable **/
        $requestHeaders = function_exists('apache_request_headers') ? \apache_request_headers() : [];
        $mtime = filemtime($filePath);

        if (isset($headers['If-Modified-Since']) 
            && (strtotime($headers['If-Modified-Since']) == $mtime) 
            &&$mtime !==FALSE) 
        {
            $headers[] = ['Last-Modified: '.gmdate('D, d M Y H:i:s', $mtime).' GMT', true, 304];
            $headers[] = ['Content-type: '.$type];
            $this->userHasFileCached = true;
        } else {
            $headers[] = ['Last-Modified: '.gmdate('D, d M Y H:i:s', $mtime).' GMT', true, 200];
            $headers[] = ['Content-type: '.$type];
            $headers[] = ['Content-Length: '.filesize($filePath)];
            $headers[] = ['Cache-Control: max-age='.(60*60*24*30)];
            // readfile($filePath);
        }
    
        $this->headers = $headers;
        return $headers;
    }

    /**
     * Get mime type of a file based on its extension
     */
    protected function getFileMimeType($filePath){
        static $typeMap = NULL;
        $typeMap = $typeMap ?? require(dirname(__DIR__,2).'/file/mime_type_map.php');
        $ext = pathinfo($filePath,PATHINFO_EXTENSION);
        // if (!isset($typeMap['mimes'][$ext]))throw new \Exception("type not available for extension '{$ext}'");
        return $typeMap['mimes'][$ext][0] ?? false;
    }

}