PageRenderTime 89ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/include/SugarTheme/SugarTheme.php

https://bitbucket.org/cviolette/sugarcrm
PHP | 1376 lines | 793 code | 144 blank | 439 comment | 206 complexity | cf9766ea68d6faab44025adde04320b6 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
  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-2012 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. * Description: Contains a variety of utility functions used to display UI
  39. * components such as form headers and footers. Intended to be modified on a per
  40. * theme basis.
  41. ********************************************************************************/
  42. if(!defined('JSMIN_AS_LIB'))
  43. define('JSMIN_AS_LIB', true);
  44. require_once("include/SugarTheme/cssmin.php");
  45. require_once("jssource/jsmin.php");
  46. require_once('include/utils/sugar_file_utils.php');
  47. /**
  48. * Class that provides tools for working with a theme.
  49. * @api
  50. */
  51. class SugarTheme
  52. {
  53. /**
  54. * Theme name
  55. *
  56. * @var string
  57. */
  58. protected $name;
  59. /**
  60. * Theme description
  61. *
  62. * @var string
  63. */
  64. protected $description;
  65. /**
  66. * Defines which parent files to not include
  67. *
  68. * @var string
  69. */
  70. protected $ignoreParentFiles = array();
  71. /**
  72. * Defines which parent files to not include
  73. *
  74. * @var string
  75. */
  76. public $directionality = 'ltr';
  77. /**
  78. * Theme directory name
  79. *
  80. * @var string
  81. */
  82. protected $dirName;
  83. /**
  84. * Parent theme name
  85. *
  86. * @var string
  87. */
  88. protected $parentTheme;
  89. /**
  90. * Colors sets provided by the theme
  91. *
  92. * @deprecated only here for BC during upgrades
  93. * @var array
  94. */
  95. protected $colors = array();
  96. /**
  97. * Font sets provided by the theme
  98. *
  99. * @deprecated only here for BC during upgrades
  100. * @var array
  101. */
  102. protected $fonts = array();
  103. /**
  104. * Maximum sugar version this theme is for; defaults to 5.5.1 as all the themes without this
  105. * parameter as assumed to work thru 5.5.1
  106. *
  107. * @var int
  108. */
  109. protected $version = '5.5.1';
  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. * Colors used in pie charts
  146. *
  147. * @var array
  148. */
  149. protected $pieChartColors = array(
  150. "docBorder" => "0xffffff",
  151. "docBg1" => "0xffffff",
  152. "docBg2" => "0xffffff",
  153. "title" => "0x333333",
  154. "subtitle" => "0x666666",
  155. "misc" => "0x999999",
  156. "altBorder" => "0xffffff",
  157. "altBg" => "0xffffff",
  158. "altText" => "0x666666",
  159. "graphText" => "0x33485c",
  160. "graphTextShadow" => "0xf9f9f9",
  161. "pieBorder" => "0xffffff",
  162. "pieBorderHilite" => "0x333333",
  163. "legendBorder" => "0xffffff",
  164. "legendBg1" => "0xffffff",
  165. "legendBg2" => "0xffffff",
  166. "legendText" => "0x444444",
  167. "legendColorKeyBorder" => "0x777777",
  168. "scrollBar" => "0xdfdfdf",
  169. "scrollBarBorder" => "0xfafafa",
  170. "scrollBarTrack" => "0xeeeeee",
  171. "scrollBarTrackBorder" => "0xcccccc",
  172. );
  173. /**
  174. * Does this theme support group tabs
  175. *
  176. * @var bool
  177. */
  178. public $group_tabs;
  179. /**
  180. * Cache built of all css files locations
  181. *
  182. * @var array
  183. */
  184. private $_cssCache = array();
  185. /**
  186. * Cache built of all image files locations
  187. *
  188. * @var array
  189. */
  190. private $_imageCache = array();
  191. /**
  192. * Cache built of all javascript files locations
  193. *
  194. * @var array
  195. */
  196. private $_jsCache = array();
  197. /**
  198. * Cache built of all template files locations
  199. *
  200. * @var array
  201. */
  202. private $_templateCache = array();
  203. /**
  204. * Cache built of sprite meta data
  205. *
  206. * @var array
  207. */
  208. private $_spriteCache = array();
  209. /**
  210. * Size of the caches after the are initialized in the constructor
  211. *
  212. * @var array
  213. */
  214. private $_initialCacheSize = array(
  215. 'cssCache' => 0,
  216. 'imageCache' => 0,
  217. 'jsCache' => 0,
  218. 'templateCache' => 0,
  219. 'spriteCache' => 0,
  220. );
  221. /**
  222. * Controls whether or not to clear the cache on destroy; defaults to false
  223. */
  224. private $_clearCacheOnDestroy = false;
  225. private $imageExtensions = array(
  226. 'gif',
  227. 'png',
  228. 'jpg',
  229. 'tif',
  230. 'bmp',
  231. );
  232. /**
  233. * Constructor
  234. *
  235. * Sets the theme properties from the defaults passed to it, and loads the file path cache from an external cache
  236. *
  237. * @param $defaults string defaults for the current theme
  238. */
  239. public function __construct(
  240. $defaults
  241. )
  242. {
  243. // apply parent theme's properties first
  244. if ( isset($defaults['parentTheme']) ) {
  245. $themedef = array();
  246. include("themes/{$defaults['parentTheme']}/themedef.php");
  247. foreach ( $themedef as $key => $value ) {
  248. if ( property_exists(__CLASS__,$key) ) {
  249. // For all arrays ( except colors and fonts ) you can just specify the items
  250. // to change instead of all of the values
  251. if ( is_array($this->$key) && !in_array($key,array('colors','fonts')) )
  252. $this->$key = array_merge($this->$key,$value);
  253. else
  254. $this->$key = $value;
  255. }
  256. }
  257. }
  258. foreach ( $defaults as $key => $value ) {
  259. if ( property_exists(__CLASS__,$key) ) {
  260. // For all arrays ( except colors and fonts ) you can just specify the items
  261. // to change instead of all of the values
  262. if ( is_array($this->$key) && !in_array($key,array('colors','fonts')) )
  263. $this->$key = array_merge($this->$key,$value);
  264. else
  265. $this->$key = $value;
  266. }
  267. }
  268. if ( !inDeveloperMode() ) {
  269. if ( sugar_is_file($cachedfile = sugar_cached($this->getFilePath().'/pathCache.php'))) {
  270. $caches = unserialize(file_get_contents($cachedfile));
  271. if ( isset($caches['jsCache']) )
  272. $this->_jsCache = $caches['jsCache'];
  273. if ( isset($caches['cssCache']) )
  274. $this->_cssCache = $caches['cssCache'];
  275. if ( isset($caches['imageCache']) )
  276. $this->_imageCache = $caches['imageCache'];
  277. if ( isset($caches['templateCache']) )
  278. $this->_templateCache = $caches['templateCache'];
  279. }
  280. $cachedfile = sugar_cached($this->getFilePath().'/spriteCache.php');
  281. if(!empty($GLOBALS['sugar_config']['use_sprites']) && sugar_is_file($cachedfile)) {
  282. $this->_spriteCache = unserialize(sugar_file_get_contents($cachedfile));
  283. }
  284. }
  285. $this->_initialCacheSize = array(
  286. 'jsCache' => count($this->_jsCache),
  287. 'cssCache' => count($this->_cssCache),
  288. 'imageCache' => count($this->_imageCache),
  289. 'templateCache' => count($this->_templateCache),
  290. 'spriteCache' => count($this->_spriteCache),
  291. );
  292. }
  293. /**
  294. * This is needed to prevent unserialize vulnerability
  295. */
  296. public function __wakeup()
  297. {
  298. // clean all properties
  299. foreach(get_object_vars($this) as $k => $v) {
  300. $this->$k = null;
  301. }
  302. throw new Exception("Not a serializable object");
  303. }
  304. /**
  305. * Destructor
  306. * Here we'll write out the internal file path caches to an external cache of some sort.
  307. */
  308. public function __destruct()
  309. {
  310. // Bug 28309 - Set the current directory to one which we expect it to be (i.e. the root directory of the install
  311. set_include_path(realpath(dirname(__FILE__) . '/../..') . PATH_SEPARATOR . get_include_path());
  312. chdir(dirname(__FILE__) . '/../..'); // destruct can be called late, and chdir could change
  313. $cachedir = sugar_cached($this->getFilePath());
  314. sugar_mkdir($cachedir, 0775, true);
  315. // clear out the cache on destroy if we are asked to
  316. if ( $this->_clearCacheOnDestroy ) {
  317. if (is_file("$cachedir/pathCache.php"))
  318. unlink("$cachedir/pathCache.php");
  319. if (is_file("$cachedir/spriteCache.php"))
  320. unlink("$cachedir/spriteCache.php");
  321. }
  322. elseif ( !inDeveloperMode() ) {
  323. // only update the caches if they have been changed in this request
  324. if ( count($this->_jsCache) != $this->_initialCacheSize['jsCache']
  325. || count($this->_cssCache) != $this->_initialCacheSize['cssCache']
  326. || count($this->_imageCache) != $this->_initialCacheSize['imageCache']
  327. || count($this->_templateCache) != $this->_initialCacheSize['templateCache']
  328. ) {
  329. sugar_file_put_contents(
  330. "$cachedir/pathCache.php",
  331. serialize(
  332. array(
  333. 'jsCache' => $this->_jsCache,
  334. 'cssCache' => $this->_cssCache,
  335. 'imageCache' => $this->_imageCache,
  336. 'templateCache' => $this->_templateCache,
  337. )
  338. )
  339. );
  340. }
  341. if ( count($this->_spriteCache) != $this->_initialCacheSize['spriteCache']) {
  342. sugar_file_put_contents(
  343. "$cachedir/spriteCache.php",
  344. serialize($this->_spriteCache)
  345. );
  346. }
  347. }
  348. }
  349. /**
  350. * Specifies what is returned when the object is cast to a string, in this case it will be the
  351. * theme directory name.
  352. *
  353. * @return string theme directory name
  354. */
  355. public function __toString()
  356. {
  357. return $this->dirName;
  358. }
  359. /**
  360. * Generic public accessor method for all the properties of the theme ( which are kept protected )
  361. *
  362. * @return string
  363. */
  364. public function __get(
  365. $key
  366. )
  367. {
  368. if ( isset($this->$key) )
  369. return $this->$key;
  370. }
  371. public function __isset($key){
  372. return isset($this->$key);
  373. }
  374. public function clearJSCache()
  375. {
  376. $this->_jsCache = array();
  377. }
  378. /**
  379. * Clears out the caches used for this themes
  380. */
  381. public function clearCache()
  382. {
  383. $this->_clearCacheOnDestroy = true;
  384. }
  385. /**
  386. * Return array of all valid fields that can be specified in the themedef.php file
  387. *
  388. * @return array
  389. */
  390. public static function getThemeDefFields()
  391. {
  392. return array(
  393. 'name',
  394. 'description',
  395. 'directionality',
  396. 'dirName',
  397. 'parentTheme',
  398. 'version',
  399. 'colors',
  400. 'fonts',
  401. 'barChartColors',
  402. 'pieChartColors',
  403. 'group_tabs',
  404. 'ignoreParentFiles',
  405. );
  406. }
  407. /**
  408. * Returns the file path of the current theme
  409. *
  410. * @return string
  411. */
  412. public function getFilePath()
  413. {
  414. return 'themes/'.$this->dirName;
  415. }
  416. /**
  417. * Returns the image path of the current theme
  418. *
  419. * @return string
  420. */
  421. public function getImagePath()
  422. {
  423. return $this->getFilePath().'/images';
  424. }
  425. /**
  426. * Returns the css path of the current theme
  427. *
  428. * @return string
  429. */
  430. public function getCSSPath()
  431. {
  432. return $this->getFilePath().'/css';
  433. }
  434. /**
  435. * Returns the javascript path of the current theme
  436. *
  437. * @return string
  438. */
  439. public function getJSPath()
  440. {
  441. return $this->getFilePath().'/js';
  442. }
  443. /**
  444. * Returns the tpl path of the current theme
  445. *
  446. * @return string
  447. */
  448. public function getTemplatePath()
  449. {
  450. return $this->getFilePath().'/tpls';
  451. }
  452. /**
  453. * Returns the file path of the theme defaults
  454. *
  455. * @return string
  456. */
  457. public final function getDefaultFilePath()
  458. {
  459. return 'themes/default';
  460. }
  461. /**
  462. * Returns the image path of the theme defaults
  463. *
  464. * @return string
  465. */
  466. public final function getDefaultImagePath()
  467. {
  468. return $this->getDefaultFilePath().'/images';
  469. }
  470. /**
  471. * Returns the css path of the theme defaults
  472. *
  473. * @return string
  474. */
  475. public final function getDefaultCSSPath()
  476. {
  477. return $this->getDefaultFilePath().'/css';
  478. }
  479. /**
  480. * Returns the template path of the theme defaults
  481. *
  482. * @return string
  483. */
  484. public final function getDefaultTemplatePath()
  485. {
  486. return $this->getDefaultFilePath().'/tpls';
  487. }
  488. /**
  489. * Returns the javascript path of the theme defaults
  490. *
  491. * @return string
  492. */
  493. public final function getDefaultJSPath()
  494. {
  495. return $this->getDefaultFilePath().'/js';
  496. }
  497. /**
  498. * Returns CSS for the current theme.
  499. *
  500. * @param $color string optional, specifies the css color file to use if the theme supports it; defaults to cookie value or theme default
  501. * @param $font string optional, specifies the css font file to use if the theme supports it; defaults to cookie value or theme default
  502. * @return string HTML code
  503. */
  504. public function getCSS(
  505. $color = null,
  506. $font = null
  507. )
  508. {
  509. // include style.css file
  510. $html = '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('yui.css').'" />';
  511. $html .= '<link rel="stylesheet" type="text/css" href="include/javascript/jquery/themes/base/jquery.ui.all.css" />';
  512. $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('deprecated.css').'" />';
  513. $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('style.css').'" />';
  514. // sprites
  515. if(!empty($GLOBALS['sugar_config']['use_sprites']) && $GLOBALS['sugar_config']['use_sprites']) {
  516. // system wide sprites
  517. if(file_exists("cache/sprites/default/sprites.css"))
  518. $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/default/sprites.css').'" />';
  519. // theme specific sprites
  520. if(file_exists("cache/sprites/{$this->dirName}/sprites.css"))
  521. $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/'.$this->dirName.'/sprites.css').'" />';
  522. // parent sprites
  523. if($this->parentTheme && $parent = SugarThemeRegistry::get($this->parentTheme)) {
  524. if(file_exists("cache/sprites/{$parent->dirName}/sprites.css"))
  525. $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/'.$parent->dirName.'/sprites.css').'" />';
  526. }
  527. // repeatable sprites
  528. if(file_exists("cache/sprites/Repeatable/sprites.css"))
  529. $html .= '<link rel="stylesheet" type="text/css" href="'.getJSPath('cache/sprites/Repeatable/sprites.css').'" />';
  530. }
  531. // for BC during upgrade
  532. if ( !empty($this->colors) ) {
  533. if ( isset($_SESSION['authenticated_user_theme_color']) && in_array($_SESSION['authenticated_user_theme_color'], $this->colors))
  534. $color = $_SESSION['authenticated_user_theme_color'];
  535. else
  536. $color = $this->colors[0];
  537. $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('colors.'.$color.'.css').'" id="current_color_style" />';
  538. }
  539. if ( !empty($this->fonts) ) {
  540. if ( isset($_SESSION['authenticated_user_theme_font']) && in_array($_SESSION['authenticated_user_theme_font'], $this->fonts))
  541. $font = $_SESSION['authenticated_user_theme_font'];
  542. else
  543. $font = $this->fonts[0];
  544. $html .= '<link rel="stylesheet" type="text/css" href="'.$this->getCSSURL('fonts.'.$font.'.css').'" id="current_font_style" />';
  545. }
  546. return $html;
  547. }
  548. /**
  549. * Returns javascript for the current theme
  550. *
  551. * @return string HTML code
  552. */
  553. public function getJS()
  554. {
  555. $styleJS = $this->getJSURL('style.js');
  556. return <<<EOHTML
  557. <script type="text/javascript" src="$styleJS"></script>
  558. EOHTML;
  559. }
  560. /**
  561. * Returns the path for the tpl file in the current theme. If not found in the current theme, will revert
  562. * to looking in the base theme.
  563. *
  564. * @param string $templateName tpl file name
  565. * @return string path of tpl file to include
  566. */
  567. public function getTemplate(
  568. $templateName
  569. )
  570. {
  571. if ( isset($this->_templateCache[$templateName]) )
  572. return $this->_templateCache[$templateName];
  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. $this->_imageCache[$templateName] = $templatePath;
  591. return $templatePath;
  592. }
  593. /**
  594. * Returns an image tag for the given image.
  595. *
  596. * @param string $image image name
  597. * @param string $other_attributes optional, other attributes to add to the image tag, not cached
  598. * @param string $width optional, defaults to the actual image's width
  599. * @param string $height optional, defaults to the actual image's height
  600. * @param string $ext optional, image extension (TODO can we deprecate this one ?)
  601. * @param string $alt optional, only used when image contains something useful, i.e. "Sally's profile pic"
  602. * @return string HTML image tag or sprite
  603. */
  604. public function getImage(
  605. $imageName,
  606. $other_attributes = '',
  607. $width = null,
  608. $height = null,
  609. $ext = null,
  610. $alt = ''
  611. )
  612. {
  613. static $cached_results = array();
  614. // trap deprecated use of image extension
  615. if(is_null($ext)) {
  616. $imageNameExp = explode('.',$imageName);
  617. if(count($imageNameExp) == 1)
  618. $imageName .= '.gif';
  619. } else {
  620. $imageName .= $ext;
  621. }
  622. // trap alt attributes in other_attributes
  623. if(preg_match('/alt=["\']([^\'"]+)["\']/i', $other_attributes))
  624. $GLOBALS['log']->debug("Sprites: alt attribute detected for $imageName");
  625. // sprite handler, makes use of own caching mechanism
  626. if(!empty($GLOBALS['sugar_config']['use_sprites']) && $GLOBALS['sugar_config']['use_sprites']) {
  627. // get sprite metadata
  628. if($sp = $this->getSpriteMeta($imageName)) {
  629. // requested size should match
  630. if( (!is_null($width) && $sp['width'] == $width) || (is_null($width)) &&
  631. (!is_null($height) && $sp['height'] == $height) || (is_null($height)) )
  632. {
  633. if($sprite = $this->getSprite($sp['class'], $other_attributes, $alt))
  634. return $sprite;
  635. }
  636. }
  637. }
  638. // img caching
  639. if(empty($cached_results[$imageName])) {
  640. $imageURL = $this->getImageURL($imageName,false);
  641. if ( empty($imageURL) )
  642. return false;
  643. $cached_results[$imageName] = '<img src="'.getJSPath($imageURL).'" ';
  644. }
  645. $attr_width = (is_null($width)) ? "" : "width=\"$width\"";
  646. $attr_height = (is_null($height)) ? "" : "height=\"$height\"";
  647. return $cached_results[$imageName] . " $attr_width $attr_height $other_attributes alt=\"$alt\" />";
  648. }
  649. /**
  650. * Returns sprite meta data
  651. *
  652. * @param string $imageName Image filename including extension
  653. * @return array Sprite meta data
  654. */
  655. public function getSpriteMeta($imageName) {
  656. // return from cache
  657. if(isset($this->_spriteCache[$imageName]))
  658. return $this->_spriteCache[$imageName];
  659. // sprite keys are base on imageURL
  660. $imageURL = $this->getImageURL($imageName,false);
  661. if(empty($imageURL)) {
  662. $this->_spriteCache[$imageName] = false;
  663. return false;
  664. }
  665. // load meta data, includes default images
  666. require_once("include/SugarTheme/SugarSprites.php");
  667. $meta = SugarSprites::getInstance();
  668. // add current theme dir
  669. $meta->loadSpriteMeta($this->dirName);
  670. // add parent theme dir
  671. if($this->parentTheme && $parent = SugarThemeRegistry::get($this->parentTheme)) {
  672. $meta->loadSpriteMeta($parent->dirName);
  673. }
  674. // add to cache
  675. if(isset($meta->sprites[$imageURL])) {
  676. $this->_spriteCache[$imageName] = $meta->sprites[$imageURL];
  677. // add imageURL to cache
  678. //$this->_spriteCache[$imageName]['imageURL'] = $imageURL;
  679. } else {
  680. $this->_spriteCache[$imageName] = false;
  681. $GLOBALS['log']->debug("Sprites: miss for $imageURL");
  682. }
  683. return $this->_spriteCache[$imageName];
  684. }
  685. /**
  686. * Returns sprite HTML span tag
  687. *
  688. * @param string class The md5 id used in the CSS sprites class
  689. * @param string attr optional, list of additional html attributes
  690. * @param string title optional, the title (equivalent to alt on img)
  691. * @return string HTML span tag
  692. */
  693. public function getSprite($class, $attr, $title) {
  694. // handle multiple class tags
  695. $class_regex = '/class=["\']([^\'"]+)["\']/i';
  696. preg_match($class_regex, $attr, $match);
  697. if(isset($match[1])) {
  698. $attr = preg_replace($class_regex, 'class="spr_'.$class.' ${1}"', $attr);
  699. // single class
  700. } else {
  701. $attr .= ' class="spr_'.$class.'"';
  702. }
  703. if($title)
  704. $attr .= ' title="'.$title.'"';
  705. // use </span> instead of /> to prevent weird UI results
  706. $GLOBALS['log']->debug("Sprites: generated sprite -> $attr");
  707. return "<span {$attr}></span>";
  708. }
  709. /**
  710. * Returns a link HTML tag with or without an embedded image
  711. */
  712. public function getLink(
  713. $url,
  714. $title,
  715. $other_attributes = '',
  716. $img_name = '',
  717. $img_other_attributes = '',
  718. $img_width = null,
  719. $img_height = null,
  720. $img_alt = '',
  721. $img_placement = 'imageonly'
  722. )
  723. {
  724. if($img_name) {
  725. $img = $this->getImage($img_name, $img_other_attributes, $img_width, $img_height, null, $img_alt);
  726. if($img == false) {
  727. $GLOBALS['log']->debug('Sprites: unknown image getLink');
  728. $img = 'unknown';
  729. }
  730. switch($img_placement) {
  731. case 'left': $inner_html = $img."<span class='title'>".$title."</span>"; break;
  732. case 'right': $inner_html = "<span class='title'>".$title."</span>".$img; break;
  733. default: $inner_html = $img; break;
  734. }
  735. } else {
  736. $inner_html = $title;
  737. }
  738. return '<a href="'.$url.'" title="'.$title.'" '.$other_attributes.'>'.$inner_html.'</a>';
  739. }
  740. /**
  741. * Returns the URL for an image in the current theme. If not found in the current theme, will revert
  742. * to looking in the base theme.
  743. * @param string $imageName image file name
  744. * @param bool $addJSPath call getJSPath() with the results to add some unique image tracking support
  745. * @return string path to image
  746. */
  747. public function getImageURL(
  748. $imageName,
  749. $addJSPath = true
  750. ){
  751. if ( isset($this->_imageCache[$imageName]) ) {
  752. if ( $addJSPath )
  753. return getJSPath($this->_imageCache[$imageName]);
  754. else
  755. return $this->_imageCache[$imageName];
  756. }
  757. $imagePath = '';
  758. if (($filename = $this->_getImageFileName('custom/'.$this->getImagePath().'/'.$imageName)) != '')
  759. $imagePath = $filename;
  760. elseif (($filename = $this->_getImageFileName($this->getImagePath().'/'.$imageName)) != '')
  761. $imagePath = $filename;
  762. elseif (isset($this->parentTheme)
  763. && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
  764. && ($filename = SugarThemeRegistry::get($this->parentTheme)->getImageURL($imageName,false)) != '')
  765. $imagePath = $filename;
  766. elseif (($filename = $this->_getImageFileName('custom/'.$this->getDefaultImagePath().'/'.$imageName)) != '')
  767. $imagePath = $filename;
  768. elseif (($filename = $this->_getImageFileName($this->getDefaultImagePath().'/'.$imageName)) != '')
  769. $imagePath = $filename;
  770. elseif (($filename = $this->_getImageFileName('include/images/'.$imageName)) != '')
  771. $imagePath = $filename;
  772. else {
  773. $GLOBALS['log']->warn("Image $imageName not found");
  774. return false;
  775. }
  776. $this->_imageCache[$imageName] = $imagePath;
  777. if ( $addJSPath )
  778. return getJSPath($imagePath);
  779. return $imagePath;
  780. }
  781. /**
  782. * Checks for an image using all of the accepted image extensions
  783. *
  784. * @param string $imageName image file name
  785. * @return string path to image
  786. */
  787. protected function _getImageFileName(
  788. $imageName
  789. )
  790. {
  791. // return now if the extension matches that of which we are looking for
  792. if ( sugar_is_file($imageName) )
  793. return $imageName;
  794. $pathParts = pathinfo($imageName);
  795. foreach ( $this->imageExtensions as $extension )
  796. if ( isset($pathParts['extension']) )
  797. if ( ( $extension != $pathParts['extension'] )
  798. && sugar_is_file($pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension) )
  799. return $pathParts['dirname'].'/'.$pathParts['filename'].'.'.$extension;
  800. return '';
  801. }
  802. /**
  803. * Returns the URL for the css file in the current theme. If not found in the current theme, will revert
  804. * to looking in the base theme.
  805. *
  806. * @param string $cssFileName css file name
  807. * @param bool $returnURL if true, returns URL with unique image mark, otherwise returns path to the file
  808. * @return string path of css file to include
  809. */
  810. public function getCSSURL($cssFileName, $returnURL = true)
  811. {
  812. if ( isset($this->_cssCache[$cssFileName]) && sugar_is_file(sugar_cached($this->_cssCache[$cssFileName])) ) {
  813. if ( $returnURL )
  814. return getJSPath("cache/".$this->_cssCache[$cssFileName]);
  815. else
  816. return sugar_cached($this->_cssCache[$cssFileName]);
  817. }
  818. $cssFileContents = '';
  819. $defaultFileName = $this->getDefaultCSSPath().'/'.$cssFileName;
  820. $fullFileName = $this->getCSSPath().'/'.$cssFileName;
  821. if (isset($this->parentTheme)
  822. && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
  823. && ($filename = SugarThemeRegistry::get($this->parentTheme)->getCSSURL($cssFileName,false)) != '')
  824. $cssFileContents .= file_get_contents($filename);
  825. else {
  826. if (sugar_is_file($defaultFileName))
  827. $cssFileContents .= file_get_contents($defaultFileName);
  828. if (sugar_is_file('custom/'.$defaultFileName))
  829. $cssFileContents .= file_get_contents('custom/'.$defaultFileName);
  830. }
  831. if (sugar_is_file($fullFileName)) {
  832. $cssFileContents .= file_get_contents($fullFileName);
  833. }
  834. if (sugar_is_file('custom/'.$fullFileName)) {
  835. $cssFileContents .= file_get_contents('custom/'.$fullFileName);
  836. }
  837. if (empty($cssFileContents)) {
  838. $GLOBALS['log']->warn("CSS File $cssFileName not found");
  839. return false;
  840. }
  841. // fix any image references that may be defined in css files
  842. $cssFileContents = str_ireplace("entryPoint=getImage&",
  843. "entryPoint=getImage&themeName={$this->dirName}&",
  844. $cssFileContents);
  845. // create the cached file location
  846. $cssFilePath = create_cache_directory($fullFileName);
  847. // if this is the style.css file, prepend the base.css and calendar-win2k-cold-1.css
  848. // files before the theme styles
  849. if ( $cssFileName == 'style.css' && !isset($this->parentTheme) ) {
  850. if ( inDeveloperMode() )
  851. $cssFileContents = file_get_contents('include/javascript/yui/build/base/base.css') . $cssFileContents;
  852. else
  853. $cssFileContents = file_get_contents('include/javascript/yui/build/base/base-min.css') . $cssFileContents;
  854. }
  855. // minify the css
  856. if ( !inDeveloperMode() && !sugar_is_file($cssFilePath) ) {
  857. $cssFileContents = cssmin::minify($cssFileContents);
  858. }
  859. // now write the css to cache
  860. sugar_file_put_contents($cssFilePath,$cssFileContents);
  861. $this->_cssCache[$cssFileName] = $fullFileName;
  862. if ( $returnURL )
  863. return getJSPath("cache/".$fullFileName);
  864. return sugar_cached($fullFileName);
  865. }
  866. /**
  867. * Returns the URL for an image in the current theme. If not found in the current theme, will revert
  868. * to looking in the base theme.
  869. *
  870. * @param string $jsFileName js file name
  871. * @param bool $returnURL if true, returns URL with unique image mark, otherwise returns path to the file
  872. * @return string path to js file
  873. */
  874. public function getJSURL($jsFileName, $returnURL = true)
  875. {
  876. if ( isset($this->_jsCache[$jsFileName]) && sugar_is_file(sugar_cached($this->_jsCache[$jsFileName])) ) {
  877. if ( $returnURL )
  878. return getJSPath("cache/".$this->_jsCache[$jsFileName]);
  879. else
  880. return sugar_cached($this->_jsCache[$jsFileName]);
  881. }
  882. $jsFileContents = '';
  883. $fullFileName = $this->getJSPath().'/'.$jsFileName;
  884. $defaultFileName = $this->getDefaultJSPath().'/'.$jsFileName;
  885. if (isset($this->parentTheme)
  886. && SugarThemeRegistry::get($this->parentTheme) instanceOf SugarTheme
  887. && ($filename = SugarThemeRegistry::get($this->parentTheme)->getJSURL($jsFileName,false)) != '' && !in_array($jsFileName,$this->ignoreParentFiles)) {
  888. $jsFileContents .= file_get_contents($filename);
  889. } else {
  890. if (sugar_is_file($defaultFileName))
  891. $jsFileContents .= file_get_contents($defaultFileName);
  892. if (sugar_is_file('custom/'.$defaultFileName))
  893. $jsFileContents .= file_get_contents('custom/'.$defaultFileName);
  894. }
  895. if (sugar_is_file($fullFileName))
  896. $jsFileContents .= file_get_contents($fullFileName);
  897. if (sugar_is_file('custom/'.$fullFileName))
  898. $jsFileContents .= file_get_contents('custom/'.$fullFileName);
  899. if (empty($jsFileContents)) {
  900. $GLOBALS['log']->warn("Javascript File $jsFileName not found");
  901. return false;
  902. }
  903. // create the cached file location
  904. $jsFilePath = create_cache_directory($fullFileName);
  905. // minify the js
  906. if ( !inDeveloperMode()&& !sugar_is_file(str_replace('.js','-min.js',$jsFilePath)) ) {
  907. $jsFileContents = SugarMin::minify($jsFileContents);
  908. $jsFilePath = str_replace('.js','-min.js',$jsFilePath);
  909. $fullFileName = str_replace('.js','-min.js',$fullFileName);
  910. }
  911. // now write the js to cache
  912. sugar_file_put_contents($jsFilePath,$jsFileContents);
  913. $this->_jsCache[$jsFileName] = $fullFileName;
  914. if ( $returnURL )
  915. return getJSPath("cache/".$fullFileName);
  916. return sugar_cached($fullFileName);
  917. }
  918. /**
  919. * Returns an array of all of the images available for the current theme
  920. *
  921. * @return array
  922. */
  923. public function getAllImages()
  924. {
  925. // first, lets get all the paths of where to look
  926. $pathsToSearch = array($this->getImagePath());
  927. $theme = $this;
  928. while (isset($theme->parentTheme) && SugarThemeRegistry::get($theme->parentTheme) instanceOf SugarTheme ) {
  929. $theme = SugarThemeRegistry::get($theme->parentTheme);
  930. $pathsToSearch[] = $theme->getImagePath();
  931. }
  932. $pathsToSearch[] = $this->getDefaultImagePath();
  933. // now build the array
  934. $imageArray = array();
  935. foreach ( $pathsToSearch as $path )
  936. {
  937. if (!sugar_is_dir($path)) $path = "custom/$path";
  938. if (sugar_is_dir($path) && is_readable($path) && $dir = opendir($path)) {
  939. while (($file = readdir($dir)) !== false) {
  940. if ($file == ".."
  941. || $file == "."
  942. || $file == ".svn"
  943. || $file == "CVS"
  944. || $file == "Attic"
  945. )
  946. continue;
  947. if ( !isset($imageArray[$file]) )
  948. $imageArray[$file] = $this->getImageURL($file,false);
  949. }
  950. closedir($dir);
  951. }
  952. }
  953. ksort($imageArray);
  954. return $imageArray;
  955. }
  956. }
  957. /**
  958. * Registry for all the current classes in the system
  959. */
  960. class SugarThemeRegistry
  961. {
  962. /**
  963. * Array of all themes and thier object
  964. *
  965. * @var array
  966. */
  967. private static $_themes = array();
  968. /**
  969. * Name of the current theme; corresponds to an index key in SugarThemeRegistry::$_themes
  970. *
  971. * @var string
  972. */
  973. private static $_currentTheme;
  974. /**
  975. * Disable the constructor since this will be a singleton
  976. */
  977. private function __construct() {}
  978. /**
  979. * Adds a new theme to the registry
  980. *
  981. * @param $themedef array
  982. */
  983. public static function add(
  984. array $themedef
  985. )
  986. {
  987. // make sure the we know the sugar version
  988. if ( !isset($GLOBALS['sugar_version']) ) {
  989. include('sugar_version.php');
  990. $GLOBALS['sugar_version'] = $sugar_version;
  991. }
  992. // Assume theme is designed for 5.5.x if not specified otherwise
  993. if ( !isset($themedef['version']) )
  994. $themedef['version']['regex_matches'] = array('5\.5\.*');
  995. // Check to see if theme is valid for this version of Sugar; return false if not
  996. $version_ok = false;
  997. if( isset($themedef['version']['exact_matches']) ){
  998. $matches_empty = false;
  999. foreach( $themedef['version']['exact_matches'] as $match ){
  1000. if( $match == $GLOBALS['sugar_version'] ){
  1001. $version_ok = true;
  1002. }
  1003. }
  1004. }
  1005. if( !$version_ok && isset($themedef['version']['regex_matches']) ){
  1006. $matches_empty = false;
  1007. foreach( $themedef['version']['regex_matches'] as $match ){
  1008. if( preg_match( "/$match/", $GLOBALS['sugar_version'] ) ){
  1009. $version_ok = true;
  1010. }
  1011. }
  1012. }
  1013. if ( !$version_ok )
  1014. return false;
  1015. $theme = new SugarTheme($themedef);
  1016. self::$_themes[$theme->dirName] = $theme;
  1017. }
  1018. /**
  1019. * Removes a new theme from the registry
  1020. *
  1021. * @param $themeName string
  1022. */
  1023. public static function remove(
  1024. $themeName
  1025. )
  1026. {
  1027. if ( self::exists($themeName) )
  1028. unset(self::$_themes[$themeName]);
  1029. }
  1030. /**
  1031. * Returns a theme object in the registry specified by the given $themeName
  1032. *
  1033. * @param $themeName string
  1034. */
  1035. public static function get(
  1036. $themeName
  1037. )
  1038. {
  1039. if ( isset(self::$_themes[$themeName]) )
  1040. return self::$_themes[$themeName];
  1041. }
  1042. /**
  1043. * Returns the current theme object
  1044. *
  1045. * @return SugarTheme object
  1046. */
  1047. public static function current()
  1048. {
  1049. if ( !isset(self::$_currentTheme) )
  1050. self::buildRegistry();
  1051. return self::$_themes[self::$_currentTheme];
  1052. }
  1053. /**
  1054. * Returns the default theme object
  1055. *
  1056. * @return SugarTheme object
  1057. */
  1058. public static function getDefault()
  1059. {
  1060. if ( !isset(self::$_currentTheme) )
  1061. self::buildRegistry();
  1062. if ( isset($GLOBALS['sugar_config']['default_theme']) && self::exists($GLOBALS['sugar_config']['default_theme']) ) {
  1063. return self::get($GLOBALS['sugar_config']['default_theme']);
  1064. }
  1065. return self::get(array_pop(array_keys(self::availableThemes())));
  1066. }
  1067. /**
  1068. * Returns true if a theme object specified by the given $themeName exists in the registry
  1069. *
  1070. * @param $themeName string
  1071. * @return bool
  1072. */
  1073. public static function exists(
  1074. $themeName
  1075. )
  1076. {
  1077. return (self::get($themeName) !== null);
  1078. }
  1079. /**
  1080. * Sets the given $themeName to be the current theme
  1081. *
  1082. * @param $themeName string
  1083. */
  1084. public static function set(
  1085. $themeName
  1086. )
  1087. {
  1088. if ( !self::exists($themeName) )
  1089. return false;
  1090. self::$_currentTheme = $themeName;
  1091. // set some of the expected globals
  1092. $GLOBALS['barChartColors'] = self::current()->barChartColors;
  1093. $GLOBALS['pieChartColors'] = self::current()->pieChartColors;
  1094. return true;
  1095. }
  1096. /**
  1097. * Builds the theme registry
  1098. */
  1099. public static function buildRegistry()
  1100. {
  1101. self::$_themes = array();
  1102. $dirs = array("themes/","custom/themes/");
  1103. // check for a default themedef file
  1104. $themedefDefault = array();
  1105. if ( sugar_is_file("custom/themes/default/themedef.php") ) {
  1106. $themedef = array();
  1107. require("custom/themes/default/themedef.php");
  1108. $themedefDefault = $themedef;
  1109. }
  1110. foreach ($dirs as $dirPath ) {
  1111. if (sugar_is_dir('./'.$dirPath) && is_readable('./'.$dirPath) && $dir = opendir('./'.$dirPath)) {
  1112. while (($file = readdir($dir)) !== false) {
  1113. if ($file == ".."
  1114. || $file == "."
  1115. || $file == ".svn"
  1116. || $file == "CVS"
  1117. || $file == "Attic"
  1118. || $file == "default"
  1119. || !sugar_is_dir("./$dirPath".$file)
  1120. || !sugar_is_file("./{$dirPath}{$file}/themedef.php")
  1121. )
  1122. continue;
  1123. $themedef = array();
  1124. require("./{$dirPath}{$file}/themedef.php");
  1125. $themedef = array_merge($themedef,$themedefDefault);
  1126. $themedef['dirName'] = $file;
  1127. // check for theme already existing in the registry
  1128. // if so, then it will override the current one
  1129. if ( self::exists($themedef['dirName']) ) {
  1130. $existingTheme = self::get($themedef['dirName']);
  1131. foreach ( SugarTheme::getThemeDefFields() as $field )
  1132. if ( !isset($themedef[$field]) )
  1133. $themedef[$field] = $existingTheme->$field;
  1134. self::remove($themedef['dirName']);
  1135. }
  1136. if ( isset($themedef['name']) ) {
  1137. self::add($themedef);
  1138. }
  1139. }
  1140. closedir($dir);
  1141. }
  1142. }
  1143. // default to setting the default theme as the current theme
  1144. if ( !isset($GLOBALS['sugar_config']['default_theme']) || !self::set($GLOBALS['sugar_config']['default_theme']) ) {
  1145. if ( count(self::availableThemes()) == 0 )
  1146. {
  1147. sugar_die('No valid themes are found on this instance');
  1148. } else {
  1149. self::set(self::getDefaultThemeKey());
  1150. }
  1151. }
  1152. }
  1153. /**
  1154. * getDefaultThemeKey
  1155. *
  1156. * This function returns the default theme key. It takes into account string casing issues that may arise
  1157. * from upgrades. It attempts to look for the Sugar theme and if not found, defaults to return the name of the last theme
  1158. * in the array of available themes loaded.
  1159. *
  1160. * @return $defaultThemeKey String value of the default theme key to use
  1161. */
  1162. private static function getDefaultThemeKey()
  1163. {
  1164. $availableThemes = self::availableThemes();
  1165. foreach($availableThemes as $key=>$theme)
  1166. {
  1167. if(strtolower($key) == 'sugar')
  1168. {
  1169. return $key;
  1170. }
  1171. }
  1172. return array_pop(array_keys($availableThemes));
  1173. }
  1174. /**
  1175. * Returns an array of available themes. Designed to be absorbed into get_select_options_with_id()
  1176. *
  1177. * @return array
  1178. */
  1179. public static function availableThemes()
  1180. {
  1181. $themelist = array();
  1182. $disabledThemes = array();
  1183. if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
  1184. $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
  1185. foreach ( self::$_themes as $themename => $themeobject ) {
  1186. if ( in_array($themename,$disabledThemes) )
  1187. continue;
  1188. $themelist[$themeobject->dirName] = $themeobject->name;
  1189. }
  1190. asort($themelist, SORT_STRING);
  1191. return $themelist;
  1192. }
  1193. /**
  1194. * Returns an array of un-available themes. Designed used with the theme selector in the admin panel
  1195. *
  1196. * @return array
  1197. */
  1198. public static function unAvailableThemes()
  1199. {
  1200. $themelist = array();
  1201. $disabledThemes = array();
  1202. if ( isset($GLOBALS['sugar_config']['disabled_themes']) )
  1203. $disabledThemes = explode(',',$GLOBALS['sugar_config']['disabled_themes']);
  1204. foreach ( self::$_themes as $themename => $themeobject ) {
  1205. if ( in_array($themename,$disabledThemes) )
  1206. $themelist[$themeobject->dirName] = $themeobject->name;
  1207. }
  1208. return $themelist;
  1209. }
  1210. /**
  1211. * Returns an array of all themes found in the current installation
  1212. *
  1213. * @return array
  1214. */
  1215. public static function allThemes()
  1216. {
  1217. $themelist = array();
  1218. foreach ( self::$_themes as $themename => $themeobject )
  1219. $themelist[$themeobject->dirName] = $themeobject->name;
  1220. return $themelist;
  1221. }
  1222. /**
  1223. * Clears out the cached path locations for all themes
  1224. */
  1225. public static function clearAllCaches()
  1226. {
  1227. foreach ( self::$_themes as $themeobject ) {
  1228. $themeobject->clearCache();
  1229. }
  1230. }
  1231. }