root/trunk/framework/Web/TAssetManager.php

Revision 2514, 11.6 kB (checked in by carl, 5 weeks ago)

#856 - Assets PRADO_CHMOD constant missing in several places

  • Property svn:keywords set to Id
Line 
1<?php
2/**
3 * TAssetManager class
4 *
5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright &copy; 2005-2008 PradoSoft
8 * @license http://www.pradosoft.com/license/
9 * @version $Id$
10 * @package System.Web
11 */
12
13/**
14 * TAssetManager class
15 *
16 * TAssetManager provides a scheme to allow web clients visiting
17 * private files that are normally web-inaccessible.
18 *
19 * TAssetManager will copy the file to be published into a web-accessible
20 * directory. The default base directory for storing the file is "assets", which
21 * should be under the application directory. This can be changed by setting
22 * the {@link setBasePath BasePath} property together with the
23 * {@link setBaseUrl BaseUrl} property that refers to the URL for accessing the base path.
24 *
25 * By default, TAssetManager will not publish a file or directory if it already
26 * exists in the publishing directory and has an older modification time.
27 * If the application mode is set as 'Performance', the modification time check
28 * will be skipped. You can explicitly require a modification time check
29 * with the function {@link publishFilePath}. This is usually
30 * very useful during development.
31 *
32 * TAssetManager may be configured in application configuration file as follows,
33 * <code>
34 * <module id="asset" BasePath="Application.assets" BaseUrl="/assets" />
35 * </code>
36 * where {@link getBasePath BasePath} and {@link getBaseUrl BaseUrl} are
37 * configurable properties of TAssetManager. Make sure that BasePath is a namespace
38 * pointing to a valid directory writable by the Web server process.
39 *
40 * @author Qiang Xue <qiang.xue@gmail.com>
41 * @version $Id$
42 * @package System.Web
43 * @since 3.0
44 */
45class TAssetManager extends TModule
46{
47    /**
48     * Default web accessible base path for storing private files
49     */
50    const DEFAULT_BASEPATH='assets';
51    /**
52     * @var string base web accessible path for storing private files
53     */
54    private $_basePath=null;
55    /**
56     * @var string base URL for accessing the publishing directory.
57     */
58    private $_baseUrl=null;
59    /**
60     * @var boolean whether to use timestamp checking to ensure files are published with up-to-date versions.
61     */
62    private $_checkTimestamp=false;
63    /**
64     * @var TApplication application instance
65     */
66    private $_application;
67    /**
68     * @var array published assets
69     */
70    private $_published=array();
71    /**
72     * @var boolean whether the module is initialized
73     */
74    private $_initialized=false;
75
76    /**
77     * Initializes the module.
78     * This method is required by IModule and is invoked by application.
79     * @param TXmlElement module configuration
80     */
81    public function init($config)
82    {
83        $application=$this->getApplication();
84        if($this->_basePath===null)
85            $this->_basePath=dirname($application->getRequest()->getApplicationFilePath()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH;
86        if(!is_writable($this->_basePath) || !is_dir($this->_basePath))
87            throw new TConfigurationException('assetmanager_basepath_invalid',$this->_basePath);
88        if($this->_baseUrl===null)
89            $this->_baseUrl=rtrim(dirname($application->getRequest()->getApplicationUrl()),'/\\').'/'.self::DEFAULT_BASEPATH;
90        $application->setAssetManager($this);
91        $this->_initialized=true;
92    }
93
94    /**
95     * @return string the root directory storing published asset files
96     */
97    public function getBasePath()
98    {
99        return $this->_basePath;
100    }
101
102    /**
103     * Sets the root directory storing published asset files.
104     * The directory must be in namespace format.
105     * @param string the root directory storing published asset files
106     * @throws TInvalidOperationException if the module is initialized already
107     */
108    public function setBasePath($value)
109    {
110        if($this->_initialized)
111            throw new TInvalidOperationException('assetmanager_basepath_unchangeable');
112        else
113        {
114            $this->_basePath=Prado::getPathOfNamespace($value);
115            if($this->_basePath===null || !is_dir($this->_basePath) || !is_writable($this->_basePath))
116                throw new TInvalidDataValueException('assetmanage_basepath_invalid',$value);
117        }
118    }
119
120    /**
121     * @return string the base url that the published asset files can be accessed
122     */
123    public function getBaseUrl()
124    {
125        return $this->_baseUrl;
126    }
127
128    /**
129     * @param string the base url that the published asset files can be accessed
130     * @throws TInvalidOperationException if the module is initialized already
131     */
132    public function setBaseUrl($value)
133    {
134        if($this->_initialized)
135            throw new TInvalidOperationException('assetmanager_baseurl_unchangeable');
136        else
137            $this->_baseUrl=rtrim($value,'/');
138    }
139
140    /**
141     * Publishes a file or a directory (recursively).
142     * This method will copy the content in a directory (recursively) to
143     * a web accessible directory and returns the URL for the directory.
144     * If the application is not in performance mode, the file modification
145     * time will be used to make sure the published file is latest or not.
146     * If not, a file copy will be performed.
147     * @param string the path to be published
148     * @param boolean If true, file modification time will be checked even if the application
149     * is in performance mode.
150     * @return string an absolute URL to the published directory
151     * @throws TInvalidDataValueException if the file path to be published is
152     * invalid
153     */
154    public function publishFilePath($path,$checkTimestamp=false)
155    {
156        if(isset($this->_published[$path]))
157            return $this->_published[$path];
158        else if(empty($path) || ($fullpath=realpath($path))===false)
159            throw new TInvalidDataValueException('assetmanager_filepath_invalid',$path);
160        else if(is_file($fullpath))
161        {
162            $dir=$this->hash(dirname($fullpath));
163            $fileName=basename($fullpath);
164            $dst=$this->_basePath.DIRECTORY_SEPARATOR.$dir;
165            if(!is_file($dst.DIRECTORY_SEPARATOR.$fileName) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance)
166                $this->copyFile($fullpath,$dst);
167            return $this->_published[$path]=$this->_baseUrl.'/'.$dir.'/'.$fileName;
168        }
169        else
170        {
171            $dir=$this->hash($fullpath);
172            if(!is_dir($this->_basePath.DIRECTORY_SEPARATOR.$dir) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance)
173            {
174                Prado::trace("Publishing directory $fullpath",'System.Web.UI.TAssetManager');
175                $this->copyDirectory($fullpath,$this->_basePath.DIRECTORY_SEPARATOR.$dir);
176            }
177            return $this->_published[$path]=$this->_baseUrl.'/'.$dir;
178        }
179    }
180
181    /**
182     * Returns the published path of a file path.
183     * This method does not perform any publishing. It merely tells you
184     * if the file path is published, where it will go.
185     * @param string directory or file path being published
186     * @return string the published file path
187     */
188    public function getPublishedPath($path)
189    {
190        $path=realpath($path);
191        if(is_file($path))
192            return $this->_basePath.DIRECTORY_SEPARATOR.$this->hash(dirname($path)).DIRECTORY_SEPARATOR.basename($path);
193        else
194            return $this->_basePath.DIRECTORY_SEPARATOR.$this->hash($path);
195    }
196
197    /**
198     * Returns the URL of a published file path.
199     * This method does not perform any publishing. It merely tells you
200     * if the file path is published, what the URL will be to access it.
201     * @param string directory or file path being published
202     * @return string the published URL for the file path
203     */
204    public function getPublishedUrl($path)
205    {
206        $path=realpath($path);
207        if(is_file($path))
208            return $this->_baseUrl.'/'.$this->hash(dirname($path)).'/'.basename($path);
209        else
210            return $this->_baseUrl.'/'.$this->hash($path);
211    }
212
213    /**
214     * Generate a CRC32 hash for the directory path. Collisions are higher
215     * than MD5 but generates a much smaller hash string.
216     * @param string string to be hashed.
217     * @return string hashed string.
218     */
219    protected function hash($dir)
220    {
221        return sprintf('%x',crc32($dir.Prado::getVersion()));
222    }
223
224    /**
225     * Copies a file to a directory.
226     * Copying is done only when the destination file does not exist
227     * or has an older file modification time.
228     * @param string source file path
229     * @param string destination directory (if not exists, it will be created)
230     */
231    protected function copyFile($src,$dst)
232    {
233        if(!is_dir($dst))
234        {
235            @mkdir($dst);
236            @chmod($dst, PRADO_CHMOD);
237        }
238        $dstFile=$dst.DIRECTORY_SEPARATOR.basename($src);
239        if(@filemtime($dstFile)<@filemtime($src))
240        {
241            Prado::trace("Publishing file $src to $dstFile",'System.Web.TAssetManager');
242            @copy($src,$dstFile);
243        }
244    }
245
246    /**
247     * Copies a directory recursively as another.
248     * If the destination directory does not exist, it will be created.
249     * File modification time is used to ensure the copied files are latest.
250     * @param string the source directory
251     * @param string the destination directory
252     * @todo a generic solution to ignore certain directories and files
253     */
254    public function copyDirectory($src,$dst)
255    {
256        if(!is_dir($dst))
257        {
258            @mkdir($dst);
259            @chmod($dst, PRADO_CHMOD);
260        }
261        if($folder=@opendir($src))
262        {
263            while($file=@readdir($folder))
264            {
265                if($file==='.' || $file==='..' || $file==='.svn')
266                    continue;
267                else if(is_file($src.DIRECTORY_SEPARATOR.$file))
268                {
269                    if(@filemtime($dst.DIRECTORY_SEPARATOR.$file)<@filemtime($src.DIRECTORY_SEPARATOR.$file))
270                    {
271                        @copy($src.DIRECTORY_SEPARATOR.$file,$dst.DIRECTORY_SEPARATOR.$file);
272                        @chmod($dst.DIRECTORY_SEPARATOR.$file, PRADO_CHMOD);
273                    }
274                }
275                else
276                    $this->copyDirectory($src.DIRECTORY_SEPARATOR.$file,$dst.DIRECTORY_SEPARATOR.$file);
277            }
278            closedir($folder);
279        } else {
280            throw new TInvalidDataValueException('assetmanager_source_directory_invalid', $src);
281        }
282    }
283
284    /**
285     * Publish a tar file by extracting its contents to the assets directory.
286     * Each tar file must be accomplished with its own MD5 check sum file.
287     * The MD5 file is published when the tar contents are successfully
288     * extracted to the assets directory. The presence of the MD5 file
289     * as published asset assumes that the tar file has already been extracted.
290     * @param string tar filename
291     * @param string MD5 checksum for the corresponding tar file.
292     * @param boolean Wether or not to check the time stamp of the file for publishing. Defaults to false.
293     * @return string URL path to the directory where the tar file was extracted.
294     */
295    public function publishTarFile($tarfile, $md5sum, $checkTimestamp=false)
296    {
297        if(isset($this->_published[$md5sum]))
298            return $this->_published[$md5sum];
299        else if(($fullpath=realpath($md5sum))===false || !is_file($fullpath))
300            throw new TInvalidDataValueException('assetmanager_tarchecksum_invalid',$md5sum);
301        else
302        {
303            $dir=$this->hash(dirname($fullpath));
304            $fileName=basename($fullpath);
305            $dst=$this->_basePath.DIRECTORY_SEPARATOR.$dir;
306            if(!is_file($dst.DIRECTORY_SEPARATOR.$fileName) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance)
307            {
308                if(@filemtime($dst.DIRECTORY_SEPARATOR.$fileName)<@filemtime($fullpath))
309                {
310                    $this->copyFile($fullpath,$dst);
311                    $this->deployTarFile($tarfile,$dst);
312                }
313            }
314            return $this->_published[$md5sum]=$this->_baseUrl.'/'.$dir;
315        }
316    }
317
318    /**
319     * Extracts the tar file to the destination directory.
320     * N.B Tar file must not be compressed.
321     * @param string tar file
322     * @param string path where the contents of tar file are to be extracted
323     * @return boolean true if extract successful, false otherwise.
324     */
325    protected function deployTarFile($path,$destination)
326    {
327        if(($fullpath=realpath($path))===false || !is_file($fullpath))
328            throw new TIOException('assetmanager_tarfile_invalid',$path);
329        else
330        {
331            Prado::using('System.IO.TTarFileExtractor');
332            $tar = new TTarFileExtractor($fullpath);
333            return $tar->extract($destination);
334        }
335    }
336
337}
338
339?>
Note: See TracBrowser for help on using the browser.