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-2012 SugarCRM Inc.
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.
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
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
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.
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.
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 ********************************************************************************/
39 /*********************************************************************************
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
44 ********************************************************************************/
46 if(!defined('JSMIN_AS_LIB'))
47 define('JSMIN_AS_LIB', true);
49 require_once("include/SugarTheme/cssmin.php");
50 require_once("jssource/jsmin.php");
51 require_once('include/utils/sugar_file_utils.php');
54 * Class that provides tools for working with a theme.
71 protected $description;
74 * Defines which parent files to not include
78 protected $ignoreParentFiles = array();
81 * Defines which parent files to not include
85 public $directionality = 'ltr';
87 * Theme directory name
98 protected $parentTheme;
101 * Colors sets provided by the theme
103 * @deprecated only here for BC during upgrades
106 protected $colors = array();
109 * Font sets provided by the theme
111 * @deprecated only here for BC during upgrades
114 protected $fonts = array();
117 * Maximum sugar version this theme is for; defaults to 5.5.1 as all the themes without this
118 * parameter as assumed to work thru 5.5.1
122 protected $version = '5.5.1';
125 * Colors used in bar charts
129 protected $barChartColors = array(
130 "docBorder" => "0xffffff",
131 "docBg1" => "0xffffff",
132 "docBg2" => "0xffffff",
133 "xText" => "0x33485c",
134 "yText" => "0x33485c",
135 "title" => "0x333333",
136 "misc" => "0x999999",
137 "altBorder" => "0xffffff",
138 "altBg" => "0xffffff",
139 "altText" => "0x666666",
140 "graphBorder" => "0xcccccc",
141 "graphBg1" => "0xf6f6f6",
142 "graphBg2" => "0xf6f6f6",
143 "graphLines" => "0xcccccc",
144 "graphText" => "0x333333",
145 "graphTextShadow" => "0xf9f9f9",
146 "barBorder" => "0xeeeeee",
147 "barBorderHilite" => "0x333333",
148 "legendBorder" => "0xffffff",
149 "legendBg1" => "0xffffff",
150 "legendBg2" => "0xffffff",
151 "legendText" => "0x444444",
152 "legendColorKeyBorder" => "0x777777",
153 "scrollBar" => "0xcccccc",
154 "scrollBarBorder" => "0xeeeeee",
155 "scrollBarTrack" => "0xeeeeee",
156 "scrollBarTrackBorder" => "0xcccccc",
160 * Colors used in pie charts
164 protected $pieChartColors = array(
165 "docBorder" => "0xffffff",
166 "docBg1" => "0xffffff",
167 "docBg2" => "0xffffff",
168 "title" => "0x333333",
169 "subtitle" => "0x666666",
170 "misc" => "0x999999",
171 "altBorder" => "0xffffff",
172 "altBg" => "0xffffff",
173 "altText" => "0x666666",
174 "graphText" => "0x33485c",
175 "graphTextShadow" => "0xf9f9f9",
176 "pieBorder" => "0xffffff",
177 "pieBorderHilite" => "0x333333",
178 "legendBorder" => "0xffffff",
179 "legendBg1" => "0xffffff",
180 "legendBg2" => "0xffffff",
181 "legendText" => "0x444444",
182 "legendColorKeyBorder" => "0x777777",
183 "scrollBar" => "0xdfdfdf",
184 "scrollBarBorder" => "0xfafafa",
185 "scrollBarTrack" => "0xeeeeee",
186 "scrollBarTrackBorder" => "0xcccccc",
190 * Does this theme support group tabs
198 * Cache built of all css files locations
202 private $_cssCache = array();
205 * Cache built of all image files locations
209 private $_imageCache = array();
212 * Cache built of all javascript files locations
216 private $_jsCache = array();
219 * Cache built of all template files locations
223 private $_templateCache = array();
226 * Cache built of sprite meta data
230 private $_spriteCache = array();
233 * Size of the caches after the are initialized in the constructor
237 private $_initialCacheSize = array(
241 'templateCache' => 0,
246 * Controls whether or not to clear the cache on destroy; defaults to false
248 private $_clearCacheOnDestroy = false;
250 private $imageExtensions = array(
261 * Sets the theme properties from the defaults passed to it, and loads the file path cache from an external cache
263 * @param $defaults string defaults for the current theme
265 public function __construct(
269 // apply parent theme's properties first
270 if ( isset($defaults['parentTheme']) ) {
272 include("themes/{$defaults['parentTheme']}/themedef.php");
273 foreach ( $themedef as $key => $value ) {
274 if ( property_exists(__CLASS__,$key) ) {
275 // For all arrays ( except colors and fonts ) you can just specify the items
276 // to change instead of all of the values
277 if ( is_array($this->$key) && !in_array($key,array('colors','fonts')) )
278 $this->$key = array_merge($this->$key,$value);
280 $this->$key = $value;
284 foreach ( $defaults as $key => $value ) {
285 if ( property_exists(__CLASS__,$key) ) {
286 // For all arrays ( except colors and fonts ) you can just specify the items
287 // to change instead of all of the values
288 if ( is_array($this->$key) && !in_array($key,array('colors','fonts')) )
289 $this->$key = array_merge($this->$key,$value);
291 $this->$key = $value;
294 if ( !inDeveloperMode() ) {
295 if ( sugar_is_file($cachedfile = sugar_cached($this->getFilePath().'/pathCache.php'))) {
296 $caches = unserialize(file_get_contents($cachedfile));
297 if ( isset($caches['jsCache']) )
298 $this->_jsCache = $caches['jsCache'];
299 if ( isset($caches['cssCache']) )
300 $this->_cssCache = $caches['cssCache'];
301 if ( isset($caches['imageCache']) )
302 $this->_imageCache = $caches['imageCache'];
303 if ( isset($caches['templateCache']) )
304 $this->_templateCache = $caches['templateCache'];
306 $cachedfile = sugar_cached($this->getFilePath().'/spriteCache.php');
307 if(!empty($GLOBALS['sugar_config']['use_sprites']) && sugar_is_file($cachedfile)) {
308 $this->_spriteCache = unserialize(sugar_file_get_contents($cachedfile));
311 $this->_initialCacheSize = array(
312 'jsCache' => count($this->_jsCache),
313 'cssCache' => count($this->_cssCache),
314 'imageCache' => count($this->_imageCache),
315 'templateCache' => count($this->_templateCache),
316 'spriteCache' => count($this->_spriteCache),
321 * This is needed to prevent unserialize vulnerability
323 public function __wakeup()
325 // clean all properties
326 foreach(get_object_vars($this) as $k => $v) {
329 throw new Exception("Not a serializable object");
334 * Here we'll write out the internal file path caches to an external cache of some sort.
336 public function __destruct()
338 // Set the current directory to one which we expect it to be (i.e. the root directory of the install
339 $dir = realpath(dirname(__FILE__) . '/../..');
340 static $includePathIsPatched = false;
341 if ($includePathIsPatched == false)
343 $path = explode(PATH_SEPARATOR, get_include_path());
344 if (in_array($dir, $path) == false)
346 set_include_path($dir . PATH_SEPARATOR . get_include_path());
348 $includePathIsPatched = true;
350 chdir($dir); // destruct can be called late, and chdir could change
351 $cachedir = sugar_cached($this->getFilePath());
352 sugar_mkdir($cachedir, 0775, true);
353 // clear out the cache on destroy if we are asked to
354 if ( $this->_clearCacheOnDestroy ) {
356 if (is_file("$cachedir/pathCache.php"))
357 unlink("$cachedir/pathCache.php");
358 if (is_file("$cachedir/spriteCache.php"))
359 unlink("$cachedir/spriteCache.php");
362 elseif ( !inDeveloperMode() ) {
363 // only update the caches if they have been changed in this request
364 if ( count($this->_jsCache) != $this->_initialCacheSize['jsCache']
365 || count($this->_cssCache) != $this->_initialCacheSize['cssCache']
366 || count($this->_imageCache) != $this->_initialCacheSize['imageCache']
367 || count($this->_templateCache) != $this->_initialCacheSize['templateCache']
369 sugar_file_put_contents(
370 "$cachedir/pathCache.php",
373 'jsCache' => $this->_jsCache,
374 'cssCache' => $this->_cssCache,
375 'imageCache' => $this->_imageCache,
376 'templateCache' => $this->_templateCache,
382 if ( count($this->_spriteCache) != $this->_initialCacheSize['spriteCache']) {
383 sugar_file_put_contents(
384 "$cachedir/spriteCache.php",
385 serialize($this->_spriteCache)
392 * Specifies what is returned when the object is cast to a string, in this case it will be the
393 * theme directory name.
395 * @return string theme directory name
397 public function __toString()
399 return $this->dirName;
403 * Generic public accessor method for all the properties of the theme ( which are kept protected )
407 public function __get(
411 if ( isset($this->$key) )
415 public function __isset($key){
416 return isset($this->$key);
420 public function clearJSCache()
422 $this->_jsCache = array();
426 * Clears out the caches used for this themes
428 public function clearCache()
430 $this->_clearCacheOnDestroy = true;
434 * Return array of all valid fields that can be specified in the themedef.php file
438 public static function getThemeDefFields()
457 * Returns the file path of the current theme
461 public function getFilePath()
463 return 'themes/'.$this->dirName;
467 * Returns the image path of the current theme
471 public function getImagePath()
473 return $this->getFilePath().'/images';
477 * Returns the css path of the current theme
481 public function getCSSPath()
483 return $this->getFilePath().'/css';
487 * Returns the javascript path of the current theme
491 public function getJSPath()
493 return $this->getFilePath().'/js';
497 * Returns the tpl path of the current theme
501 public function getTemplatePath()
503 return $this->getFilePath().'/tpls';
507 * Returns the file path of the theme defaults
511 public final function getDefaultFilePath()
513 return 'themes/default';
517 * Returns the image path of the theme defaults
521 public final function getDefaultImagePath()
523 return $this->getDefaultFilePath().'/images';
527 * Returns the css path of the theme defaults
531 public final function getDefaultCSSPath()
533 return $this->getDefaultFilePath().'/css';
537 * Returns the template path of the theme defaults
541 public final function getDefaultTemplatePath()
543 return $this->getDefaultFilePath().'/tpls';
547 * Returns the javascript path of the theme defaults
551 public final function getDefaultJSPath()
553 return $this->getDefaultFilePath().'/js';
557 * Returns CSS for the current theme.
559 * @param $color string optional, specifies the css color file to use if the theme supports it; defaults to cookie value or theme default
560 * @param $font string optional, specifies the css font file to use if the theme supports it; defaults to cookie value or theme default
561 * @return string HTML code
563 public function getCSS(
568 // include style.css file
569 $html = '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('yui.css').'" />';
570 $html .= '<link rel="stylesheet" type="text/css" href="include/javascript/jquery/themes/base/jquery.ui.all.css" />';
571 $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('deprecated.css').'" />';
572 $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('style.css').'" />';
575 if(!empty($GLOBALS['sugar_config']['use_sprites']) && $GLOBALS['sugar_config']['use_sprites']) {
577 // system wide sprites
578 if(file_exists("cache/sprites/default/sprites.css"))
579 $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/default/sprites.css').'" />';
581 // theme specific sprites
582 if(file_exists("cache/sprites/{$this->dirName}/sprites.css"))
583 $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/'.$this->dirName.'/sprites.css').'" />';
586 if($this->parentTheme && $parent = SugarThemeRegistry::get($this->parentTheme)) {
587 if(file_exists("cache/sprites/{$parent->dirName}/sprites.css"))
588 $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/'.$parent->dirName.'/sprites.css').'" />';
591 // repeatable sprites
592 if(file_exists("cache/sprites/Repeatable/sprites.css"))
593 $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/Repeatable/sprites.css').'" />';
596 // for BC during upgrade
597 if ( !empty($this->colors) ) {
598 if ( isset($_SESSION['authenticated_user_theme_color']) && in_array($_SESSION['authenticated_user_theme_color'], $this->colors))
599 $color = $_SESSION['authenticated_user_theme_color'];
601 $color = $this->colors[0];
602 $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('colors.'.$color.'.css').'" id="current_color_style" />';
605 if ( !empty($this->fonts) ) {
606 if ( isset($_SESSION['authenticated_user_theme_font']) && in_array($_SESSION['authenticated_user_theme_font'], $this->fonts))
607 $font = $_SESSION['authenticated_user_theme_font'];
609 $font = $this->fonts[0];
610 $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('fonts.'.$font.'.css').'" id="current_font_style" />';
617 * Returns javascript for the current theme
619 * @return string HTML code
621 public function getJS()
623 $styleJS = $this->getJSURL('style.js');
625 <script type="text/javascript" src="$styleJS"></script>
630 * Returns the path for the tpl file in the current theme. If not found in the current theme, will revert
631 * to looking in the base theme.
633 * @param string $templateName tpl file name
634 * @return string path of tpl file to include
636 public function getTemplate(
640 if ( isset($this->_templateCache[$templateName]) )
641 return $this->_templateCache[$templateName];
644 if (sugar_is_file('custom/'.$this->getTemplatePath().'/'.$templateName))
645 $templatePath = 'custom/'.$this->getTemplatePath().'/'.$templateName;
646 elseif (sugar_is_file($this->getTemplatePath().'/'.$templateName))
647 $templatePath = $this->getTemplatePath().'/'.$templateName;
648 elseif (isset($this->parentTheme)
649 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
650 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getTemplate($templateName)) != '')
651 $templatePath = $filename;
652 elseif (sugar_is_file('custom/'.$this->getDefaultTemplatePath().'/'.$templateName))
653 $templatePath = 'custom/'.$this->getDefaultTemplatePath().'/'.$templateName;
654 elseif (sugar_is_file($this->getDefaultTemplatePath().'/'.$templateName))
655 $templatePath = $this->getDefaultTemplatePath().'/'.$templateName;
657 $GLOBALS['log']->warn("Template $templateName not found");
661 $this->_imageCache[$templateName] = $templatePath;
663 return $templatePath;
667 * Returns an image tag for the given image.
669 * @param string $image image name
670 * @param string $other_attributes optional, other attributes to add to the image tag, not cached
671 * @param string $width optional, defaults to the actual image's width
672 * @param string $height optional, defaults to the actual image's height
673 * @param string $ext optional, image extension (TODO can we deprecate this one ?)
674 * @param string $alt optional, only used when image contains something useful, i.e. "Sally's profile pic"
675 * @return string HTML image tag or sprite
677 public function getImage(
679 $other_attributes = '',
687 static $cached_results = array();
689 // trap deprecated use of image extension
691 $imageNameExp = explode('.',$imageName);
692 if(count($imageNameExp) == 1)
693 $imageName .= '.gif';
698 // trap alt attributes in other_attributes
699 if(preg_match('/alt=["\']([^\'"]+)["\']/i', $other_attributes))
700 $GLOBALS['log']->debug("Sprites: alt attribute detected for $imageName");
701 // sprite handler, makes use of own caching mechanism
702 if(!empty($GLOBALS['sugar_config']['use_sprites']) && $GLOBALS['sugar_config']['use_sprites']) {
703 // get sprite metadata
704 if($sp = $this->getSpriteMeta($imageName)) {
705 // requested size should match
706 if( (!is_null($width) && $sp['width'] == $width) || (is_null($width)) &&
707 (!is_null($height) && $sp['height'] == $height) || (is_null($height)) )
709 $other_attributes .= ' data-orig="'.$imageName.'"';
711 if($sprite = $this->getSprite($sp['class'], $other_attributes, $alt))
720 if(empty($cached_results[$imageName])) {
721 $imageURL = $this->getImageURL($imageName,false);
722 if ( empty($imageURL) )
724 $cached_results[$imageName] = '<img src="'.getJSPath($imageURL).'" ';
727 $attr_width = (is_null($width)) ? "" : "width=\"$width\"";
728 $attr_height = (is_null($height)) ? "" : "height=\"$height\"";
729 return $cached_results[$imageName] . " $attr_width $attr_height $other_attributes alt=\"$alt\" />";
733 * Returns sprite meta data
735 * @param string $imageName Image filename including extension
736 * @return array Sprite meta data
738 public function getSpriteMeta($imageName) {
741 if(isset($this->_spriteCache[$imageName]))
742 return $this->_spriteCache[$imageName];
744 // sprite keys are base on imageURL
745 $imageURL = $this->getImageURL($imageName,false);
746 if(empty($imageURL)) {
747 $this->_spriteCache[$imageName] = false;
751 // load meta data, includes default images
752 require_once("include/SugarTheme/SugarSprites.php");
753 $meta = SugarSprites::getInstance();
754 // add current theme dir
755 $meta->loadSpriteMeta($this->dirName);
756 // add parent theme dir
757 if($this->parentTheme && $parent = SugarThemeRegistry::get($this->parentTheme)) {
758 $meta->loadSpriteMeta($parent->dirName);
762 if(isset($meta->sprites[$imageURL])) {
763 $this->_spriteCache[$imageName] = $meta->sprites[$imageURL];
764 // add imageURL to cache
765 //$this->_spriteCache[$imageName]['imageURL'] = $imageURL;
767 $this->_spriteCache[$imageName] = false;
768 $GLOBALS['log']->debug("Sprites: miss for $imageURL");
770 return $this->_spriteCache[$imageName];
774 * Returns sprite HTML span tag
776 * @param string class The md5 id used in the CSS sprites class
777 * @param string attr optional, list of additional html attributes
778 * @param string title optional, the title (equivalent to alt on img)
779 * @return string HTML span tag
781 public function getSprite($class, $attr, $title) {
783 // handle multiple class tags
784 $class_regex = '/class=["\']([^\'"]+)["\']/i';
785 preg_match($class_regex, $attr, $match);
786 if(isset($match[1])) {
787 $attr = preg_replace($class_regex, 'class="spr_'.$class.' ${1}"', $attr);
791 $attr .= ' class="spr_'.$class.'"';
795 $attr .= ' title="'.$title.'"';
797 // use </span> instead of /> to prevent weird UI results
798 $GLOBALS['log']->debug("Sprites: generated sprite -> $attr");
799 return "<span {$attr}></span>";
803 * Returns a link HTML tag with or without an embedded image
805 public function getLink(
808 $other_attributes = '',
810 $img_other_attributes = '',
814 $img_placement = 'imageonly'
819 $img = $this->getImage($img_name, $img_other_attributes, $img_width, $img_height, null, $img_alt);
821 $GLOBALS['log']->debug('Sprites: unknown image getLink');
824 switch($img_placement) {
825 case 'left': $inner_html = $img."<span class='title'>".$title."</span>"; break;
826 case 'right': $inner_html = "<span class='title'>".$title."</span>".$img; break;
827 default: $inner_html = $img; break;
830 $inner_html = $title;
833 return '<a href="'.$url.'" title="'.$title.'" '.$other_attributes.'>'.$inner_html.'</a>';
838 * Returns the URL for an image in the current theme. If not found in the current theme, will revert
839 * to looking in the base theme.
840 * @param string $imageName image file name
841 * @param bool $addJSPath call getJSPath() with the results to add some unique image tracking support
842 * @return string path to image
844 public function getImageURL(
848 if ( isset($this->_imageCache[$imageName]) ) {
850 return getJSPath($this->_imageCache[$imageName]);
852 return $this->_imageCache[$imageName];
855 if (($filename = $this->_getImageFileName('custom/'.$this->getImagePath().'/'.$imageName)) != '')
856 $imagePath = $filename;
857 elseif (($filename = $this->_getImageFileName($this->getImagePath().'/'.$imageName)) != '')
858 $imagePath = $filename;
859 elseif (isset($this->parentTheme)
860 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
861 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getImageURL($imageName,false)) != '')
862 $imagePath = $filename;
863 elseif (($filename = $this->_getImageFileName('custom/'.$this->getDefaultImagePath().'/'.$imageName)) != '')
864 $imagePath = $filename;
865 elseif (($filename = $this->_getImageFileName($this->getDefaultImagePath().'/'.$imageName)) != '')
866 $imagePath = $filename;
867 elseif (($filename = $this->_getImageFileName('include/images/'.$imageName)) != '')
868 $imagePath = $filename;
870 $GLOBALS['log']->warn("Image $imageName not found");
874 $this->_imageCache[$imageName] = $imagePath;
877 return getJSPath($imagePath);
883 * Checks for an image using all of the accepted image extensions
885 * @param string $imageName image file name
886 * @return string path to image
888 protected function _getImageFileName(
892 // return now if the extension matches that of which we are looking for
893 if ( sugar_is_file($imageName) )
895 $pathParts = pathinfo($imageName);
896 foreach ( $this->imageExtensions as $extension )
897 if ( isset($pathParts['extension']) )
898 if ( ( $extension != $pathParts['extension'] )
899 && sugar_is_file($pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension) )
900 return $pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension;
906 * Returns the URL for the css file in the current theme. If not found in the current theme, will revert
907 * to looking in the base theme.
909 * @param string $cssFileName css file name
910 * @param bool $returnURL if true, returns URL with unique image mark, otherwise returns path to the file
911 * @return string path of css file to include
913 public function getCSSURL($cssFileName, $returnURL = true)
915 if ( isset($this->_cssCache[$cssFileName]) && sugar_is_file(sugar_cached($this->_cssCache[$cssFileName])) ) {
917 return getJSPath("cache/".$this->_cssCache[$cssFileName]);
919 return sugar_cached($this->_cssCache[$cssFileName]);
922 $cssFileContents = '';
923 $defaultFileName = $this->getDefaultCSSPath().'/'.$cssFileName;
924 $fullFileName = $this->getCSSPath().'/'.$cssFileName;
925 if (isset($this->parentTheme)
926 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
927 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getCSSURL($cssFileName,false)) != '')
928 $cssFileContents .= file_get_contents($filename);
930 if (sugar_is_file($defaultFileName))
931 $cssFileContents .= file_get_contents($defaultFileName);
932 if (sugar_is_file('custom/'.$defaultFileName))
933 $cssFileContents .= file_get_contents('custom/'.$defaultFileName);
935 if (sugar_is_file($fullFileName)) {
936 $cssFileContents .= file_get_contents($fullFileName);
938 if (sugar_is_file('custom/'.$fullFileName)) {
939 $cssFileContents .= file_get_contents('custom/'.$fullFileName);
941 if (empty($cssFileContents)) {
942 $GLOBALS['log']->warn("CSS File $cssFileName not found");
946 // fix any image references that may be defined in css files
947 $cssFileContents = str_ireplace("entryPoint=getImage&",
948 "entryPoint=getImage&themeName={$this->dirName}&",
951 // create the cached file location
952 $cssFilePath = create_cache_directory($fullFileName);
954 // if this is the style.css file, prepend the base.css and calendar-win2k-cold-1.css
955 // files before the theme styles
956 if ( $cssFileName == 'style.css' && !isset($this->parentTheme) ) {
957 if ( inDeveloperMode() )
958 $cssFileContents = file_get_contents('include/javascript/yui/build/base/base.css') . $cssFileContents;
960 $cssFileContents = file_get_contents('include/javascript/yui/build/base/base-min.css') . $cssFileContents;
964 if ( !inDeveloperMode() && !sugar_is_file($cssFilePath) ) {
965 $cssFileContents = cssmin::minify($cssFileContents);
968 // now write the css to cache
969 sugar_file_put_contents($cssFilePath,$cssFileContents);
971 $this->_cssCache[$cssFileName] = $fullFileName;
974 return getJSPath("cache/".$fullFileName);
976 return sugar_cached($fullFileName);
980 * Returns the URL for an image in the current theme. If not found in the current theme, will revert
981 * to looking in the base theme.
983 * @param string $jsFileName js file name
984 * @param bool $returnURL if true, returns URL with unique image mark, otherwise returns path to the file
985 * @return string path to js file
987 public function getJSURL($jsFileName, $returnURL = true)
989 if ( isset($this->_jsCache[$jsFileName]) && sugar_is_file(sugar_cached($this->_jsCache[$jsFileName])) ) {
991 return getJSPath("cache/".$this->_jsCache[$jsFileName]);
993 return sugar_cached($this->_jsCache[$jsFileName]);
996 $jsFileContents = '';
997 $fullFileName = $this->getJSPath().'/'.$jsFileName;
998 $defaultFileName = $this->getDefaultJSPath().'/'.$jsFileName;
999 if (isset($this->parentTheme)
1000 && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
1001 && ($filename = SugarThemeRegistry::get($this->parentTheme)->getJSURL($jsFileName,false)) != '' && !in_array($jsFileName,$this->ignoreParentFiles)) {
1002 $jsFileContents .= file_get_contents($filename);
1004 if (sugar_is_file($defaultFileName))
1005 $jsFileContents .= file_get_contents($defaultFileName);
1006 if (sugar_is_file('custom/'.$defaultFileName))
1007 $jsFileContents .= file_get_contents('custom/'.$defaultFileName);
1009 if (sugar_is_file($fullFileName))
1010 $jsFileContents .= file_get_contents($fullFileName);
1011 if (sugar_is_file('custom/'.$fullFileName))
1012 $jsFileContents .= file_get_contents('custom/'.$fullFileName);
1013 if (empty($jsFileContents)) {
1014 $GLOBALS['log']->warn("Javascript File $jsFileName not found");
1018 // create the cached file location
1019 $jsFilePath = create_cache_directory($fullFileName);
1022 if ( !inDeveloperMode()&& !sugar_is_file(str_replace('.js','-min.js',$jsFilePath)) ) {
1023 $jsFileContents = SugarMin::minify($jsFileContents);
1024 $jsFilePath = str_replace('.js','-min.js',$jsFilePath);
1025 $fullFileName = str_replace('.js','-min.js',$fullFileName);
1028 // now write the js to cache
1029 sugar_file_put_contents($jsFilePath,$jsFileContents);
1031 $this->_jsCache[$jsFileName] = $fullFileName;
1034 return getJSPath("cache/".$fullFileName);
1036 return sugar_cached($fullFileName);
1040 * Returns an array of all of the images available for the current theme
1044 public function getAllImages()
1046 // first, lets get all the paths of where to look
1047 $pathsToSearch = array($this->getImagePath());
1049 while (isset($theme->parentTheme) && SugarThemeRegistry::get($theme->parentTheme) instanceOf SugarTheme ) {
1050 $theme = SugarThemeRegistry::get($theme->parentTheme);
1051 $pathsToSearch[] = $theme->getImagePath();
1053 $pathsToSearch[] = $this->getDefaultImagePath();
1055 // now build the array
1056 $imageArray = array();
1057 foreach ( $pathsToSearch as $path )
1059 if (!sugar_is_dir($path)) $path = "custom/$path";
1060 if (sugar_is_dir($path) && is_readable($path) && $dir = opendir($path)) {
1061 while (($file = readdir($dir)) !== false) {
1069 if ( !isset($imageArray[$file]) )
1070 $imageArray[$file] = $this->getImageURL($file,false);
1084 * Registry for all the current classes in the system
1086 class SugarThemeRegistry
1089 * Array of all themes and thier object
1093 private static $_themes = array();
1096 * Name of the current theme; corresponds to an index key in SugarThemeRegistry::$_themes
1100 private static $_currentTheme;
1103 * Disable the constructor since this will be a singleton
1105 private function __construct() {}
1108 * Adds a new theme to the registry
1110 * @param $themedef array
1112 public static function add(
1116 // make sure the we know the sugar version
1117 if ( !isset($GLOBALS['sugar_version']) ) {
1118 include('sugar_version.php');
1119 $GLOBALS['sugar_version'] = $sugar_version;
1122 // Assume theme is designed for 5.5.x if not specified otherwise
1123 if ( !isset($themedef['version']) )
1124 $themedef['version']['regex_matches'] = array('5\.5\.*');
1126 // Check to see if theme is valid for this version of Sugar; return false if not
1127 $version_ok = false;
1128 if( isset($themedef['version']['exact_matches']) ){
1129 $matches_empty = false;
1130 foreach( $themedef['version']['exact_matches'] as $match ){
1131 if( $match == $GLOBALS['sugar_version'] ){
1136 if( !$version_ok && isset($themedef['version']['regex_matches']) ){
1137 $matches_empty = false;
1138 foreach( $themedef['version']['regex_matches'] as $match ){
1139 if( preg_match( "/$match/", $GLOBALS['sugar_version'] ) ){
1147 $theme = new SugarTheme($themedef);
1148 self::$_themes[$theme->dirName] = $theme;
1152 * Removes a new theme from the registry
1154 * @param $themeName string
1156 public static function remove(
1160 if ( self::exists($themeName) )
1161 unset(self::$_themes[$themeName]);
1165 * Returns a theme object in the registry specified by the given $themeName
1167 * @param $themeName string
1169 public static function get(
1173 if ( isset(self::$_themes[$themeName]) )
1174 return self::$_themes[$themeName];
1178 * Returns the current theme object
1180 * @return SugarTheme object
1182 public static function current()
1184 if ( !isset(self::$_currentTheme) )
1185 self::buildRegistry();
1187 return self::$_themes[self::$_currentTheme];
1191 * Returns the default theme object
1193 * @return SugarTheme object
1195 public static function getDefault()
1197 if ( !isset(self::$_currentTheme) )
1198 self::buildRegistry();
1200 if ( isset($GLOBALS['sugar_config']['default_theme']) && self::exists($GLOBALS['sugar_config']['default_theme']) ) {
1201 return self::get($GLOBALS['sugar_config']['default_theme']);
1204 return self::get(array_pop(array_keys(self::availableThemes())));
1208 * Returns true if a theme object specified by the given $themeName exists in the registry
1210 * @param $themeName string
1213 public static function exists(
1217 return (self::get($themeName) !== null);
1221 * Sets the given $themeName to be the current theme
1223 * @param $themeName string
1225 public static function set(
1229 if ( !self::exists($themeName) )
1232 self::$_currentTheme = $themeName;
1234 // set some of the expected globals
1235 $GLOBALS['barChartColors'] = self::current()->barChartColors;
1236 $GLOBALS['pieChartColors'] = self::current()->pieChartColors;
1241 * Builds the theme registry
1243 public static function buildRegistry()
1245 self::$_themes = array();
1246 $dirs = array("themes/","custom/themes/");
1248 // check for a default themedef file
1249 $themedefDefault = array();
1250 if ( sugar_is_file("custom/themes/default/themedef.php") ) {
1251 $themedef = array();
1252 require("custom/themes/default/themedef.php");
1253 $themedefDefault = $themedef;
1256 foreach ($dirs as $dirPath ) {
1257 if (sugar_is_dir('./'.$dirPath) && is_readable('./'.$dirPath) && $dir = opendir('./'.$dirPath)) {
1258 while (($file = readdir($dir)) !== false) {
1264 || $file == "default"
1265 || !sugar_is_dir("./$dirPath".$file)
1266 || !sugar_is_file("./{$dirPath}{$file}/themedef.php")
1269 $themedef = array();
1270 require("./{$dirPath}{$file}/themedef.php");
1271 $themedef = array_merge($themedef,$themedefDefault);
1272 $themedef['dirName'] = $file;
1273 // check for theme already existing in the registry
1274 // if so, then it will override the current one
1275 if ( self::exists($themedef['dirName']) ) {
1276 $existingTheme = self::get($themedef['dirName']);
1277 foreach ( SugarTheme::getThemeDefFields() as $field )
1278 if ( !isset($themedef[$field]) )
1279 $themedef[$field] = $existingTheme->$field;
1280 self::remove($themedef['dirName']);
1282 if ( isset($themedef['name']) ) {
1283 self::add($themedef);
1289 // default to setting the default theme as the current theme
1290 if ( !isset($GLOBALS['sugar_config']['default_theme']) || !self::set($GLOBALS['sugar_config']['default_theme']) ) {
1291 if ( count(self::availableThemes()) == 0 )
1293 sugar_die('No valid themes are found on this instance');
1295 self::set(self::getDefaultThemeKey());
1302 * getDefaultThemeKey
1304 * This function returns the default theme key. It takes into account string casing issues that may arise
1305 * from upgrades. It attempts to look for the Sugar theme and if not found, defaults to return the name of the last theme
1306 * in the array of available themes loaded.
1308 * @return $defaultThemeKey String value of the default theme key to use
1310 private static function getDefaultThemeKey()
1312 $availableThemes = self::availableThemes();
1313 foreach($availableThemes as $key=>$theme)
1315 if(strtolower($key) == 'sugar')
1321 return array_pop(array_keys($availableThemes));
1326 * Returns an array of available themes. Designed to be absorbed into get_select_options_with_id()
1330 public static function availableThemes()
1332 $themelist = array();
1333 $disabledThemes = array();
1334 if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
1335 $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
1337 foreach ( self::$_themes as $themename => $themeobject ) {
1338 if ( in_array($themename,$disabledThemes) )
1340 $themelist[$themeobject->dirName] = $themeobject->name;
1342 asort($themelist, SORT_STRING);
1347 * Returns an array of un-available themes. Designed used with the theme selector in the admin panel
1351 public static function unAvailableThemes()
1353 $themelist = array();
1354 $disabledThemes = array();
1355 if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
1356 $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
1358 foreach ( self::$_themes as $themename => $themeobject ) {
1359 if ( in_array($themename,$disabledThemes) )
1360 $themelist[$themeobject->dirName] = $themeobject->name;
1367 * Returns an array of all themes found in the current installation
1371 public static function allThemes()
1373 $themelist = array();
1375 foreach ( self::$_themes as $themename => $themeobject )
1376 $themelist[$themeobject->dirName] = $themeobject->name;
1382 * Clears out the cached path locations for all themes
1384 public static function clearAllCaches()
1386 foreach ( self::$_themes as $themeobject ) {
1387 $themeobject->clearCache();