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