2 if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
3 /*********************************************************************************
4 * SugarCRM Community Edition is a customer relationship management program developed by
5 * SugarCRM, Inc. Copyright (C) 2004-2011 SugarCRM Inc.
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 require_once("include/SugarTheme/cssmin.php");
41 class SugarSpriteBuilder
43 var $isAvailable = false;
44 var $silentRun = false;
45 var $fromSilentUpgrade = false;
46 var $writeToUpgradeLog = false;
49 var $fileName = 'sprites';
50 var $cssMinify = true;
52 // class supported image types
53 var $supportedTypeMap = array(
54 IMG_GIF => IMAGETYPE_GIF,
55 IMG_JPG => IMAGETYPE_JPEG,
56 IMG_PNG => IMAGETYPE_PNG,
60 var $pngCompression = 9;
61 var $pngFilter = PNG_NO_FILTER;
66 // processed image types
67 var $imageTypes = array();
70 var $spriteSrc = array();
71 var $spriteRepeat = array();
73 // sprite resource images
76 // sprite_config collection
77 var $sprites_config = array();
80 public function __construct()
82 // check if we have gd installed
83 if(function_exists('imagecreatetruecolor'))
85 $this->isAvailable = true;
86 foreach($this->supportedTypeMap as $gd_bit => $imagetype)
88 if(imagetypes() & $gd_bit) {
89 // swap gd_bit & imagetype
90 $this->imageTypes[$imagetype] = $gd_bit;
95 if(function_exists('logThis') && isset($GLOBALS['path']))
97 $this->writeToUpgradeLog = true;
105 * This function is used to create the spriteSrc array
106 * @param $name String value of the sprite name
107 * @param $dir String value of the directory associated with the sprite entry
109 public function addDirectory($name, $dir) {
112 if(!array_key_exists($name, $this->spriteSrc))
114 $this->spriteSrc[$name] = array();
117 // add files from directory
118 $this->spriteSrc[$name][$dir] = $this->getFileList($dir);
124 * This method processes files in a directory and adds them to the sprites array
125 * @param $dir String value of the directory to scan for image files in
127 private function getFileList($dir) {
130 if($dh = opendir($dir)) {
132 // optional sprites_config.php file
133 $this->loadSpritesConfig($dir);
135 while (($file = readdir($dh)) !== false)
137 if ($file != "." && $file != ".." && $file != "sprites_config.php")
140 // file info & check supported image format
141 if($info = $this->getFileInfo($dir, $file)) {
143 // skip excluded files
144 if(isset($this->sprites_config[$dir]['exclude']) && array_search($file, $this->sprites_config[$dir]['exclude']) !== false)
147 $msg = string_format($mod_strings['LBL_SPRITES_EXCLUDING_FILE'], array("{$dir}/{$file}"));
148 $GLOBALS['log']->debug($msg);
149 $this->logMessage($msg);
151 // repeatable sprite ?
154 if(isset($this->sprites_config[$dir]['repeat']))
156 foreach($this->sprites_config[$dir]['repeat'] as $repeat)
158 if($info['x'] == $repeat['width'] && $info['y'] == $repeat['height'])
160 $id = md5($repeat['width'].$repeat['height'].$repeat['direction']);
162 $this->spriteRepeat['repeat_'.$repeat['direction'].'_'.$id][$dir][$file] = $info;
169 $list[$file] = $info;
172 } else if(preg_match('/\.(jpg|jpeg|gif|png|bmp|ico)$/i', $file)) {
173 $GLOBALS['log']->error('Unable to process image file ' . $file);
174 //$this->logMessage('Unable to process image file ' . $file);
188 * This function is used to load the sprites_config.php file. The sprites_config.php file may be used to add entries
189 * to the sprites_config member variable which may contain a list of array entries of files/directories to exclude from
190 * being included into the sprites image.
192 * @param $dir String value of the directory containing the custom sprites_config.php file
194 private function loadSpritesConfig($dir) {
195 $sprites_config = array();
196 if(file_exists("$dir/sprites_config.php"))
198 include("$dir/sprites_config.php");
199 if(count($sprites_config)) {
200 $this->sprites_config = array_merge($this->sprites_config, $sprites_config);
209 * This is a private helper function to return attributes about an image. If the width, height or type of the
210 * image file cannot be determined, then we do not process the file.
212 * @return array of file info entries containing file information (x, y, type) if image type is supported
214 private function getFileInfo($dir, $file) {
216 $info = @getimagesize($dir.'/'.$file);
219 // supported image type ?
220 if(isset($this->imageTypes[$info[2]]))
226 // be sure we have an image size
231 if($w <= $this->maxWidth && $h <= $this->maxHeight)
242 $result['type'] = $info[2];
245 $msg = "Skipping unsupported image file type ({$info[2]}) for file {$file}";
246 $GLOBALS['log']->error($msg);
247 $this->logMessage($msg."\n");
257 * This is the public function to allow the sprites to be built.
259 * @return $result boolean value indicating whether or not sprites were created
261 public function createSprites() {
265 if(!$this->isAvailable)
267 if(!$this->silentRun)
269 $msg = $mod_strings['LBL_SPRITES_NOT_SUPPORTED'];
270 $GLOBALS['log']->warn($msg);
271 $this->logMessage($msg);
276 // add repeatable sprites
277 if(count($this->spriteRepeat))
279 $this->spriteSrc = array_merge($this->spriteSrc, $this->spriteRepeat);
282 foreach($this->spriteSrc as $name => $dirs)
284 if(!$this->silentRun)
286 $msg = string_format($mod_strings['LBL_SPRITES_CREATING_NAMESPACE'], array($name));
287 $GLOBALS['log']->debug($msg);
288 $this->logMessage($msg);
291 // setup config for sprite placement algorithm
292 if(substr($name, 0, 6) == 'repeat')
295 $type = substr($name, 7, 10) == 'horizontal' ? 'horizontal' : 'vertical';
303 'width' => $this->maxWidth,
304 'height' => $this->maxHeight,
305 'rowcnt' => $this->rowCnt,
309 // use seperate class to arrange the images
310 $sp = new SpritePlacement($dirs, $config);
311 $sp->processSprites();
313 //if(! $this->silentRun)
314 // echo " (size {$sp->width()}x{$sp->height()})<br />";
316 // we need a target image size
317 if($sp->width() && $sp->height())
320 $this->initSpriteImg($sp->width(), $sp->height());
322 // add sprites based upon determined coordinates
323 foreach($dirs as $dir => $files)
325 if(!$this->silentRun)
327 $msg = string_format($mod_strings['LBL_SPRITES_PROCESSING_DIR'], array($dir));
328 $GLOBALS['log']->debug($msg);
329 $this->logMessage($msg);
332 foreach($files as $file => $info)
334 if($im = $this->loadImage($dir, $file, $info['type']))
337 $dst_x = $sp->spriteMatrix[$dir.'/'.$file]['x'];
338 $dst_y = $sp->spriteMatrix[$dir.'/'.$file]['y'];
340 imagecopy($this->spriteImg, $im, $dst_x, $dst_y, 0, 0, $info['x'], $info['y']);
343 if(!$this->silentRun)
345 $msg = string_format($mod_strings['LBL_SPRITES_ADDED'], array("{$dir}/{$file}"));
346 $GLOBALS['log']->debug($msg);
347 $this->logMessage($msg);
356 $outputDir = sugar_cached("sprites/Repeatable");
357 $spriteFileName = "{$name}.png";
358 $cssFileName = "{$this->fileName}.css";
359 $metaFileName = "{$this->fileName}.meta.php";
360 $nameSpace = "Repeatable";
362 $outputDir = sugar_cached("sprites/$name");
363 $spriteFileName = "{$this->fileName}.png";
364 $cssFileName = "{$this->fileName}.css";
365 $metaFileName = "{$this->fileName}.meta.php";
366 $nameSpace = "{$name}";
369 // directory structure
370 if(!is_dir(sugar_cached("sprites/$nameSpace")))
372 sugar_mkdir(sugar_cached("sprites/$nameSpace"), 0775, true);
376 imagepng($this->spriteImg, "$outputDir/$spriteFileName", $this->pngCompression, $this->pngFilter);
377 imagedestroy($this->spriteImg);
379 /* generate css & metadata */
385 foreach($sp->spriteSrc as $id => $info)
391 $head .= "span.spr_{$hash_id},\n";
398 $offset_x = $sp->spriteMatrix[$id]['x'];
399 $offset_y = $sp->spriteMatrix[$id]['y'];
402 $body .= "/* {$id} */
403 span.spr_{$hash_id} {
406 background-position: -{$offset_x}px -{$offset_y}px;
409 $metadata .= '$sprites["'.$id.'"] = array ("class"=>"'.$hash_id.'","width"=>"'.$w.'","height"=>"'.$h.'");'."\n";
413 $head = rtrim($head, "\n,")." {background: url('../../../index.php?entryPoint=getImage&imageName={$spriteFileName}&spriteNamespace={$nameSpace}'); no-repeat;display:inline-block;}\n";
415 // append mode for repeatable sprites
416 $fileMode = $isRepeat ? 'a' : 'w';
419 $css_content = "\n/* autogenerated sprites - $name */\n".$head.$body;
422 $css_content = cssmin::minify($css_content);
424 $fh = fopen("$outputDir/$cssFileName", $fileMode);
425 fwrite($fh, $css_content);
429 $add_php_tag = (file_exists("$outputDir/$metaFileName") && $isRepeat) ? false : true;
430 $fh = fopen("$outputDir/$metaFileName", $fileMode);
433 fwrite($fh, '<?php');
435 fwrite($fh, "\n/* sprites metadata - $name */\n");
436 fwrite($fh, $metadata."\n");
442 if(!$this->silentRun)
444 $msg = string_format($mod_strings['LBL_SPRITES_ADDED'], array($name));
445 $GLOBALS['log']->debug($msg);
446 $this->logMessage($msg);
459 * @param w int value representing width of sprite
460 * @param h int value representing height of sprite
461 * Private function to initialize creating the sprite canvas image
463 private function initSpriteImg($w, $h) {
464 $this->spriteImg = imagecreatetruecolor($w,$h);
465 $transparent = imagecolorallocatealpha($this->spriteImg, 0, 0, 0, 127);
466 imagefill($this->spriteImg, 0, 0, $transparent);
467 imagealphablending($this->spriteImg, false);
468 imagesavealpha($this->spriteImg, true);
475 * private function to load image resources
477 * @param $dir String value of directory where image is located
478 * @param $file String value of file
479 * @param $type String value of the file type (IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG)
482 private function loadImage($dir, $file, $type) {
483 $path_file = $dir.'/'.$file;
486 return imagecreatefromgif($path_file);
488 return imagecreatefromjpeg($path_file);
490 return imagecreatefrompng($path_file);
499 * This is a private function used to log messages generated from this class. Depending on whether or not
500 * silentRun or fromSilentUpgrade is set to true/false then it will either output to screen or write to log file
502 * @param $msg String value of message to log into file or echo into output buffer depending on the context
504 private function logMessage($msg)
506 if(!$this->silentRun && !$this->fromSilentUpgrade)
508 echo $msg . '<br />';
509 } else if ($this->fromSilentUpgrade && $this->writeToUpgradeLog) {
510 logThis($msg, $GLOBALS['path']);
511 } else if(!$this->silentRun) {
522 class SpritePlacement
526 var $spriteMatrix = array();
531 // sprite src (flattened array)
532 var $spriteSrc = array();
534 // placement config array
546 var $config = array();
548 function __construct($spriteSrc, $config) {
550 // convert spriteSrc to flat array
551 foreach($spriteSrc as $dir => $files) {
552 foreach($files as $file => $info) {
553 // use full path as identifier
554 $full_path = $dir.'/'.$file;
555 $this->spriteSrc[$full_path] = $info;
559 $this->config = $config;
562 function processSprites() {
564 foreach($this->spriteSrc as $id => $info) {
570 // update min surface
571 $this->minSurface += $x * $y;
573 // get coordinates where to add this sprite
574 if($coor = $this->addSprite($x, $y)) {
575 $this->spriteMatrix[$id] = $coor;
580 // returns x/y coordinates to fit the sprite
581 function addSprite($w, $h) {
584 switch($this->config['type']) {
589 $spriteX = $this->config['width'];
590 $spriteY = $this->config['height'];
591 $spriteCnt = count($this->spriteMatrix) + 1;
592 $y = ceil($spriteCnt / $this->config['rowcnt']);
593 $x = $spriteCnt - (($y - 1) * $this->config['rowcnt']);
595 'x' => ($x * $spriteX) + 1 - $spriteX,
596 'y' => ($y * $spriteY) + 1 - $spriteY);
600 // horizontal -> align vertically
602 $result = array('x' => 1, 'y' => $this->height() + 1);
605 // vertical -> align horizontally
607 $result = array('x' => $this->width() + 1, 'y' => 1);
611 $GLOBALS['log']->warn(__CLASS__.": Unknown sprite placement algorithm -> {$this->config['type']}");
618 // calculate total width
620 return $this->getMaxAxis('x');
623 // calculate total height
625 return $this->getMaxAxis('y');
628 // helper function to get highest axis value
629 function getMaxAxis($axis) {
631 foreach($this->spriteMatrix as $id => $coor) {
632 $new_val = $coor[$axis] + $this->spriteSrc[$id][$axis] - 1;
633 if($new_val > $val) {