Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * TThemeManager 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: TThemeManager.php 2497 2008-08-14 21:15:30Z knut $
10
 * @package System.Web.UI
11
 */
12
 
13
Prado::using('System.Web.Services.TPageService');
14
 
15
/**
16
 * TThemeManager class
17
 *
18
 * TThemeManager manages the themes used in a Prado application.
19
 *
20
 * Themes are stored under the directory specified by the
21
 * {@link setBasePath BasePath} property. The themes can be accessed
22
 * via URL {@link setBaseUrl BaseUrl}. Each theme is represented by a subdirectory
23
 * and all the files under that directory. The name of a theme is the name
24
 * of the corresponding subdirectory.
25
 * By default, the base path of all themes is a directory named "themes"
26
 * under the directory containing the application entry script.
27
 * To get a theme (normally you do not need to), call {@link getTheme}.
28
 *
29
 * TThemeManager may be configured within page service tag in application
30
 * configuration file as follows,
31
 * <module id="themes" class="System.Web.UI.TThemeManager"
32
 *         BasePath="Application.themes" BaseUrl="/themes" />
33
 * where {@link getCacheExpire CacheExpire}, {@link getCacheControl CacheControl}
34
 * and {@link getBufferOutput BufferOutput} are configurable properties of THttpResponse.
35
 *
36
 * @author Qiang Xue <qiang.xue@gmail.com>
37
 * @version $Id: TThemeManager.php 2497 2008-08-14 21:15:30Z knut $
38
 * @package System.Web.UI
39
 * @since 3.0
40
 */
41
class TThemeManager extends TModule
42
{
43
	/**
44
	 * default themes base path
45
	 */
46
	const DEFAULT_BASEPATH='themes';
47
	/**
48
	 * @var boolean whether this module has been initialized
49
	 */
50
	private $_initialized=false;
51
	/**
52
	 * @var string the directory containing all themes
53
	 */
54
	private $_basePath=null;
55
	/**
56
	 * @var string the base URL for all themes
57
	 */
58
	private $_baseUrl=null;
59
 
60
	/**
61
	 * Initializes the module.
62
	 * This method is required by IModule and is invoked by application.
63
	 * @param TXmlElement module configuration
64
	 */
65
	public function init($config)
66
	{
67
		$this->_initialized=true;
68
		$service=$this->getService();
69
		if($service instanceof TPageService)
70
			$service->setThemeManager($this);
71
		else
72
			throw new TConfigurationException('thememanager_service_unavailable');
73
	}
74
 
75
	/**
76
	 * @param string name of the theme to be retrieved
77
	 * @return TTheme the theme retrieved
78
	 */
79
	public function getTheme($name)
80
	{
81
		$themePath=$this->getBasePath().DIRECTORY_SEPARATOR.$name;
82
		$themeUrl=rtrim($this->getBaseUrl(),'/').'/'.$name;
83
		return new TTheme($themePath,$themeUrl);
84
 
85
	}
86
 
87
	/**
88
	 * @return array list of available theme names
89
	 */
90
	public function getAvailableThemes()
91
	{
92
		$themes=array();
93
		$basePath=$this->getBasePath();
94
		$folder=@opendir($basePath);
95
		while($file=@readdir($folder))
96
		{
97
			if($file!=='.' && $file!=='..' && $file!=='.svn' && is_dir($basePath.DIRECTORY_SEPARATOR.$file))
98
				$themes[]=$file;
99
		}
100
		closedir($folder);
101
		return $themes;
102
	}
103
 
104
	/**
105
	 * @return string the base path for all themes. It is returned as an absolute path.
106
	 * @throws TConfigurationException if base path is not set and "themes" directory does not exist.
107
	 */
108
	public function getBasePath()
109
	{
110
		if($this->_basePath===null)
111
		{
112
			$this->_basePath=dirname($this->getRequest()->getApplicationFilePath()).DIRECTORY_SEPARATOR.self::DEFAULT_BASEPATH;
113
			if(($basePath=realpath($this->_basePath))===false || !is_dir($basePath))
114
				throw new TConfigurationException('thememanager_basepath_invalid2',$this->_basePath);
115
			$this->_basePath=$basePath;
116
		}
117
		return $this->_basePath;
118
	}
119
 
120
	/**
121
	 * @param string the base path for all themes. It must be in the format of a namespace.
122
	 * @throws TInvalidDataValueException if the base path is not a proper namespace.
123
	 */
124
	public function setBasePath($value)
125
	{
126
		if($this->_initialized)
127
			throw new TInvalidOperationException('thememanager_basepath_unchangeable');
128
		else
129
		{
130
			$this->_basePath=Prado::getPathOfNamespace($value);
131
			if($this->_basePath===null || !is_dir($this->_basePath))
132
				throw new TInvalidDataValueException('thememanager_basepath_invalid',$value);
133
		}
134
	}
135
 
136
	/**
137
	 * @return string the base URL for all themes.
138
	 * @throws TConfigurationException If base URL is not set and a correct one cannot be determined by Prado.
139
	 */
140
	public function getBaseUrl()
141
	{
142
		if($this->_baseUrl===null)
143
		{
144
			$appPath=dirname($this->getRequest()->getApplicationFilePath());
145
			$basePath=$this->getBasePath();
146
			if(strpos($basePath,$appPath)===false)
147
				throw new TConfigurationException('thememanager_baseurl_required');
148
			$appUrl=rtrim(dirname($this->getRequest()->getApplicationUrl()),'/\\');
149
			$this->_baseUrl=$appUrl.strtr(substr($basePath,strlen($appPath)),'\\','/');
150
		}
151
		return $this->_baseUrl;
152
	}
153
 
154
	/**
155
	 * @param string the base URL for all themes.
156
	 */
157
	public function setBaseUrl($value)
158
	{
159
		$this->_baseUrl=rtrim($value,'/');
160
	}
161
}
162
 
163
/**
164
 * TTheme class
165
 *
166
 * TTheme represents a particular theme. It is merely a collection of skins
167
 * that are applicable to the corresponding controls.
168
 *
169
 * Each theme is stored as a directory and files under that directory.
170
 * The theme name is the directory name. When TTheme is created, the files
171
 * whose name has the extension ".skin" are parsed and saved as controls skins.
172
 *
173
 * A skin is essentially a list of initial property values that are to be applied
174
 * to a control when the skin is applied.
175
 * Each type of control can have multiple skins identified by the SkinID.
176
 * If a skin does not have SkinID, it is the default skin that will be applied
177
 * to controls that do not specify particular SkinID.
178
 *
179
 * Whenever possible, TTheme will try to make use of available cache to save
180
 * the parsing time.
181
 *
182
 * To apply a theme to a particular control, call {@link applySkin}.
183
 *
184
 * @author Qiang Xue <qiang.xue@gmail.com>
185
 * @version $Id: TThemeManager.php 2497 2008-08-14 21:15:30Z knut $
186
 * @package System.Web.UI
187
 * @since 3.0
188
 */
189
class TTheme extends TApplicationComponent implements ITheme
190
{
191
	/**
192
	 * prefix for cache variable name used to store parsed themes
193
	 */
194
	const THEME_CACHE_PREFIX='prado:theme:';
195
	/**
196
	 * Extension name of skin files
197
	 */
198
	const SKIN_FILE_EXT='.skin';
199
	/**
200
	 * @var string theme path
201
	 */
202
	private $_themePath;
203
	/**
204
	 * @var string theme url
205
	 */
206
	private $_themeUrl;
207
	/**
208
	 * @var array list of skins for the theme
209
	 */
210
	private $_skins=null;
211
	/**
212
	 * @var string theme name
213
	 */
214
	private $_name='';
215
	/**
216
	 * @var array list of css files
217
	 */
218
	private $_cssFiles=array();
219
	/**
220
	 * @var array list of js files
221
	 */
222
	private $_jsFiles=array();
223
 
224
	/**
225
	 * Constructor.
226
	 * @param string theme path
227
	 * @param string theme URL
228
	 * @throws TConfigurationException if theme path does not exist or any parsing error of the skin files
229
	 */
230
	public function __construct($themePath,$themeUrl)
231
	{
232
		$this->_themeUrl=$themeUrl;
233
		$this->_themePath=realpath($themePath);
234
		$this->_name=basename($themePath);
235
		$cacheValid=false;
236
		// TODO: the following needs to be cleaned up (Qiang)
237
		if(($cache=$this->getApplication()->getCache())!==null)
238
		{
239
			$array=$cache->get(self::THEME_CACHE_PREFIX.$themePath);
240
			if(is_array($array))
241
			{
242
				list($skins,$cssFiles,$jsFiles,$timestamp)=$array;
243
				if($this->getApplication()->getMode()!==TApplicationMode::Performance)
244
				{
245
					if(($dir=opendir($themePath))===false)
246
						throw new TIOException('theme_path_inexistent',$themePath);
247
					$cacheValid=true;
248
					while(($file=readdir($dir))!==false)
249
					{
250
						if($file==='.' || $file==='..')
251
							continue;
252
						else if(basename($file,'.css')!==$file)
253
							$this->_cssFiles[]=$themeUrl.'/'.$file;
254
						else if(basename($file,'.js')!==$file)
255
							$this->_jsFiles[]=$themeUrl.'/'.$file;
256
						else if(basename($file,self::SKIN_FILE_EXT)!==$file && filemtime($themePath.DIRECTORY_SEPARATOR.$file)>$timestamp)
257
						{
258
							$cacheValid=false;
259
							break;
260
						}
261
					}
262
					closedir($dir);
263
					if($cacheValid)
264
						$this->_skins=$skins;
265
				}
266
				else
267
				{
268
					$cacheValid=true;
269
					$this->_cssFiles=$cssFiles;
270
					$this->_jsFiles=$jsFiles;
271
					$this->_skins=$skins;
272
				}
273
			}
274
		}
275
		if(!$cacheValid)
276
		{
277
			$this->_cssFiles=array();
278
			$this->_jsFiles=array();
279
			$this->_skins=array();
280
			if(($dir=opendir($themePath))===false)
281
				throw new TIOException('theme_path_inexistent',$themePath);
282
			while(($file=readdir($dir))!==false)
283
			{
284
				if($file==='.' || $file==='..')
285
					continue;
286
				else if(basename($file,'.css')!==$file)
287
					$this->_cssFiles[]=$themeUrl.'/'.$file;
288
				else if(basename($file,'.js')!==$file)
289
					$this->_jsFiles[]=$themeUrl.'/'.$file;
290
				else if(basename($file,self::SKIN_FILE_EXT)!==$file)
291
				{
292
					$template=new TTemplate(file_get_contents($themePath.'/'.$file),$themePath,$themePath.'/'.$file);
293
					foreach($template->getItems() as $skin)
294
					{
295
						if(!isset($skin[2]))  // a text string, ignored
296
							continue;
297
						else if($skin[0]!==-1)
298
							throw new TConfigurationException('theme_control_nested',$skin[1],dirname($themePath));
299
						$type=$skin[1];
300
						$id=isset($skin[2]['skinid'])?$skin[2]['skinid']:0;
301
						unset($skin[2]['skinid']);
302
						if(isset($this->_skins[$type][$id]))
303
							throw new TConfigurationException('theme_skinid_duplicated',$type,$id,dirname($themePath));
304
						/*
305
						foreach($skin[2] as $name=>$value)
306
						{
307
							if(is_array($value) && ($value[0]===TTemplate::CONFIG_DATABIND || $value[0]===TTemplate::CONFIG_PARAMETER))
308
								throw new TConfigurationException('theme_databind_forbidden',dirname($themePath),$type,$id);
309
						}
310
						*/
311
						$this->_skins[$type][$id]=$skin[2];
312
					}
313
				}
314
			}
315
			closedir($dir);
316
			sort($this->_cssFiles);
317
			sort($this->_jsFiles);
318
			if($cache!==null)
319
				$cache->set(self::THEME_CACHE_PREFIX.$themePath,array($this->_skins,$this->_cssFiles,$this->_jsFiles,time()));
320
		}
321
	}
322
 
323
	/**
324
	 * @return string theme name
325
	 */
326
	public function getName()
327
	{
328
		return $this->_name;
329
	}
330
 
331
 	/**
332
	 * @param string theme name
333
	 */
334
	protected function setName($value)
335
	{
336
		$this->_name = $value;
337
	}
338
 
339
	/**
340
	 * @return string the URL to the theme folder (without ending slash)
341
	 */
342
	public function getBaseUrl()
343
	{
344
		return $this->_themeUrl;
345
	}
346
 
347
 	/**
348
	 * @param string the URL to the theme folder
349
	 */
350
	protected function setBaseUrl($value)
351
	{
352
		$this->_themeUrl=rtrim($value,'/');
353
	}
354
 
355
	/**
356
	 * @return string the file path to the theme folder
357
	 */
358
	public function getBasePath()
359
	{
360
		return $this->_themePath;
361
	}
362
 
363
 	/**
364
	 * @param string tthe file path to the theme folder
365
	 */
366
	protected function setBasePath($value)
367
	{
368
		$this->_themePath=$value;
369
	}
370
 
371
	/**
372
	 * @return array list of skins for the theme
373
	 */
374
	public function getSkins()
375
	{
376
		return $this->_skins;
377
	}
378
 
379
 	/**
380
	 * @param array list of skins for the theme
381
	 */
382
	protected function setSkins($value)
383
	{
384
		$this->_skins = $value;
385
	}
386
 
387
	/**
388
	 * Applies the theme to a particular control.
389
	 * The control's class name and SkinID value will be used to
390
	 * identify which skin to be applied. If the control's SkinID is empty,
391
	 * the default skin will be applied.
392
	 * @param TControl the control to be applied with a skin
393
	 * @return boolean if a skin is successfully applied
394
	 * @throws TConfigurationException if any error happened during the skin application
395
	 */
396
	public function applySkin($control)
397
	{
398
		$type=get_class($control);
399
		if(($id=$control->getSkinID())==='')
400
			$id=0;
401
		if(isset($this->_skins[$type][$id]))
402
		{
403
			foreach($this->_skins[$type][$id] as $name=>$value)
404
			{
405
				Prado::trace("Applying skin $name to $type",'System.Web.UI.TThemeManager');
406
				if(is_array($value))
407
				{
408
					switch($value[0])
409
					{
410
						case TTemplate::CONFIG_EXPRESSION:
411
							$value=$this->evaluateExpression($value[1]);
412
							break;
413
						case TTemplate::CONFIG_ASSET:
414
							$value=$this->_themeUrl.'/'.ltrim($value[1],'/');
415
							break;
416
						case TTemplate::CONFIG_DATABIND:
417
							$control->bindProperty($name,$value[1]);
418
							break;
419
						case TTemplate::CONFIG_PARAMETER:
420
							$control->setSubProperty($name,$this->getApplication()->getParameters()->itemAt($value[1]));
421
							break;
422
						case TTemplate::CONFIG_TEMPLATE:
423
							$control->setSubProperty($name,$value[1]);
424
							break;
425
						case TTemplate::CONFIG_LOCALIZATION:
426
							$control->setSubProperty($name,Prado::localize($value[1]));
427
							break;
428
						default:
429
							throw new TConfigurationException('theme_tag_unexpected',$name,$value[0]);
430
							break;
431
					}
432
				}
433
				if(!is_array($value))
434
				{
435
					if(strpos($name,'.')===false)	// is simple property or custom attribute
436
					{
437
						if($control->hasProperty($name))
438
						{
439
							if($control->canSetProperty($name))
440
							{
441
								$setter='set'.$name;
442
								$control->$setter($value);
443
							}
444
							else
445
								throw new TConfigurationException('theme_property_readonly',$type,$name);
446
						}
447
						else
448
							throw new TConfigurationException('theme_property_undefined',$type,$name);
449
					}
450
					else	// complex property
451
						$control->setSubProperty($name,$value);
452
				}
453
			}
454
			return true;
455
		}
456
		else
457
			return false;
458
	}
459
 
460
	/**
461
	 * @return array list of CSS files (URL) in the theme
462
	 */
463
	public function getStyleSheetFiles()
464
	{
465
		return $this->_cssFiles;
466
	}
467
 
468
 	/**
469
	 * @param array list of CSS files (URL) in the theme
470
	 */
471
	protected function setStyleSheetFiles($value)
472
	{
473
		$this->_cssFiles=$value;
474
	}
475
 
476
	/**
477
	 * @return array list of Javascript files (URL) in the theme
478
	 */
479
	public function getJavaScriptFiles()
480
	{
481
		return $this->_jsFiles;
482
	}
483
 
484
	/**
485
	 * @param array list of Javascript files (URL) in the theme
486
	 */
487
	protected function setJavaScriptFiles($value)
488
	{
489
		$this->_jsFiles=$value;
490
	}
491
}
492
 
493
?>