| 1 |
lars |
1 |
<?php
|
|
|
2 |
/**
|
|
|
3 |
* TAssetManager class
|
|
|
4 |
*
|
|
|
5 |
* @author Qiang Xue <qiang.xue@gmail.com>
|
|
|
6 |
* @link http://www.pradosoft.com/
|
|
|
7 |
* @copyright Copyright © 2005-2008 PradoSoft
|
|
|
8 |
* @license http://www.pradosoft.com/license/
|
|
|
9 |
* @version $Id: TAssetManager.php 2564 2008-11-11 21:56:02Z carlgmathisen $
|
|
|
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: TAssetManager.php 2564 2008-11-11 21:56:02Z carlgmathisen $
|
|
|
42 |
* @package System.Web
|
|
|
43 |
* @since 3.0
|
|
|
44 |
*/
|
|
|
45 |
class 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('assetmanager_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 |
?>
|