Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
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 &copy; 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
?>