/modules/Administration/SugarSpriteBuilder.php
PHP | 643 lines | 379 code | 96 blank | 168 comment | 68 complexity | 95bb50747b7f6903959bb58404c4a586 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, BSD-3-Clause
- <?php
- if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
- /*********************************************************************************
- * SugarCRM Community Edition is a customer relationship management program developed by
- * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU Affero General Public License version 3 as published by the
- * Free Software Foundation with the addition of the following permission added
- * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
- * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
- * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
- *
- * This program is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
- * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
- * details.
- *
- * You should have received a copy of the GNU Affero General Public License along with
- * this program; if not, see http://www.gnu.org/licenses or write to the Free
- * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
- * 02110-1301 USA.
- *
- * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
- * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
- *
- * The interactive user interfaces in modified source and object code versions
- * of this program must display Appropriate Legal Notices, as required under
- * Section 5 of the GNU Affero General Public License version 3.
- *
- * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
- * these Appropriate Legal Notices must retain the display of the "Powered by
- * SugarCRM" logo. If the display of the logo is not reasonably feasible for
- * technical reasons, the Appropriate Legal Notices must display the words
- * "Powered by SugarCRM".
- ********************************************************************************/
- require_once("include/SugarTheme/cssmin.php");
- class SugarSpriteBuilder
- {
- var $isAvailable = false;
- var $silentRun = false;
- var $fromSilentUpgrade = false;
- var $writeToUpgradeLog = false;
- var $debug = false;
- var $fileName = 'sprites';
- var $cssMinify = true;
- // class supported image types
- var $supportedTypeMap = array(
- IMG_GIF => IMAGETYPE_GIF,
- IMG_JPG => IMAGETYPE_JPEG,
- IMG_PNG => IMAGETYPE_PNG,
- );
- // sprite settings
- var $pngCompression = 9;
- var $pngFilter = PNG_NO_FILTER;
- var $maxWidth = 75;
- var $maxHeight = 75;
- var $rowCnt = 30;
- // processed image types
- var $imageTypes = array();
- // source files
- var $spriteSrc = array();
- var $spriteRepeat = array();
- // sprite resource images
- var $spriteImg;
- // sprite_config collection
- var $sprites_config = array();
- public function __construct()
- {
- // check if we have gd installed
- if(function_exists('imagecreatetruecolor'))
- {
- $this->isAvailable = true;
- foreach($this->supportedTypeMap as $gd_bit => $imagetype)
- {
- if(imagetypes() & $gd_bit) {
- // swap gd_bit & imagetype
- $this->imageTypes[$imagetype] = $gd_bit;
- }
- }
- }
- if(function_exists('logThis') && isset($GLOBALS['path']))
- {
- $this->writeToUpgradeLog = true;
- }
- }
- /**
- * addDirectory
- *
- * This function is used to create the spriteSrc array
- * @param $name String value of the sprite name
- * @param $dir String value of the directory associated with the sprite entry
- */
- public function addDirectory($name, $dir) {
- // sprite namespace
- if(!array_key_exists($name, $this->spriteSrc))
- {
- $this->spriteSrc[$name] = array();
- }
- // add files from directory
- $this->spriteSrc[$name][$dir] = $this->getFileList($dir);
- }
- /**
- * getFileList
- *
- * This method processes files in a directory and adds them to the sprites array
- * @param $dir String value of the directory to scan for image files in
- */
- private function getFileList($dir) {
- $list = array();
- if(is_dir($dir)) {
- if($dh = opendir($dir)) {
- // optional sprites_config.php file
- $this->loadSpritesConfig($dir);
- while (($file = readdir($dh)) !== false)
- {
- if ($file != "." && $file != ".." && $file != "sprites_config.php")
- {
- // file info & check supported image format
- if($info = $this->getFileInfo($dir, $file)) {
- // skip excluded files
- if(isset($this->sprites_config[$dir]['exclude']) && array_search($file, $this->sprites_config[$dir]['exclude']) !== false)
- {
- global $mod_strings;
- $msg = string_format($mod_strings['LBL_SPRITES_EXCLUDING_FILE'], array("{$dir}/{$file}"));
- $GLOBALS['log']->debug($msg);
- $this->logMessage($msg);
- } else {
- // repeatable sprite ?
- $isRepeat = false;
- if(isset($this->sprites_config[$dir]['repeat']))
- {
- foreach($this->sprites_config[$dir]['repeat'] as $repeat)
- {
- if($info['x'] == $repeat['width'] && $info['y'] == $repeat['height'])
- {
- $id = md5($repeat['width'].$repeat['height'].$repeat['direction']);
- $isRepeat = true;
- $this->spriteRepeat['repeat_'.$repeat['direction'].'_'.$id][$dir][$file] = $info;
- }
- }
- }
- if(!$isRepeat)
- {
- $list[$file] = $info;
- }
- }
- } else if(preg_match('/\.(jpg|jpeg|gif|png|bmp|ico)$/i', $file)) {
- $GLOBALS['log']->error('Unable to process image file ' . $file);
- //$this->logMessage('Unable to process image file ' . $file);
- }
- }
- }
- }
- closedir($dh);
- }
- return $list;
- }
- /**
- * loadSpritesConfig
- *
- * This function is used to load the sprites_config.php file. The sprites_config.php file may be used to add entries
- * to the sprites_config member variable which may contain a list of array entries of files/directories to exclude from
- * being included into the sprites image.
- *
- * @param $dir String value of the directory containing the custom sprites_config.php file
- */
- private function loadSpritesConfig($dir) {
- $sprites_config = array();
- if(file_exists("$dir/sprites_config.php"))
- {
- include("$dir/sprites_config.php");
- if(count($sprites_config)) {
- $this->sprites_config = array_merge($this->sprites_config, $sprites_config);
- }
- }
- }
- /**
- * getFileInfo
- *
- * This is a private helper function to return attributes about an image. If the width, height or type of the
- * image file cannot be determined, then we do not process the file.
- *
- * @return array of file info entries containing file information (x, y, type) if image type is supported
- */
- private function getFileInfo($dir, $file) {
- $result = false;
- $info = @getimagesize($dir.'/'.$file);
- if($info) {
- // supported image type ?
- if(isset($this->imageTypes[$info[2]]))
- {
- $w = $info[0];
- $h = $info[1];
- $surface = $w * $h;
- // be sure we have an image size
- $addSprite = false;
- if($surface)
- {
- // sprite dimensions
- if($w <= $this->maxWidth && $h <= $this->maxHeight)
- {
- $addSprite = true;
- }
- }
- if($addSprite)
- {
- $result = array();
- $result['x'] = $w;
- $result['y'] = $h;
- $result['type'] = $info[2];
- }
- } else {
- $msg = "Skipping unsupported image file type ({$info[2]}) for file {$file}";
- $GLOBALS['log']->error($msg);
- $this->logMessage($msg."\n");
- }
- }
- return $result;
- }
- /**
- * createSprites
- *
- * This is the public function to allow the sprites to be built.
- *
- * @return $result boolean value indicating whether or not sprites were created
- */
- public function createSprites() {
- global $mod_strings;
- if(!$this->isAvailable)
- {
- if(!$this->silentRun)
- {
- $msg = $mod_strings['LBL_SPRITES_NOT_SUPPORTED'];
- $GLOBALS['log']->warn($msg);
- $this->logMessage($msg);
- }
- return false;
- }
- // add repeatable sprites
- if(count($this->spriteRepeat))
- {
- $this->spriteSrc = array_merge($this->spriteSrc, $this->spriteRepeat);
- }
- foreach($this->spriteSrc as $name => $dirs)
- {
- if(!$this->silentRun)
- {
- $msg = string_format($mod_strings['LBL_SPRITES_CREATING_NAMESPACE'], array($name));
- $GLOBALS['log']->debug($msg);
- $this->logMessage($msg);
- }
- // setup config for sprite placement algorithm
- if(substr($name, 0, 6) == 'repeat')
- {
- $isRepeat = true;
- $type = substr($name, 7, 10) == 'horizontal' ? 'horizontal' : 'vertical';
- $config = array(
- 'type' => $type,
- );
- } else {
- $isRepeat = false;
- $config = array(
- 'type' => 'boxed',
- 'width' => $this->maxWidth,
- 'height' => $this->maxHeight,
- 'rowcnt' => $this->rowCnt,
- );
- }
- // use separate class to arrange the images
- $sp = new SpritePlacement($dirs, $config);
- $sp->processSprites();
- //if(! $this->silentRun)
- // echo " (size {$sp->width()}x{$sp->height()})<br />";
- // we need a target image size
- if($sp->width() && $sp->height())
- {
- // init sprite image
- $this->initSpriteImg($sp->width(), $sp->height());
- // add sprites based upon determined coordinates
- foreach($dirs as $dir => $files)
- {
- if(!$this->silentRun)
- {
- $msg = string_format($mod_strings['LBL_SPRITES_PROCESSING_DIR'], array($dir));
- $GLOBALS['log']->debug($msg);
- $this->logMessage($msg);
- }
- foreach($files as $file => $info)
- {
- if($im = $this->loadImage($dir, $file, $info['type']))
- {
- // coordinates
- $dst_x = $sp->spriteMatrix[$dir.'/'.$file]['x'];
- $dst_y = $sp->spriteMatrix[$dir.'/'.$file]['y'];
- imagecopy($this->spriteImg, $im, $dst_x, $dst_y, 0, 0, $info['x'], $info['y']);
- imagedestroy($im);
- if(!$this->silentRun)
- {
- $msg = string_format($mod_strings['LBL_SPRITES_ADDED'], array("{$dir}/{$file}"));
- $GLOBALS['log']->debug($msg);
- $this->logMessage($msg);
- }
- }
- }
- }
- // dir & filenames
- if($isRepeat)
- {
- $outputDir = sugar_cached("sprites/Repeatable");
- $spriteFileName = "{$name}.png";
- $cssFileName = "{$this->fileName}.css";
- $metaFileName = "{$this->fileName}.meta.php";
- $nameSpace = "Repeatable";
- } else {
- $outputDir = sugar_cached("sprites/$name");
- $spriteFileName = "{$this->fileName}.png";
- $cssFileName = "{$this->fileName}.css";
- $metaFileName = "{$this->fileName}.meta.php";
- $nameSpace = "{$name}";
- }
- // directory structure
- if(!is_dir(sugar_cached("sprites/$nameSpace")))
- {
- sugar_mkdir(sugar_cached("sprites/$nameSpace"), 0775, true);
- }
- // save sprite image
- imagepng($this->spriteImg, "$outputDir/$spriteFileName", $this->pngCompression, $this->pngFilter);
- imagedestroy($this->spriteImg);
- /* generate css & metadata */
- $head = '';
- $body = '';
- $metadata = '';
- foreach($sp->spriteSrc as $id => $info)
- {
- // sprite id
- $hash_id = md5($id);
- // header
- $head .= "span.spr_{$hash_id},\n";
- // image size
- $w = $info['x'];
- $h = $info['y'];
- // image offset
- $offset_x = $sp->spriteMatrix[$id]['x'];
- $offset_y = $sp->spriteMatrix[$id]['y'];
- // sprite css
- $body .= "/* {$id} */
- span.spr_{$hash_id} {
- width: {$w}px;
- height: {$h}px;
- background-position: -{$offset_x}px -{$offset_y}px;
- }\n";
- $metadata .= '$sprites["'.$id.'"] = array ("class"=>"'.$hash_id.'","width"=>"'.$w.'","height"=>"'.$h.'");'."\n";
- }
- // common css header
- require_once('include/utils.php');
- $bg_path = getVersionedPath('index.php').'&entryPoint=getImage&imageName='.$spriteFileName.'&spriteNamespace='.$nameSpace;
- $head = rtrim($head, "\n,")." {background: url('../../../{$bg_path}'); no-repeat;display:inline-block;}\n";
- // append mode for repeatable sprites
- $fileMode = $isRepeat ? 'a' : 'w';
- // save css
- $css_content = "\n/* autogenerated sprites - $name */\n".$head.$body;
- if($this->cssMinify)
- {
- $css_content = cssmin::minify($css_content);
- }
- $fh = fopen("$outputDir/$cssFileName", $fileMode);
- fwrite($fh, $css_content);
- fclose($fh);
- /* save metadata */
- $add_php_tag = (file_exists("$outputDir/$metaFileName") && $isRepeat) ? false : true;
- $fh = fopen("$outputDir/$metaFileName", $fileMode);
- if($add_php_tag)
- {
- fwrite($fh, '<?php');
- }
- fwrite($fh, "\n/* sprites metadata - $name */\n");
- fwrite($fh, $metadata."\n");
- fclose($fh);
- // if width & height
- } else {
- if(!$this->silentRun)
- {
- $msg = string_format($mod_strings['LBL_SPRITES_ADDED'], array($name));
- $GLOBALS['log']->debug($msg);
- $this->logMessage($msg);
- }
- }
- }
- return true;
- }
- /**
- * initSpriteImg
- *
- * @param w int value representing width of sprite
- * @param h int value representing height of sprite
- * Private function to initialize creating the sprite canvas image
- */
- private function initSpriteImg($w, $h) {
- $this->spriteImg = imagecreatetruecolor($w,$h);
- $transparent = imagecolorallocatealpha($this->spriteImg, 0, 0, 0, 127);
- imagefill($this->spriteImg, 0, 0, $transparent);
- imagealphablending($this->spriteImg, false);
- imagesavealpha($this->spriteImg, true);
- }
- /**
- * loadImage
- *
- * private function to load image resources
- *
- * @param $dir String value of directory where image is located
- * @param $file String value of file
- * @param $type String value of the file type (IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG)
- *
- */
- private function loadImage($dir, $file, $type) {
- $path_file = $dir.'/'.$file;
- switch($type) {
- case IMAGETYPE_GIF:
- return imagecreatefromgif($path_file);
- case IMAGETYPE_JPEG:
- return imagecreatefromjpeg($path_file);
- case IMAGETYPE_PNG:
- return imagecreatefrompng($path_file);
- default:
- return false;
- }
- }
- /**
- * private logMessage
- *
- * This is a private function used to log messages generated from this class. Depending on whether or not
- * silentRun or fromSilentUpgrade is set to true/false then it will either output to screen or write to log file
- *
- * @param $msg String value of message to log into file or echo into output buffer depending on the context
- */
- private function logMessage($msg)
- {
- if(!$this->silentRun && !$this->fromSilentUpgrade)
- {
- echo $msg . '<br />';
- } else if ($this->fromSilentUpgrade && $this->writeToUpgradeLog) {
- logThis($msg, $GLOBALS['path']);
- } else if(!$this->silentRun) {
- echo $msg . "\n";
- }
- }
- }
- /**
- * SpritePlacement
- *
- */
- class SpritePlacement
- {
- // occupied space
- var $spriteMatrix = array();
- // minimum surface
- var $minSurface = 0;
- // sprite src (flattened array)
- var $spriteSrc = array();
- // placement config array
- /*
- type = boxed
- horizontal
- vertical
- required params for
- type 1 -> width
- -> height
- -> rowcnt
- */
- var $config = array();
- function __construct($spriteSrc, $config) {
- // convert spriteSrc to flat array
- foreach($spriteSrc as $dir => $files) {
- foreach($files as $file => $info) {
- // use full path as identifier
- $full_path = $dir.'/'.$file;
- $this->spriteSrc[$full_path] = $info;
- }
- }
- $this->config = $config;
- }
- function processSprites() {
- foreach($this->spriteSrc as $id => $info) {
- // dimensions
- $x = $info['x'];
- $y = $info['y'];
- // update min surface
- $this->minSurface += $x * $y;
- // get coordinates where to add this sprite
- if($coor = $this->addSprite($x, $y)) {
- $this->spriteMatrix[$id] = $coor;
- }
- }
- }
- // returns x/y coordinates to fit the sprite
- function addSprite($w, $h) {
- $result = false;
- switch($this->config['type']) {
- // boxed
- case 'boxed':
- $spriteX = $this->config['width'];
- $spriteY = $this->config['height'];
- $spriteCnt = count($this->spriteMatrix) + 1;
- $y = ceil($spriteCnt / $this->config['rowcnt']);
- $x = $spriteCnt - (($y - 1) * $this->config['rowcnt']);
- $result = array(
- 'x' => ($x * $spriteX) + 1 - $spriteX,
- 'y' => ($y * $spriteY) + 1 - $spriteY);
- break;
- // horizontal -> align vertically
- case 'horizontal':
- $result = array('x' => 1, 'y' => $this->height() + 1);
- break;
- // vertical -> align horizontally
- case 'vertical':
- $result = array('x' => $this->width() + 1, 'y' => 1);
- break;
- default:
- $GLOBALS['log']->warn(__CLASS__.": Unknown sprite placement algorithm -> {$this->config['type']}");
- break;
- }
- return $result;
- }
- // calculate total width
- function width() {
- return $this->getMaxAxis('x');
- }
- // calculate total height
- function height() {
- return $this->getMaxAxis('y');
- }
- // helper function to get highest axis value
- function getMaxAxis($axis) {
- $val = 0;
- foreach($this->spriteMatrix as $id => $coor) {
- $new_val = $coor[$axis] + $this->spriteSrc[$id][$axis] - 1;
- if($new_val > $val) {
- $val = $new_val;
- }
- }
- return $val;
- }
- }
- ?>