]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/SugarTheme/SugarTheme.php
Release 6.3.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     public function clearJSCache()
374     {
375         $this->_jsCache = array();
376     }
377
378     /**
379      * Clears out the caches used for this themes
380      */
381     public function clearCache()
382     {
383         $this->_clearCacheOnDestroy = true;
384     }
385
386     /**
387      * Return array of all valid fields that can be specified in the themedef.php file
388      *
389      * @return array
390      */
391     public static function getThemeDefFields()
392     {
393         return array(
394             'name',
395             'description',
396             'directionality',
397             'dirName',
398             'parentTheme',
399             'version',
400             'colors',
401             'fonts',
402             'barChartColors',
403             'pieChartColors',
404             'group_tabs',
405             'ignoreParentFiles',
406             );
407     }
408
409     /**
410      * Returns the file path of the current theme
411      *
412      * @return string
413      */
414     public function getFilePath()
415     {
416         return 'themes/'.$this->dirName;
417     }
418
419     /**
420      * Returns the image path of the current theme
421      *
422      * @return string
423      */
424     public function getImagePath()
425     {
426         return $this->getFilePath().'/images';
427     }
428
429     /**
430      * Returns the css path of the current theme
431      *
432      * @return string
433      */
434     public function getCSSPath()
435     {
436         return $this->getFilePath().'/css';
437     }
438
439     /**
440      * Returns the javascript path of the current theme
441      *
442      * @return string
443      */
444     public function getJSPath()
445     {
446         return $this->getFilePath().'/js';
447     }
448
449     /**
450      * Returns the tpl path of the current theme
451      *
452      * @return string
453      */
454     public function getTemplatePath()
455     {
456         return $this->getFilePath().'/tpls';
457     }
458
459     /**
460      * Returns the file path of the theme defaults
461      *
462      * @return string
463      */
464     public final function getDefaultFilePath()
465     {
466         return 'themes/default';
467     }
468
469     /**
470      * Returns the image path of the theme defaults
471      *
472      * @return string
473      */
474     public final function getDefaultImagePath()
475     {
476         return $this->getDefaultFilePath().'/images';
477     }
478
479     /**
480      * Returns the css path of the theme defaults
481      *
482      * @return string
483      */
484     public final function getDefaultCSSPath()
485     {
486         return $this->getDefaultFilePath().'/css';
487     }
488
489     /**
490      * Returns the template path of the theme defaults
491      *
492      * @return string
493      */
494     public final function getDefaultTemplatePath()
495     {
496         return $this->getDefaultFilePath().'/tpls';
497     }
498
499     /**
500      * Returns the javascript path of the theme defaults
501      *
502      * @return string
503      */
504     public final function getDefaultJSPath()
505     {
506         return $this->getDefaultFilePath().'/js';
507     }
508
509     /**
510      * Returns CSS for the current theme.
511      *
512      * @param  $color string optional, specifies the css color file to use if the theme supports it; defaults to cookie value or theme default
513      * @param  $font  string optional, specifies the css font file to use if the theme supports it; defaults to cookie value or theme default
514      * @return string HTML code
515      */
516     public function getCSS(
517         $color = null,
518         $font = null
519         )
520     {
521         // include style.css file
522         $html = '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('yui.css').'" />';
523         $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('deprecated.css').'" />';
524         $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('style.css').'" />';
525
526         // for BC during upgrade
527         if ( !empty($this->colors) ) {
528             if ( isset($_SESSION['authenticated_user_theme_color']) && in_array($_SESSION['authenticated_user_theme_color'], $this->colors))
529                 $color = $_SESSION['authenticated_user_theme_color'];
530             else
531                 $color = $this->colors[0];
532             $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('colors.'.$color.'.css').'" id="current_color_style" />';
533         }
534
535         if ( !empty($this->fonts) ) {
536             if ( isset($_SESSION['authenticated_user_theme_font']) && in_array($_SESSION['authenticated_user_theme_font'], $this->fonts))
537                 $font = $_SESSION['authenticated_user_theme_font'];
538             else
539                 $font = $this->fonts[0];
540             $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('fonts.'.$font.'.css').'" id="current_font_style" />';
541         }
542
543         return $html;
544     }
545
546     /**
547      * Returns javascript for the current theme
548      *
549      * @return string HTML code
550      */
551     public function getJS()
552     {
553         $styleJS = $this->getJSURL('style.js');
554         return <<<EOHTML
555 <script type="text/javascript" src="$styleJS"></script>
556 EOHTML;
557     }
558
559     /**
560      * Returns the path for the tpl file in the current theme. If not found in the current theme, will revert
561      * to looking in the base theme.
562      *
563      * @param  string $templateName tpl file name
564      * @return string path of tpl file to include
565      */
566     public function getTemplate(
567         $templateName
568         )
569     {
570         if ( isset($this->_templateCache[$templateName]) )
571             return $this->_templateCache[$templateName];
572
573         $templatePath = '';
574         if (sugar_is_file('custom/'.$this->getTemplatePath().'/'.$templateName))
575             $templatePath = 'custom/'.$this->getTemplatePath().'/'.$templateName;
576         elseif (sugar_is_file($this->getTemplatePath().'/'.$templateName))
577             $templatePath = $this->getTemplatePath().'/'.$templateName;
578         elseif (isset($this->parentTheme)
579                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
580                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getTemplate($templateName)) != '')
581             $templatePath = $filename;
582         elseif (sugar_is_file('custom/'.$this->getDefaultTemplatePath().'/'.$templateName))
583             $templatePath = 'custom/'.$this->getDefaultTemplatePath().'/'.$templateName;
584         elseif (sugar_is_file($this->getDefaultTemplatePath().'/'.$templateName))
585             $templatePath = $this->getDefaultTemplatePath().'/'.$templateName;
586         else {
587             $GLOBALS['log']->warn("Template $templateName not found");
588             return false;
589         }
590
591         $this->_imageCache[$templateName] = $templatePath;
592
593         return $templatePath;
594     }
595
596     /**
597      * Returns an image tag for the given image.
598      *
599      * @param  string $image image name
600      * @param  string $other_attributes optional, other attributes to add to the image tag, not cached
601      * @param  string $width optional, defaults to the actual image's width
602      * @param  string $height optional, defaults to the actual image's height
603      * @return string HTML image tag
604      */
605     public function getImage(
606         $imageName,
607         $other_attributes = '',
608         $width = null,
609         $height = null,
610                 $ext = '.gif'
611         )
612     {
613         static $cached_results = array();
614
615         $imageName .= $ext;
616         if(!empty($cached_results[$imageName]))
617             return $cached_results[$imageName]."$other_attributes />";
618
619         $imageURL = $this->getImageURL($imageName,false);
620         if ( empty($imageURL) )
621             return false;
622
623         $size = getimagesize($imageURL);
624         if ( is_null($width) )
625             $width = $size[0];
626         if ( is_null($height) )
627             $height = $size[1];
628
629         // Cache everything but the other attributes....
630         $cached_results[$imageName] = "<img src=\"". getJSPath($imageURL) ."\" width=\"$width\" height=\"$height\" ";
631
632         return $cached_results[$imageName] . "$other_attributes />";
633     }
634
635     /**
636      * Returns the URL for an image in the current theme. If not found in the current theme, will revert
637      * to looking in the base theme.
638      *
639      * @param  string $imageName image file name
640      * @param  bool   $addJSPath call getJSPath() with the results to add some unique image tracking support
641      * @return string path to image
642      */
643     public function getImageURL(
644         $imageName,
645         $addJSPath = true
646         )
647     {
648         if ( isset($this->_imageCache[$imageName]) ) {
649             if ( $addJSPath )
650                 return getJSPath($this->_imageCache[$imageName]);
651             else
652                 return $this->_imageCache[$imageName];
653         }
654         $imagePath = '';
655         if (($filename = $this->_getImageFileName('custom/'.$this->getImagePath().'/'.$imageName)) != '')
656             $imagePath = $filename;
657         elseif (($filename = $this->_getImageFileName($this->getImagePath().'/'.$imageName)) != '')
658             $imagePath = $filename;
659         elseif (isset($this->parentTheme)
660                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
661                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getImageURL($imageName,false)) != '')
662             $imagePath = $filename;
663         elseif (($filename = $this->_getImageFileName('custom/'.$this->getDefaultImagePath().'/'.$imageName)) != '')
664             $imagePath = $filename;
665         elseif (($filename = $this->_getImageFileName($this->getDefaultImagePath().'/'.$imageName)) != '')
666             $imagePath = $filename;
667         else {
668             $GLOBALS['log']->warn("Image $imageName not found");
669             return false;
670         }
671
672         $this->_imageCache[$imageName] = $imagePath;
673
674         if ( $addJSPath )
675             return getJSPath($imagePath);
676
677         return $imagePath;
678     }
679
680     /**
681      * Checks for an image using all of the accepted image extensions
682      *
683      * @param  string $imageName image file name
684      * @return string path to image
685      */
686     protected function _getImageFileName(
687         $imageName
688         )
689     {
690         // return now if the extension matches that of which we are looking for
691         if ( sugar_is_file($imageName) )
692             return $imageName;
693         $pathParts = pathinfo($imageName);
694         foreach ( $this->imageExtensions as $extension )
695             if ( isset($pathParts['extension']) )
696                 if ( ( $extension != $pathParts['extension'] )
697                         && sugar_is_file($pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension) )
698                     return $pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension;
699
700         return '';
701     }
702
703     /**
704      * Returns the URL for the css file in the current theme. If not found in the current theme, will revert
705      * to looking in the base theme.
706      *
707      * @param  string $cssFileName css file name
708      * @param  bool   $addJSPath call getJSPath() with the results to add some unique image tracking support
709      * @return string path of css file to include
710      */
711     public function getCSSURL(
712         $cssFileName,
713         $addJSPath = true
714         )
715     {
716         if ( isset($this->_cssCache[$cssFileName])) {
717             if ( $addJSPath )
718                 return getJSPath($this->_cssCache[$cssFileName]);
719             else
720                 return $this->_cssCache[$cssFileName];
721         }
722
723         $cssFileContents = '';
724         if (isset($this->parentTheme)
725                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
726                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getCSSURL($cssFileName,false)) != '')
727             $cssFileContents .= file_get_contents($filename);
728         else {
729             if (sugar_is_file($this->getDefaultCSSPath().'/'.$cssFileName))
730                 $cssFileContents .= file_get_contents($this->getDefaultCSSPath().'/'.$cssFileName);
731             if (sugar_is_file('custom/'.$this->getDefaultCSSPath().'/'.$cssFileName))
732                 $cssFileContents .= file_get_contents('custom/'.$this->getDefaultCSSPath().'/'.$cssFileName);
733         }
734         if (sugar_is_file($this->getCSSPath().'/'.$cssFileName))
735             $cssFileContents .= file_get_contents($this->getCSSPath().'/'.$cssFileName);
736         if (sugar_is_file('custom/'.$this->getCSSPath().'/'.$cssFileName))
737             $cssFileContents .= file_get_contents('custom/'.$this->getCSSPath().'/'.$cssFileName);
738         if (empty($cssFileContents)) {
739             $GLOBALS['log']->warn("CSS File $cssFileName not found");
740             return false;
741         }
742
743         // fix any image references that may be defined in css files
744         $cssFileContents = str_ireplace("entryPoint=getImage&",
745             "entryPoint=getImage&themeName={$this->dirName}&",
746             $cssFileContents);
747
748         // create the cached file location
749         $cssFilePath = create_cache_directory($this->getCSSPath()."/$cssFileName");
750
751         // if this is the style.css file, prepend the base.css and calendar-win2k-cold-1.css
752         // files before the theme styles
753         if ( $cssFileName == 'style.css' && !isset($this->parentTheme) ) {
754             if ( inDeveloperMode() )
755                 $cssFileContents = file_get_contents('include/javascript/yui/build/base/base.css') . $cssFileContents;
756             else
757                 $cssFileContents = file_get_contents('include/javascript/yui/build/base/base-min.css') . $cssFileContents;
758         }
759
760         // minify the css
761         if ( !inDeveloperMode() && !sugar_is_file($cssFilePath) ) {
762             $cssFileContents = cssmin::minify($cssFileContents);
763         }
764
765         // now write the css to cache
766         sugar_file_put_contents($cssFilePath,$cssFileContents);
767
768         $this->_cssCache[$cssFileName] = $cssFilePath;
769
770         if ( $addJSPath )
771             return getJSPath($cssFilePath);
772
773         return $cssFilePath;
774     }
775
776     /**
777      * Returns the URL for an image in the current theme. If not found in the current theme, will revert
778      * to looking in the base theme.
779      *
780      * @param  string $jsFileName js file name
781      * @param  bool   $addJSPath call getJSPath() with the results to add some unique image tracking support
782      * @return string path to js file
783      */
784     public function getJSURL(
785         $jsFileName,
786         $addJSPath = true
787         )
788     {
789         if ( isset($this->_jsCache[$jsFileName])) {
790             if ( $addJSPath )
791                 return getJSPath($this->_jsCache[$jsFileName]);
792             else
793                 return $this->_jsCache[$jsFileName];
794         }
795
796         $jsFileContents = '';
797
798         if (isset($this->parentTheme)
799                 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
800                 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getJSURL($jsFileName,false)) != '' && !in_array($jsFileName,$this->ignoreParentFiles))
801             $jsFileContents .= file_get_contents($filename);
802         else {
803             if (sugar_is_file($this->getDefaultJSPath().'/'.$jsFileName))
804                 $jsFileContents .= file_get_contents($this->getDefaultJSPath().'/'.$jsFileName);
805             if (sugar_is_file('custom/'.$this->getDefaultJSPath().'/'.$jsFileName))
806                 $jsFileContents .= file_get_contents('custom/'.$this->getDefaultJSPath().'/'.$jsFileName);
807         }
808         if (sugar_is_file($this->getJSPath().'/'.$jsFileName))
809             $jsFileContents .= file_get_contents($this->getJSPath().'/'.$jsFileName);
810         if (sugar_is_file('custom/'.$this->getJSPath().'/'.$jsFileName))
811             $jsFileContents .= file_get_contents('custom/'.$this->getJSPath().'/'.$jsFileName);
812         if (empty($jsFileContents)) {
813             $GLOBALS['log']->warn("Javascript File $jsFileName not found");
814             return false;
815         }
816
817         // create the cached file location
818         $jsFilePath = create_cache_directory($this->getJSPath()."/$jsFileName");
819
820         // minify the js
821         if ( !inDeveloperMode()&& !sugar_is_file(str_replace('.js','-min.js',$jsFilePath)) ) {
822             $jsFileContents = JSMin::minify($jsFileContents);
823             $jsFilePath = str_replace('.js','-min.js',$jsFilePath);
824         }
825
826         // now write the js to cache
827         sugar_file_put_contents($jsFilePath,$jsFileContents);
828
829         $this->_jsCache[$jsFileName] = $jsFilePath;
830
831         if ( $addJSPath )
832             return getJSPath($jsFilePath);
833
834         return $jsFilePath;
835     }
836
837     /**
838      * Returns an array of all of the images available for the current theme
839      *
840      * @return array
841      */
842     public function getAllImages()
843     {
844         // first, lets get all the paths of where to look
845         $pathsToSearch = array($this->getImagePath());
846         $theme = $this;
847         while (isset($theme->parentTheme) && SugarThemeRegistry::get($theme->parentTheme) instanceOf SugarTheme ) {
848             $theme = SugarThemeRegistry::get($theme->parentTheme);
849             $pathsToSearch[] = $theme->getImagePath();
850         }
851         $pathsToSearch[] = $this->getDefaultImagePath();
852
853         // now build the array
854         $imageArray = array();
855         foreach ( $pathsToSearch as $path )
856         {
857             if (!sugar_is_dir($path)) $path = "custom/$path";
858             if (sugar_is_dir($path) && is_readable($path) && $dir = opendir($path)) {
859                 while (($file = readdir($dir)) !== false) {
860                     if ($file == ".."
861                             || $file == "."
862                             || $file == ".svn"
863                             || $file == "CVS"
864                             || $file == "Attic"
865                             )
866                         continue;
867                     if ( !isset($imageArray[$file]) )
868                         $imageArray[$file] = $this->getImageURL($file,false);
869                 }
870                 closedir($dir);
871             }
872         }
873
874         ksort($imageArray);
875
876         return $imageArray;
877     }
878
879 }
880
881 /**
882  * Registry for all the current classes in the system
883  */
884 class SugarThemeRegistry
885 {
886     /**
887      * Array of all themes and thier object
888      *
889      * @var array
890      */
891     private static $_themes = array();
892
893     /**
894      * Name of the current theme; corresponds to an index key in SugarThemeRegistry::$_themes
895      *
896      * @var string
897      */
898     private static $_currentTheme;
899
900     /**
901      * Disable the constructor since this will be a singleton
902      */
903     private function __construct() {}
904
905     /**
906      * Adds a new theme to the registry
907      *
908      * @param $themedef array
909      */
910     public static function add(
911         array $themedef
912         )
913     {
914         // make sure the we know the sugar version
915         if ( !isset($GLOBALS['sugar_version']) ) {
916             include('sugar_version.php');
917             $GLOBALS['sugar_version'] = $sugar_version;
918         }
919
920         // Assume theme is designed for 5.5.x if not specified otherwise
921         if ( !isset($themedef['version']) )
922             $themedef['version']['regex_matches'] = array('5\.5\.*');
923
924         // Check to see if theme is valid for this version of Sugar; return false if not
925         $version_ok = false;
926         if( isset($themedef['version']['exact_matches']) ){
927             $matches_empty = false;
928             foreach( $themedef['version']['exact_matches'] as $match ){
929                 if( $match == $GLOBALS['sugar_version'] ){
930                     $version_ok = true;
931                 }
932             }
933         }
934         if( !$version_ok && isset($themedef['version']['regex_matches']) ){
935             $matches_empty = false;
936             foreach( $themedef['version']['regex_matches'] as $match ){
937                 if( preg_match( "/$match/", $GLOBALS['sugar_version'] ) ){
938                     $version_ok = true;
939                 }
940             }
941         }
942         if ( !$version_ok )
943             return false;
944
945         $theme = new SugarTheme($themedef);
946         self::$_themes[$theme->dirName] = $theme;
947     }
948
949     /**
950      * Removes a new theme from the registry
951      *
952      * @param $themeName string
953      */
954     public static function remove(
955         $themeName
956         )
957     {
958         if ( self::exists($themeName) )
959             unset(self::$_themes[$themeName]);
960     }
961
962     /**
963      * Returns a theme object in the registry specified by the given $themeName
964      *
965      * @param $themeName string
966      */
967     public static function get(
968         $themeName
969         )
970     {
971         if ( isset(self::$_themes[$themeName]) )
972             return self::$_themes[$themeName];
973     }
974
975     /**
976      * Returns the current theme object
977      *
978      * @return SugarTheme object
979      */
980     public static function current()
981     {
982         if ( !isset(self::$_currentTheme) )
983             self::buildRegistry();
984
985         return self::$_themes[self::$_currentTheme];
986     }
987
988     /**
989      * Returns the default theme object
990      *
991      * @return SugarTheme object
992      */
993     public static function getDefault()
994     {
995         if ( !isset(self::$_currentTheme) )
996             self::buildRegistry();
997
998         if ( isset($GLOBALS['sugar_config']['default_theme']) && self::exists($GLOBALS['sugar_config']['default_theme']) ) {
999             return self::get($GLOBALS['sugar_config']['default_theme']);
1000         }
1001
1002         return self::get(array_pop(array_keys(self::availableThemes())));
1003     }
1004
1005     /**
1006      * Returns true if a theme object specified by the given $themeName exists in the registry
1007      *
1008      * @param  $themeName string
1009      * @return bool
1010      */
1011     public static function exists(
1012         $themeName
1013         )
1014     {
1015         return (self::get($themeName) !== null);
1016     }
1017
1018     /**
1019      * Sets the given $themeName to be the current theme
1020      *
1021      * @param  $themeName string
1022      */
1023     public static function set(
1024         $themeName
1025         )
1026     {
1027         if ( !self::exists($themeName) )
1028             return false;
1029
1030         self::$_currentTheme = $themeName;
1031
1032         // set some of the expected globals
1033         $GLOBALS['barChartColors'] = self::current()->barChartColors;
1034         $GLOBALS['pieChartColors'] = self::current()->pieChartColors;
1035         return true;
1036     }
1037
1038     /**
1039      * Builds the theme registry
1040      */
1041     public static function buildRegistry()
1042     {
1043         self::$_themes = array();
1044         $dirs = array("themes/","custom/themes/");
1045
1046         // check for a default themedef file
1047         $themedefDefault = array();
1048         if ( sugar_is_file("custom/themes/default/themedef.php") ) {
1049             $themedef = array();
1050             require("custom/themes/default/themedef.php");
1051             $themedefDefault = $themedef;
1052         }
1053
1054         foreach ($dirs as $dirPath ) {
1055             if (sugar_is_dir('./'.$dirPath) && is_readable('./'.$dirPath) && $dir = opendir('./'.$dirPath)) {
1056                 while (($file = readdir($dir)) !== false) {
1057                     if ($file == ".."
1058                             || $file == "."
1059                             || $file == ".svn"
1060                             || $file == "CVS"
1061                             || $file == "Attic"
1062                             || $file == "default"
1063                             || !sugar_is_dir("./$dirPath".$file)
1064                             || !sugar_is_file("./{$dirPath}{$file}/themedef.php")
1065                             )
1066                         continue;
1067                     $themedef = array();
1068                     require("./{$dirPath}{$file}/themedef.php");
1069                     $themedef = array_merge($themedef,$themedefDefault);
1070                     $themedef['dirName'] = $file;
1071                     // check for theme already existing in the registry
1072                     // if so, then it will override the current one
1073                     if ( self::exists($themedef['dirName']) ) {
1074                         $existingTheme = self::get($themedef['dirName']);
1075                         foreach ( SugarTheme::getThemeDefFields() as $field )
1076                             if ( !isset($themedef[$field]) )
1077                                 $themedef[$field] = $existingTheme->$field;
1078                         self::remove($themedef['dirName']);
1079                     }
1080                     if ( isset($themedef['name']) ) {
1081                         self::add($themedef);
1082                     }
1083                 }
1084                 closedir($dir);
1085             }
1086         }
1087         // default to setting the default theme as the current theme
1088         if ( !isset($GLOBALS['sugar_config']['default_theme']) || !self::set($GLOBALS['sugar_config']['default_theme']) ) {
1089             if ( count(self::availableThemes()) == 0 )
1090                 sugar_die('No valid themes are found on this instance');
1091             else
1092                 self::set(array_pop(array_keys(self::availableThemes())));
1093         }
1094     }
1095
1096     /**
1097      * Returns an array of available themes. Designed to be absorbed into get_select_options_with_id()
1098      *
1099      * @return array
1100      */
1101     public static function availableThemes()
1102     {
1103         $themelist = array();
1104         $disabledThemes = array();
1105         if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
1106             $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
1107
1108         foreach ( self::$_themes as $themename => $themeobject ) {
1109             if ( in_array($themename,$disabledThemes) )
1110                 continue;
1111             $themelist[$themeobject->dirName] = $themeobject->name;
1112         }
1113         asort($themelist, SORT_STRING);
1114         return $themelist;
1115     }
1116
1117     /**
1118      * Returns an array of un-available themes. Designed used with the theme selector in the admin panel
1119      *
1120      * @return array
1121      */
1122     public static function unAvailableThemes()
1123     {
1124         $themelist = array();
1125         $disabledThemes = array();
1126         if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
1127             $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
1128
1129         foreach ( self::$_themes as $themename => $themeobject ) {
1130             if ( in_array($themename,$disabledThemes) )
1131                 $themelist[$themeobject->dirName] = $themeobject->name;
1132         }
1133
1134         return $themelist;
1135     }
1136
1137     /**
1138      * Returns an array of all themes found in the current installation
1139      *
1140      * @return array
1141      */
1142     public static function allThemes()
1143     {
1144         $themelist = array();
1145
1146         foreach ( self::$_themes as $themename => $themeobject )
1147             $themelist[$themeobject->dirName] = $themeobject->name;
1148
1149         return $themelist;
1150     }
1151
1152     /**
1153      * Clears out the cached path locations for all themes
1154      */
1155     public static function clearAllCaches()
1156     {
1157         foreach ( self::$_themes as $themeobject ) {
1158             $themeobject->clearCache();
1159         }
1160     }
1161 }