]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarTheme/SugarTheme.php
Release 6.2.0
[Github/sugarcrm.git] / include / SugarTheme / SugarTheme.php
1 <?php
2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4  * SugarCRM Community Edition is a customer relationship management program developed by
5  * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 /*********************************************************************************
40
41  * Description:  Contains a variety of utility functions used to display UI
42  * components such as form headers and footers.  Intended to be modified on a per
43  * theme basis.
44  ********************************************************************************/
45
46 if(!defined('JSMIN_AS_LIB'))
47     define('JSMIN_AS_LIB', true);
48
49 require_once("include/SugarTheme/cssmin.php");
50 require_once("jssource/jsmin.php");
51 require_once('include/utils/sugar_file_utils.php');
52  
53 /**
54  * Class that provides tools for working with a theme.
55  */
56 class SugarTheme
57 {
58     /**
59      * Theme name
60      *
61      * @var string
62      */
63     protected $name;
64     
65     /**
66      * Theme description
67      *
68      * @var string
69      */
70     protected $description;
71     
72     /**
73      * Defines which parent files to not include
74      *
75      * @var string
76      */
77     protected $ignoreParentFiles = array();
78     
79     /**
80      * Defines which parent files to not include
81      *
82      * @var string
83      */
84     protected $directionality = 'ltr';
85     /**
86      * Theme directory name
87      *
88      * @var string
89      */
90     protected $dirName;
91     
92     /**
93      * Parent theme name
94      * 
95      * @var string
96      */
97     protected $parentTheme;
98     
99     /**
100      * Colors sets provided by the theme
101      * 
102      * @deprecated only here for BC during upgrades
103      * @var array
104      */ 
105     protected $colors = array();
106     
107     /**
108      * Font sets provided by the theme
109      *
110      * @deprecated only here for BC during upgrades
111      * @var array
112      */
113     protected $fonts  = array();
114     
115     /**
116      * Maximum sugar version this theme is for; defaults to 5.5.1 as all the themes without this
117      * parameter as assumed to work thru 5.5.1
118      *
119      * @var int
120      */
121     protected $version = '5.5.1';
122     
123     /**
124      * Colors used in bar charts
125      *
126      * @var array
127      */
128     protected $barChartColors = array(
129         "docBorder"             => "0xffffff",
130         "docBg1"                => "0xffffff",
131         "docBg2"                => "0xffffff",
132         "xText"                 => "0x33485c",
133         "yText"                 => "0x33485c",
134         "title"                 => "0x333333",
135         "misc"                  => "0x999999",
136         "altBorder"             => "0xffffff",
137         "altBg"                 => "0xffffff",
138         "altText"               => "0x666666",
139         "graphBorder"           => "0xcccccc",
140         "graphBg1"              => "0xf6f6f6",
141         "graphBg2"              => "0xf6f6f6",
142         "graphLines"            => "0xcccccc",
143         "graphText"             => "0x333333",
144         "graphTextShadow"       => "0xf9f9f9",
145         "barBorder"             => "0xeeeeee",
146         "barBorderHilite"       => "0x333333",
147         "legendBorder"          => "0xffffff",
148         "legendBg1"             => "0xffffff",
149         "legendBg2"             => "0xffffff",
150         "legendText"            => "0x444444",
151         "legendColorKeyBorder"  => "0x777777",
152         "scrollBar"             => "0xcccccc",
153         "scrollBarBorder"       => "0xeeeeee",
154         "scrollBarTrack"        => "0xeeeeee",
155         "scrollBarTrackBorder"  => "0xcccccc",
156         );
157
158     /**
159      * Colors used in pie charts
160      *
161      * @var array
162      */
163     protected $pieChartColors = array(
164         "docBorder"             => "0xffffff",
165         "docBg1"                => "0xffffff",
166         "docBg2"                => "0xffffff",
167         "title"                 => "0x333333",
168         "subtitle"              => "0x666666",
169         "misc"                  => "0x999999",
170         "altBorder"             => "0xffffff",
171         "altBg"                 => "0xffffff",
172         "altText"               => "0x666666",
173         "graphText"             => "0x33485c",
174         "graphTextShadow"       => "0xf9f9f9",
175         "pieBorder"             => "0xffffff",
176         "pieBorderHilite"       => "0x333333",
177         "legendBorder"          => "0xffffff",
178         "legendBg1"             => "0xffffff",
179         "legendBg2"             => "0xffffff",
180         "legendText"            => "0x444444",
181         "legendColorKeyBorder"  => "0x777777",
182         "scrollBar"             => "0xdfdfdf",
183         "scrollBarBorder"       => "0xfafafa",
184         "scrollBarTrack"        => "0xeeeeee",
185         "scrollBarTrackBorder"  => "0xcccccc",
186         );
187         
188     /**
189      * Does this theme support group tabs
190      * 
191      * @var bool
192      */
193     protected $group_tabs;
194     
195
196     /**
197      * Cache built of all css files locations
198      *
199      * @var array
200      */
201     private $_cssCache = array();
202     
203     /**
204      * Cache built of all image files locations
205      *
206      * @var array
207      */
208     private $_imageCache = array();
209     
210     /**
211      * Cache built of all javascript files locations
212      *
213      * @var array
214      */
215     private $_jsCache = array();
216     
217     /**
218      * Cache built of all template files locations
219      *
220      * @var array
221      */
222     private $_templateCache = array();
223     
224     /**
225      * Size of the caches after the are initialized in the constructor
226      *
227      * @var array
228      */
229     private $_initialCacheSize = array(
230         'cssCache'      => 0,
231         'imageCache'    => 0,
232         'jsCache'       => 0,
233         'templateCache' => 0,
234         );
235     
236     /**
237      * Controls whether or not to clear the cache on destroy; defaults to false
238      */
239     private $_clearCacheOnDestroy = false;
240     
241     private $imageExtensions = array(
242             'gif',
243             'png',
244             'jpg',
245             'tif',
246             'bmp',
247     );
248     
249     /**
250      * Constructor
251      *
252      * Sets the theme properties from the defaults passed to it, and loads the file path cache from an external cache
253      *
254      * @param  $defaults string defaults for the current theme
255      */
256     public function __construct(
257         $defaults
258         )
259     {
260         // apply parent theme's properties first
261         if ( isset($defaults['parentTheme']) ) {
262             $themedef = array();
263             include("themes/{$defaults['parentTheme']}/themedef.php");
264             foreach ( $themedef as $key => $value ) {
265                 if ( property_exists(__CLASS__,$key) ) {
266                     // For all arrays ( except colors and fonts ) you can just specify the items 
267                     // to change instead of all of the values
268                     if ( is_array($this->$key) && !in_array($key,array('colors','fonts')) )
269                         $this->$key = array_merge($this->$key,$value);
270                     else
271                         $this->$key = $value;
272                 }
273             }
274         }
275         foreach ( $defaults as $key => $value ) {
276             if ( property_exists(__CLASS__,$key) ) {
277                 // For all arrays ( except colors and fonts ) you can just specify the items 
278                 // to change instead of all of the values
279                 if ( is_array($this->$key) && !in_array($key,array('colors','fonts')) )
280                     $this->$key = array_merge($this->$key,$value);
281                 else
282                     $this->$key = $value;
283             }
284         }
285         if ( !inDeveloperMode() ) {
286             if ( sugar_is_file($GLOBALS['sugar_config']['cache_dir'].$this->getFilePath().'/pathCache.php') ) {
287                 $caches = unserialize(sugar_file_get_contents($GLOBALS['sugar_config']['cache_dir'].$this->getFilePath().'/pathCache.php'));
288                 if ( isset($caches['jsCache']) )
289                     $this->_jsCache       = $caches['jsCache'];
290                 if ( isset($caches['cssCache']) )
291                     $this->_cssCache      = $caches['cssCache'];
292                 if ( isset($caches['imageCache']) )
293                     $this->_imageCache    = $caches['imageCache'];
294                 if ( isset($caches['templateCache']) )
295                     $this->_templateCache = $caches['templateCache'];
296             }
297         }
298         $this->_initialCacheSize = array(
299             'jsCache'       => count($this->_jsCache),
300             'cssCache'      => count($this->_cssCache),
301             'imageCache'    => count($this->_imageCache),
302             'templateCache' => count($this->_templateCache),
303             );
304     }
305     
306     /**
307      * Destructor
308      * Here we'll write out the internal file path caches to an external cache of some sort.
309      */
310     public function __destruct()
311     {
312         // Bug 28309 - Set the current directory to one which we expect it to be (i.e. the root directory of the install
313         set_include_path(realpath(dirname(__FILE__) . '/../..') . PATH_SEPARATOR . get_include_path());
314         chdir(realpath(dirname(__FILE__) . '/../..'));
315         
316         // clear out the cache on destroy if we are asked to
317         if ( $this->_clearCacheOnDestroy ) {
318             if (is_file($GLOBALS['sugar_config']['cache_dir'].$this->getFilePath().'/pathCache.php'))
319                 unlink($GLOBALS['sugar_config']['cache_dir'].$this->getFilePath().'/pathCache.php');
320         }
321         elseif ( !inDeveloperMode() ) {
322             // only update the caches if they have been changed in this request
323             if ( count($this->_jsCache) != $this->_initialCacheSize['jsCache'] 
324                     || count($this->_cssCache) != $this->_initialCacheSize['cssCache']
325                     || count($this->_imageCache) != $this->_initialCacheSize['imageCache']
326                     || count($this->_templateCache) != $this->_initialCacheSize['templateCache']
327                 ) {
328                 sugar_file_put_contents(
329                     create_cache_directory($this->getFilePath().'/pathCache.php'),
330                     serialize(
331                         array(
332                             'jsCache'       => $this->_jsCache,
333                             'cssCache'      => $this->_cssCache,
334                             'imageCache'    => $this->_imageCache,
335                             'templateCache' => $this->_templateCache,
336                             )
337                         )
338                     );
339                 
340             }
341         }
342     }
343     
344     /**
345      * Specifies what is returned when the object is cast to a string, in this case it will be the
346      * theme directory name.
347      *
348      * @return string theme directory name
349      */
350     public function __toString() 
351     {
352         return $this->dirName;
353     }
354     
355     /**
356      * Generic public accessor method for all the properties of the theme ( which are kept protected )
357      *
358      * @return string
359      */
360     public function __get(
361         $key
362         )
363     {
364         if ( isset($this->$key) )
365             return $this->$key;
366     }
367     
368     public function __isset($key){
369         return isset($this->$key);
370         
371     }
372     
373     /**
374      * Clears out the caches used for this themes
375      */
376     public function clearCache()
377     {
378         $this->_clearCacheOnDestroy = true;
379     }
380     
381     /**
382      * Return array of all valid fields that can be specified in the themedef.php file
383      * 
384      * @return array
385      */
386     public static function getThemeDefFields()
387     {
388         return array(
389             'name',
390             'description',
391             'directionality',
392             'dirName',
393             'parentTheme',
394             'version',
395             'colors',
396             'fonts',
397             'barChartColors',
398             'pieChartColors',
399             'group_tabs',
400             'ignoreParentFiles',
401             );
402     }
403     
404     /**
405      * Returns the file path of the current theme
406      *
407      * @return string
408      */
409     public function getFilePath()
410     {
411         return 'themes/'.$this->dirName;
412     }
413     
414     /**
415      * Returns the image path of the current theme
416      *
417      * @return string
418      */
419     public function getImagePath()
420     {
421         return $this->getFilePath().'/images';
422     }
423     
424     /**
425      * Returns the css path of the current theme
426      *
427      * @return string
428      */
429     public function getCSSPath()
430     {
431         return $this->getFilePath().'/css';
432     }
433     
434     /**
435      * Returns the javascript path of the current theme
436      *
437      * @return string
438      */
439     public function getJSPath()
440     {
441         return $this->getFilePath().'/js';
442     }
443     
444     /**
445      * Returns the tpl path of the current theme
446      *
447      * @return string
448      */
449     public function getTemplatePath()
450     {
451         return $this->getFilePath().'/tpls';
452     }
453     
454     /**
455      * Returns the file path of the theme defaults
456      *
457      * @return string
458      */
459     public final function getDefaultFilePath()
460     {
461         return 'themes/default';
462     }
463     
464     /**
465      * Returns the image path of the theme defaults
466      *
467      * @return string
468      */
469     public final function getDefaultImagePath()
470     {
471         return $this->getDefaultFilePath().'/images';
472     }
473     
474     /**
475      * Returns the css path of the theme defaults
476      *
477      * @return string
478      */
479     public final function getDefaultCSSPath()
480     {
481         return $this->getDefaultFilePath().'/css';
482     }
483     
484     /**
485      * Returns the template path of the theme defaults
486      *
487      * @return string
488      */
489     public final function getDefaultTemplatePath()
490     {
491         return $this->getDefaultFilePath().'/tpls';
492     }
493     
494     /**
495      * Returns the javascript path of the theme defaults
496      *
497      * @return string
498      */
499     public final function getDefaultJSPath()
500     {
501         return $this->getDefaultFilePath().'/js';
502     }
503     
504     /**
505      * Returns CSS for the current theme.
506      * 
507      * @param  $color string optional, specifies the css color file to use if the theme supports it; defaults to cookie value or theme default
508      * @param  $font  string optional, specifies the css font file to use if the theme supports it; defaults to cookie value or theme default
509      * @return string HTML code
510      */
511     public function getCSS(
512         $color = null,
513         $font = null
514         )
515     {
516         // include style.css file
517         $html = '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('yui.css').'" />';
518         $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('deprecated.css').'" />';
519         $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('style.css').'" />';
520                 
521         // for BC during upgrade
522         if ( !empty($this->colors) ) {
523             if ( isset($_SESSION['authenticated_user_theme_color']) && in_array($_SESSION['authenticated_user_theme_color'], $this->colors))
524                 $color = $_SESSION['authenticated_user_theme_color'];
525             else
526                 $color = $this->colors[0];
527             $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('colors.'.$color.'.css').'" id="current_color_style" />';
528         }
529         
530         if ( !empty($this->fonts) ) {
531             if ( isset($_SESSION['authenticated_user_theme_font']) && in_array($_SESSION['authenticated_user_theme_font'], $this->fonts))
532                 $font = $_SESSION['authenticated_user_theme_font'];
533             else
534                 $font = $this->fonts[0];
535             $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('fonts.'.$font.'.css').'" id="current_font_style" />';
536         }
537
538         return $html;
539     }
540     
541     /**
542      * Returns javascript for the current theme
543      * 
544      * @return string HTML code
545      */
546     public function getJS()
547     {
548         $styleJS = $this->getJSURL('style.js');
549         return <<<EOHTML
550 <script type="text/javascript" src="$styleJS"></script>
551 EOHTML;
552     }
553     
554     /**
555      * Returns the path for the tpl file in the current theme. If not found in the current theme, will revert
556      * to looking in the base theme.
557      * 
558      * @param  string $templateName tpl file name
559      * @return string path of tpl file to include
560      */
561     public function getTemplate(
562         $templateName
563         )
564     {
565         if ( isset($this->_templateCache[$templateName]) )
566             return $this->_templateCache[$templateName];
567         
568         $templatePath = '';
569         if (sugar_is_file('custom/'.$this->getTemplatePath().'/'.$templateName))
570             $templatePath = 'custom/'.$this->getTemplatePath().'/'.$templateName;
571         elseif (sugar_is_file($this->getTemplatePath().'/'.$templateName)) 
572             $templatePath = $this->getTemplatePath().'/'.$templateName;
573         elseif (isset($this->parentTheme) 
574                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
575                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getTemplate($templateName)) != '')
576             $templatePath = $filename;
577         elseif (sugar_is_file('custom/'.$this->getDefaultTemplatePath().'/'.$templateName))
578             $templatePath = 'custom/'.$this->getDefaultTemplatePath().'/'.$templateName;
579         elseif (sugar_is_file($this->getDefaultTemplatePath().'/'.$templateName))
580             $templatePath = $this->getDefaultTemplatePath().'/'.$templateName;
581         else {
582             $GLOBALS['log']->warn("Template $templateName not found");
583             return false;
584         }
585         
586         $this->_imageCache[$templateName] = $templatePath;
587         
588         return $templatePath;
589     }
590     
591     /**
592      * Returns an image tag for the given image.
593      *
594      * @param  string $image image name
595      * @param  string $other_attributes optional, other attributes to add to the image tag, not cached
596      * @param  string $width optional, defaults to the actual image's width
597      * @param  string $height optional, defaults to the actual image's height
598      * @return string HTML image tag
599      */
600     public function getImage(
601         $imageName,
602         $other_attributes = '',
603         $width = null,
604         $height = null,
605                 $ext = '.gif'
606         )
607     {
608         static $cached_results = array();
609         
610         $imageName .= $ext;
611         if(!empty($cached_results[$imageName]))
612             return $cached_results[$imageName]."$other_attributes />";
613         
614         $imageURL = $this->getImageURL($imageName,false);
615         if ( empty($imageURL) )
616             return false;
617         
618         $size = getimagesize($imageURL);
619         if ( is_null($width) ) 
620             $width = $size[0];
621         if ( is_null($height) ) 
622             $height = $size[1];
623         
624         // Cache everything but the other attributes....
625         $cached_results[$imageName] = "<img src=\"". getJSPath($imageURL) ."\" width=\"$width\" height=\"$height\" ";
626         
627         return $cached_results[$imageName] . "$other_attributes />";
628     }
629     
630     /**
631      * Returns the URL for an image in the current theme. If not found in the current theme, will revert
632      * to looking in the base theme.
633      * 
634      * @param  string $imageName image file name
635      * @param  bool   $addJSPath call getJSPath() with the results to add some unique image tracking support
636      * @return string path to image
637      */
638     public function getImageURL(
639         $imageName,
640         $addJSPath = true
641         )
642     {
643         if ( isset($this->_imageCache[$imageName]) ) {
644             if ( $addJSPath )
645                 return getJSPath($this->_imageCache[$imageName]);
646             else
647                 return $this->_imageCache[$imageName];
648         }
649         
650         $imagePath = '';
651         if (($filename = $this->_getImageFileName('custom/'.$this->getImagePath().'/'.$imageName)) != '')
652             $imagePath = $filename;
653         elseif (($filename = $this->_getImageFileName($this->getImagePath().'/'.$imageName)) != '')
654             $imagePath = $filename;
655         elseif (isset($this->parentTheme) 
656                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
657                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getImageURL($imageName,false)) != '')
658             $imagePath = $filename;
659         elseif (($filename = $this->_getImageFileName('custom/'.$this->getDefaultImagePath().'/'.$imageName)) != '')
660             $imagePath = $filename;
661         elseif (($filename = $this->_getImageFileName($this->getDefaultImagePath().'/'.$imageName)) != '')
662             $imagePath = $filename;
663         else {
664             $GLOBALS['log']->warn("Image $imageName not found");
665             return false;
666         }
667         
668         $this->_imageCache[$imageName] = $imagePath;
669         
670         if ( $addJSPath )
671             return getJSPath($imagePath);
672         
673         return $imagePath;
674     }
675     
676     /**
677      * Checks for an image using all of the accepted image extensions
678      *
679      * @param  string $imageName image file name
680      * @return string path to image
681      */
682     protected function _getImageFileName(
683         $imageName
684         )
685     {   
686         // return now if the extension matches that of which we are looking for
687         if ( sugar_is_file($imageName) ) 
688             return $imageName;
689         $pathParts = pathinfo($imageName);
690         foreach ( $this->imageExtensions as $extension )
691             if ( isset($pathParts['extension']) )
692                 if ( ( $extension != $pathParts['extension'] )
693                         && sugar_is_file($pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension) )
694                     return $pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension;
695         
696         return '';
697     }
698     
699     /**
700      * Returns the URL for the css file in the current theme. If not found in the current theme, will revert
701      * to looking in the base theme.
702      * 
703      * @param  string $cssFileName css file name
704      * @param  bool   $addJSPath call getJSPath() with the results to add some unique image tracking support
705      * @return string path of css file to include
706      */
707     public function getCSSURL(
708         $cssFileName,
709         $addJSPath = true
710         )
711     {
712         if ( isset($this->_cssCache[$cssFileName])) {
713             if ( $addJSPath )
714                 return getJSPath($this->_cssCache[$cssFileName]);
715             else
716                 return $this->_cssCache[$cssFileName];
717         }
718         
719         $cssFileContents = '';
720         if (isset($this->parentTheme) 
721                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
722                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getCSSURL($cssFileName,false)) != '')
723             $cssFileContents .= file_get_contents($filename);
724         else {
725             if (sugar_is_file($this->getDefaultCSSPath().'/'.$cssFileName))
726                 $cssFileContents .= file_get_contents($this->getDefaultCSSPath().'/'.$cssFileName);
727             if (sugar_is_file('custom/'.$this->getDefaultCSSPath().'/'.$cssFileName))
728                 $cssFileContents .= file_get_contents('custom/'.$this->getDefaultCSSPath().'/'.$cssFileName);
729         }
730         if (sugar_is_file($this->getCSSPath().'/'.$cssFileName))
731             $cssFileContents .= file_get_contents($this->getCSSPath().'/'.$cssFileName);
732         if (sugar_is_file('custom/'.$this->getCSSPath().'/'.$cssFileName))
733             $cssFileContents .= file_get_contents('custom/'.$this->getCSSPath().'/'.$cssFileName);
734         if (empty($cssFileContents)) {
735             $GLOBALS['log']->warn("CSS File $cssFileName not found");
736             return false;
737         }
738         
739         // fix any image references that may be defined in css files
740         $cssFileContents = str_ireplace("entryPoint=getImage&",
741             "entryPoint=getImage&themeName={$this->dirName}&",
742             $cssFileContents);
743         
744         // create the cached file location
745         $cssFilePath = create_cache_directory($this->getCSSPath()."/$cssFileName");
746         
747         // if this is the style.css file, prepend the base.css and calendar-win2k-cold-1.css 
748         // files before the theme styles
749         if ( $cssFileName == 'style.css' && !isset($this->parentTheme) ) {
750             if ( inDeveloperMode() )
751                 $cssFileContents = file_get_contents('include/javascript/yui/build/base/base.css') . $cssFileContents;
752             else
753                 $cssFileContents = file_get_contents('include/javascript/yui/build/base/base-min.css') . $cssFileContents;
754         }
755         
756         // minify the css
757         if ( !inDeveloperMode() && !sugar_is_file($cssFilePath) ) {
758             $cssFileContents = cssmin::minify($cssFileContents);
759         }
760         
761         // now write the css to cache
762         sugar_file_put_contents($cssFilePath,$cssFileContents);
763         
764         $this->_cssCache[$cssFileName] = $cssFilePath;
765         
766         if ( $addJSPath )
767             return getJSPath($cssFilePath);
768         
769         return $cssFilePath;
770     }
771     
772     /**
773      * Returns the URL for an image in the current theme. If not found in the current theme, will revert
774      * to looking in the base theme.
775      * 
776      * @param  string $jsFileName js file name
777      * @param  bool   $addJSPath call getJSPath() with the results to add some unique image tracking support
778      * @return string path to js file
779      */
780     public function getJSURL(
781         $jsFileName,
782         $addJSPath = true
783         )
784     {
785         if ( isset($this->_jsCache[$jsFileName])) {
786             if ( $addJSPath )
787                 return getJSPath($this->_jsCache[$jsFileName]);
788             else
789                 return $this->_jsCache[$jsFileName];
790         }
791         
792         $jsFileContents = '';
793         
794         if (isset($this->parentTheme) 
795                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
796                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getJSURL($jsFileName,false)) != '' && !in_array($jsFileName,$this->ignoreParentFiles))
797             $jsFileContents .= file_get_contents($filename);
798         else {
799             if (sugar_is_file($this->getDefaultJSPath().'/'.$jsFileName))
800                 $jsFileContents .= file_get_contents($this->getDefaultJSPath().'/'.$jsFileName);
801             if (sugar_is_file('custom/'.$this->getDefaultJSPath().'/'.$jsFileName))
802                 $jsFileContents .= file_get_contents('custom/'.$this->getDefaultJSPath().'/'.$jsFileName);
803         }
804         if (sugar_is_file($this->getJSPath().'/'.$jsFileName))
805             $jsFileContents .= file_get_contents($this->getJSPath().'/'.$jsFileName);
806         if (sugar_is_file('custom/'.$this->getJSPath().'/'.$jsFileName))
807             $jsFileContents .= file_get_contents('custom/'.$this->getJSPath().'/'.$jsFileName);
808         if (empty($jsFileContents)) {
809             $GLOBALS['log']->warn("Javascript File $jsFileName not found");
810             return false;
811         }
812         
813         // create the cached file location
814         $jsFilePath = create_cache_directory($this->getJSPath()."/$jsFileName");
815         
816         // minify the js
817         if ( !inDeveloperMode()&& !sugar_is_file(str_replace('.js','-min.js',$jsFilePath)) ) {
818             $jsFileContents = JSMin::minify($jsFileContents);
819             $jsFilePath = str_replace('.js','-min.js',$jsFilePath);
820         }
821         
822         // now write the js to cache
823         sugar_file_put_contents($jsFilePath,$jsFileContents);
824
825         $this->_jsCache[$jsFileName] = $jsFilePath;
826         
827         if ( $addJSPath )
828             return getJSPath($jsFilePath);
829         
830         return $jsFilePath;
831     }
832     
833     /**
834      * Returns an array of all of the images available for the current theme
835      *
836      * @return array
837      */
838     public function getAllImages()
839     {
840         // first, lets get all the paths of where to look
841         $pathsToSearch = array($this->getImagePath());
842         $theme = $this;
843         while (isset($theme->parentTheme) && SugarThemeRegistry::get($theme->parentTheme) instanceOf SugarTheme ) {
844             $theme = SugarThemeRegistry::get($theme->parentTheme);
845             $pathsToSearch[] = $theme->getImagePath();
846         }
847         $pathsToSearch[] = $this->getDefaultImagePath();
848         
849         // now build the array
850         $imageArray = array();
851         foreach ( $pathsToSearch as $path )
852         {
853             if (!sugar_is_dir($path)) $path = "custom/$path";
854             if (sugar_is_dir($path) && is_readable($path) && $dir = opendir($path)) {
855                 while (($file = readdir($dir)) !== false) {
856                     if ($file == ".." 
857                             || $file == "."
858                             || $file == ".svn"
859                             || $file == "CVS" 
860                             || $file == "Attic"
861                             )
862                         continue;
863                     if ( !isset($imageArray[$file]) )
864                         $imageArray[$file] = $this->getImageURL($file,false);
865                 }
866                 closedir($dir);
867             }
868         }
869         
870         ksort($imageArray);
871         
872         return $imageArray;
873     }
874
875 }
876
877 /**
878  * Registry for all the current classes in the system
879  */
880 class SugarThemeRegistry
881 {
882     /**
883      * Array of all themes and thier object
884      *
885      * @var array
886      */
887     private static $_themes = array();
888     
889     /**
890      * Name of the current theme; corresponds to an index key in SugarThemeRegistry::$_themes
891      *
892      * @var string
893      */
894     private static $_currentTheme;
895     
896     /**
897      * Disable the constructor since this will be a singleton
898      */
899     private function __construct() {}
900     
901     /**
902      * Adds a new theme to the registry
903      *
904      * @param $themedef array
905      */
906     public static function add(
907         array $themedef
908         )
909     {
910         // make sure the we know the sugar version
911         if ( !isset($GLOBALS['sugar_version']) ) {
912             include('sugar_version.php');
913             $GLOBALS['sugar_version'] = $sugar_version;
914         }
915         
916         // Assume theme is designed for 5.5.x if not specified otherwise
917         if ( !isset($themedef['version']) )
918             $themedef['version']['regex_matches'] = array('5\.5\.*');
919         
920         // Check to see if theme is valid for this version of Sugar; return false if not
921         $version_ok = false;
922         if( isset($themedef['version']['exact_matches']) ){
923             $matches_empty = false;
924             foreach( $themedef['version']['exact_matches'] as $match ){
925                 if( $match == $GLOBALS['sugar_version'] ){
926                     $version_ok = true;
927                 }
928             }
929         }
930         if( !$version_ok && isset($themedef['version']['regex_matches']) ){
931             $matches_empty = false;
932             foreach( $themedef['version']['regex_matches'] as $match ){
933                 if( preg_match( "/$match/", $GLOBALS['sugar_version'] ) ){
934                     $version_ok = true;
935                 }
936             }
937         }
938         if ( !$version_ok )
939             return false;
940         
941         $theme = new SugarTheme($themedef);
942         self::$_themes[$theme->dirName] = $theme;
943     }
944     
945     /**
946      * Removes a new theme from the registry
947      *
948      * @param $themeName string
949      */
950     public static function remove(
951         $themeName
952         )
953     {
954         if ( self::exists($themeName) )
955             unset(self::$_themes[$themeName]);
956     }
957     
958     /**
959      * Returns a theme object in the registry specified by the given $themeName
960      *
961      * @param $themeName string
962      */
963     public static function get(
964         $themeName
965         )
966     {
967         if ( isset(self::$_themes[$themeName]) )
968             return self::$_themes[$themeName];
969     }
970     
971     /**
972      * Returns the current theme object
973      *
974      * @return SugarTheme object
975      */
976     public static function current()
977     {
978         if ( !isset(self::$_currentTheme) )
979             self::buildRegistry();
980         
981         return self::$_themes[self::$_currentTheme];
982     }
983     
984     /**
985      * Returns the default theme object
986      *
987      * @return SugarTheme object
988      */
989     public static function getDefault()
990     {
991         if ( !isset(self::$_currentTheme) )
992             self::buildRegistry();
993         
994         if ( isset($GLOBALS['sugar_config']['default_theme']) && self::exists($GLOBALS['sugar_config']['default_theme']) ) {
995             return self::get($GLOBALS['sugar_config']['default_theme']);
996         }
997             
998         return self::get(array_pop(array_keys(self::availableThemes())));
999     }
1000     
1001     /**
1002      * Returns true if a theme object specified by the given $themeName exists in the registry
1003      *
1004      * @param  $themeName string
1005      * @return bool
1006      */
1007     public static function exists(
1008         $themeName
1009         )
1010     {
1011         return (self::get($themeName) !== null);
1012     }
1013     
1014     /**
1015      * Sets the given $themeName to be the current theme
1016      *
1017      * @param  $themeName string
1018      */
1019     public static function set(
1020         $themeName
1021         )
1022     {
1023         if ( !self::exists($themeName) ) 
1024             return false;
1025         
1026         self::$_currentTheme = $themeName;
1027         
1028         // set some of the expected globals
1029         $GLOBALS['barChartColors'] = self::current()->barChartColors;
1030         $GLOBALS['pieChartColors'] = self::current()->pieChartColors;
1031     }
1032     
1033     /**
1034      * Builds the theme registry
1035      */
1036     public static function buildRegistry()
1037     {
1038         self::$_themes = array();
1039         $dirs = array("themes/","custom/themes/");
1040         
1041         // check for a default themedef file
1042         $themedefDefault = array();
1043         if ( sugar_is_file("custom/themes/default/themedef.php") ) {
1044             $themedef = array();
1045             require("custom/themes/default/themedef.php");
1046             $themedefDefault = $themedef;
1047         }
1048         
1049         foreach ($dirs as $dirPath ) {
1050             if (sugar_is_dir('./'.$dirPath) && is_readable('./'.$dirPath) && $dir = opendir('./'.$dirPath)) {
1051                 while (($file = readdir($dir)) !== false) {
1052                     if ($file == ".." 
1053                             || $file == "."
1054                             || $file == ".svn"
1055                             || $file == "CVS" 
1056                             || $file == "Attic" 
1057                             || $file == "default" 
1058                             || !sugar_is_dir("./$dirPath".$file)
1059                             || !sugar_is_file("./{$dirPath}{$file}/themedef.php")
1060                             )
1061                         continue;
1062                     $themedef = array();
1063                     require("./{$dirPath}{$file}/themedef.php");
1064                     $themedef = array_merge($themedef,$themedefDefault);
1065                     $themedef['dirName'] = $file;
1066                     // check for theme already existing in the registry
1067                     // if so, then it will override the current one
1068                     if ( self::exists($themedef['dirName']) ) {
1069                         $existingTheme = self::get($themedef['dirName']);
1070                         foreach ( SugarTheme::getThemeDefFields() as $field )
1071                             if ( !isset($themedef[$field]) )
1072                                 $themedef[$field] = $existingTheme->$field;
1073                         self::remove($themedef['dirName']);
1074                     }
1075                     if ( isset($themedef['name']) ) {
1076                         self::add($themedef);
1077                     }
1078                 }
1079                 closedir($dir);
1080             }
1081         }
1082         
1083         // default to setting the default theme as the current theme
1084         if ( !isset($GLOBALS['sugar_config']['default_theme']) || !self::set($GLOBALS['sugar_config']['default_theme']) ) {
1085             if ( count(self::availableThemes()) == 0 )
1086                 sugar_die('No valid themes are found on this instance');
1087             else
1088                 self::set(array_pop(array_keys(self::availableThemes())));
1089         }
1090     }
1091     
1092     /**
1093      * Returns an array of available themes. Designed to be absorbed into get_select_options_with_id()
1094      *
1095      * @return array
1096      */
1097     public static function availableThemes()
1098     {
1099         $themelist = array();
1100         $disabledThemes = array();
1101         if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
1102             $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
1103         
1104         foreach ( self::$_themes as $themename => $themeobject ) {
1105             if ( in_array($themename,$disabledThemes) )
1106                 continue;
1107             $themelist[$themeobject->dirName] = $themeobject->name;
1108         }
1109         asort($themelist, SORT_STRING);
1110         return $themelist;
1111     }
1112     
1113     /**
1114      * Returns an array of un-available themes. Designed used with the theme selector in the admin panel
1115      *
1116      * @return array
1117      */
1118     public static function unAvailableThemes()
1119     {
1120         $themelist = array();
1121         $disabledThemes = array();
1122         if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
1123             $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
1124         
1125         foreach ( self::$_themes as $themename => $themeobject ) {
1126             if ( in_array($themename,$disabledThemes) )
1127                 $themelist[$themeobject->dirName] = $themeobject->name;
1128         }
1129         
1130         return $themelist;
1131     }
1132     
1133     /**
1134      * Returns an array of all themes found in the current installation
1135      *
1136      * @return array
1137      */
1138     public static function allThemes()
1139     {
1140         $themelist = array();
1141         
1142         foreach ( self::$_themes as $themename => $themeobject )
1143             $themelist[$themeobject->dirName] = $themeobject->name;
1144         
1145         return $themelist;
1146     }
1147     
1148     /**
1149      * Clears out the cached path locations for all themes
1150      */
1151     public static function clearAllCaches()
1152     {
1153         foreach ( self::$_themes as $themeobject ) {
1154             $themeobject->clearCache();
1155         }
1156     }
1157 }