5 * @author Qiang Xue <qiang.xue@gmail.com>
6 * @link http://www.pradosoft.com/
7 * @copyright Copyright © 2005-2014 PradoSoft
8 * @license http://www.pradosoft.com/license/
15 * TAssetManager provides a scheme to allow web clients visiting
16 * private files that are normally web-inaccessible.
18 * TAssetManager will copy the file to be published into a web-accessible
19 * directory. The default base directory for storing the file is "assets", which
20 * should be under the application directory. This can be changed by setting
21 * the {@link setBasePath BasePath} property together with the
22 * {@link setBaseUrl BaseUrl} property that refers to the URL for accessing the base path.
24 * By default, TAssetManager will not publish a file or directory if it already
25 * exists in the publishing directory and has an older modification time.
26 * If the application mode is set as 'Performance', the modification time check
27 * will be skipped. You can explicitly require a modification time check
28 * with the function {@link publishFilePath}. This is usually
29 * very useful during development.
31 * TAssetManager may be configured in application configuration file as follows,
33 * <module id="asset" BasePath="Application.assets" BaseUrl="/assets" />
35 * where {@link getBasePath BasePath} and {@link getBaseUrl BaseUrl} are
36 * configurable properties of TAssetManager. Make sure that BasePath is a namespace
37 * pointing to a valid directory writable by the Web server process.
39 * @author Qiang Xue <qiang.xue@gmail.com>
43 class TAssetManager extends TModule
46 * Default web accessible base path for storing private files
48 const DEFAULT_BASEPATH='assets';
50 * @var string base web accessible path for storing private files
52 private $_basePath=null;
54 * @var string base URL for accessing the publishing directory.
56 private $_baseUrl=null;
58 * @var boolean whether to use timestamp checking to ensure files are published with up-to-date versions.
60 private $_checkTimestamp=false;
62 * @var TApplication application instance
64 private $_application;
66 * @var array published assets
68 private $_published=array();
70 * @var boolean whether the module is initialized
72 private $_initialized=false;
75 * Initializes the module.
76 * This method is required by IModule and is invoked by application.
77 * @param TXmlElement module configuration
79 public function init($config)
81 $application=$this->getApplication();
82 if($this->_basePath===null)
83 $this->_basePath=dirname($application->getRequest()->getApplicationFilePath()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH;
84 if(!is_writable($this->_basePath) || !is_dir($this->_basePath))
85 throw new TConfigurationException('assetmanager_basepath_invalid',$this->_basePath);
86 if($this->_baseUrl===null)
87 $this->_baseUrl=rtrim(dirname($application->getRequest()->getApplicationUrl()),'/\\').'/'.self::DEFAULT_BASEPATH;
88 $application->setAssetManager($this);
89 $this->_initialized=true;
93 * @return string the root directory storing published asset files
95 public function getBasePath()
97 return $this->_basePath;
101 * Sets the root directory storing published asset files.
102 * The directory must be in namespace format.
103 * @param string the root directory storing published asset files
104 * @throws TInvalidOperationException if the module is initialized already
106 public function setBasePath($value)
108 if($this->_initialized)
109 throw new TInvalidOperationException('assetmanager_basepath_unchangeable');
112 $this->_basePath=Prado::getPathOfNamespace($value);
113 if($this->_basePath===null || !is_dir($this->_basePath) || !is_writable($this->_basePath))
114 throw new TInvalidDataValueException('assetmanager_basepath_invalid',$value);
119 * @return string the base url that the published asset files can be accessed
121 public function getBaseUrl()
123 return $this->_baseUrl;
127 * @param string the base url that the published asset files can be accessed
128 * @throws TInvalidOperationException if the module is initialized already
130 public function setBaseUrl($value)
132 if($this->_initialized)
133 throw new TInvalidOperationException('assetmanager_baseurl_unchangeable');
135 $this->_baseUrl=rtrim($value,'/');
139 * Publishes a file or a directory (recursively).
140 * This method will copy the content in a directory (recursively) to
141 * a web accessible directory and returns the URL for the directory.
142 * If the application is not in performance mode, the file modification
143 * time will be used to make sure the published file is latest or not.
144 * If not, a file copy will be performed.
145 * @param string the path to be published
146 * @param boolean If true, file modification time will be checked even if the application
147 * is in performance mode.
148 * @return string an absolute URL to the published directory
149 * @throws TInvalidDataValueException if the file path to be published is
152 public function publishFilePath($path,$checkTimestamp=false)
154 if(isset($this->_published[$path]))
155 return $this->_published[$path];
156 else if(empty($path) || ($fullpath=realpath($path))===false)
157 throw new TInvalidDataValueException('assetmanager_filepath_invalid',$path);
158 else if(is_file($fullpath))
160 $dir=$this->hash(dirname($fullpath));
161 $fileName=basename($fullpath);
162 $dst=$this->_basePath.DIRECTORY_SEPARATOR.$dir;
163 if(!is_file($dst.DIRECTORY_SEPARATOR.$fileName) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance)
164 $this->copyFile($fullpath,$dst);
165 return $this->_published[$path]=$this->_baseUrl.'/'.$dir.'/'.$fileName;
169 $dir=$this->hash($fullpath);
170 if(!is_dir($this->_basePath.DIRECTORY_SEPARATOR.$dir) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance)
172 Prado::trace("Publishing directory $fullpath",'System.Web.UI.TAssetManager');
173 $this->copyDirectory($fullpath,$this->_basePath.DIRECTORY_SEPARATOR.$dir);
175 return $this->_published[$path]=$this->_baseUrl.'/'.$dir;
180 * @return array List of published assets
183 public function getPublished()
185 return $this->_published;
189 * @param $values List of published assets
192 protected function setPublished($values=array())
194 $this->_published = $values;
198 * Returns the published path of a file path.
199 * This method does not perform any publishing. It merely tells you
200 * if the file path is published, where it will go.
201 * @param string directory or file path being published
202 * @return string the published file path
204 public function getPublishedPath($path)
206 $path=realpath($path);
208 return $this->_basePath.DIRECTORY_SEPARATOR.$this->hash(dirname($path)).DIRECTORY_SEPARATOR.basename($path);
210 return $this->_basePath.DIRECTORY_SEPARATOR.$this->hash($path);
214 * Returns the URL of a published file path.
215 * This method does not perform any publishing. It merely tells you
216 * if the file path is published, what the URL will be to access it.
217 * @param string directory or file path being published
218 * @return string the published URL for the file path
220 public function getPublishedUrl($path)
222 $path=realpath($path);
224 return $this->_baseUrl.'/'.$this->hash(dirname($path)).'/'.basename($path);
226 return $this->_baseUrl.'/'.$this->hash($path);
230 * Generate a CRC32 hash for the directory path. Collisions are higher
231 * than MD5 but generates a much smaller hash string.
232 * @param string string to be hashed.
233 * @return string hashed string.
235 protected function hash($dir)
237 return sprintf('%x',crc32($dir.Prado::getVersion()));
241 * Copies a file to a directory.
242 * Copying is done only when the destination file does not exist
243 * or has an older file modification time.
244 * @param string source file path
245 * @param string destination directory (if not exists, it will be created)
247 protected function copyFile($src,$dst)
252 @chmod($dst, PRADO_CHMOD);
254 $dstFile=$dst.DIRECTORY_SEPARATOR.basename($src);
255 if(@filemtime($dstFile)<@filemtime($src))
257 Prado::trace("Publishing file $src to $dstFile",'System.Web.TAssetManager');
258 @copy($src,$dstFile);
263 * Copies a directory recursively as another.
264 * If the destination directory does not exist, it will be created.
265 * File modification time is used to ensure the copied files are latest.
266 * @param string the source directory
267 * @param string the destination directory
268 * @todo a generic solution to ignore certain directories and files
270 public function copyDirectory($src,$dst)
275 @chmod($dst, PRADO_CHMOD);
277 if($folder=@opendir($src))
279 while($file=@readdir($folder))
281 if($file==='.' || $file==='..' || $file==='.svn')
283 else if(is_file($src.DIRECTORY_SEPARATOR.$file))
285 if(@filemtime($dst.DIRECTORY_SEPARATOR.$file)<@filemtime($src.DIRECTORY_SEPARATOR.$file))
287 @copy($src.DIRECTORY_SEPARATOR.$file,$dst.DIRECTORY_SEPARATOR.$file);
288 @chmod($dst.DIRECTORY_SEPARATOR.$file, PRADO_CHMOD);
292 $this->copyDirectory($src.DIRECTORY_SEPARATOR.$file,$dst.DIRECTORY_SEPARATOR.$file);
296 throw new TInvalidDataValueException('assetmanager_source_directory_invalid', $src);
301 * Publish a tar file by extracting its contents to the assets directory.
302 * Each tar file must be accomplished with its own MD5 check sum file.
303 * The MD5 file is published when the tar contents are successfully
304 * extracted to the assets directory. The presence of the MD5 file
305 * as published asset assumes that the tar file has already been extracted.
306 * @param string tar filename
307 * @param string MD5 checksum for the corresponding tar file.
308 * @param boolean Wether or not to check the time stamp of the file for publishing. Defaults to false.
309 * @return string URL path to the directory where the tar file was extracted.
311 public function publishTarFile($tarfile, $md5sum, $checkTimestamp=false)
313 if(isset($this->_published[$md5sum]))
314 return $this->_published[$md5sum];
315 else if(($fullpath=realpath($md5sum))===false || !is_file($fullpath))
316 throw new TInvalidDataValueException('assetmanager_tarchecksum_invalid',$md5sum);
319 $dir=$this->hash(dirname($fullpath));
320 $fileName=basename($fullpath);
321 $dst=$this->_basePath.DIRECTORY_SEPARATOR.$dir;
322 if(!is_file($dst.DIRECTORY_SEPARATOR.$fileName) || $checkTimestamp || $this->getApplication()->getMode()!==TApplicationMode::Performance)
324 if(@filemtime($dst.DIRECTORY_SEPARATOR.$fileName)<@filemtime($fullpath))
326 $this->copyFile($fullpath,$dst);
327 $this->deployTarFile($tarfile,$dst);
330 return $this->_published[$md5sum]=$this->_baseUrl.'/'.$dir;
335 * Extracts the tar file to the destination directory.
336 * N.B Tar file must not be compressed.
337 * @param string tar file
338 * @param string path where the contents of tar file are to be extracted
339 * @return boolean true if extract successful, false otherwise.
341 protected function deployTarFile($path,$destination)
343 if(($fullpath=realpath($path))===false || !is_file($fullpath))
344 throw new TIOException('assetmanager_tarfile_invalid',$path);
347 Prado::using('System.IO.TTarFileExtractor');
348 $tar = new TTarFileExtractor($fullpath);
349 return $tar->extract($destination);