Ticket #806: TThemeManager.php

File TThemeManager.php, 14.3 kB (added by rabol, 10 months ago)

TThemeManager - Option to set localize function

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