]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - modules/Administration/SugarSpriteBuilder.php
Release 6.4.0beta1
[Github/sugarcrm.git] / modules / Administration / SugarSpriteBuilder.php
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-2011 SugarCRM Inc.
6  * 
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Affero General Public License version 3 as published by the
9  * Free Software Foundation with the addition of the following permission added
10  * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
11  * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
12  * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
13  * 
14  * This program is distributed in the hope that it will be useful, but WITHOUT
15  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  * FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License for more
17  * details.
18  * 
19  * You should have received a copy of the GNU Affero General Public License along with
20  * this program; if not, see http://www.gnu.org/licenses or write to the Free
21  * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22  * 02110-1301 USA.
23  * 
24  * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
25  * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
26  * 
27  * The interactive user interfaces in modified source and object code versions
28  * of this program must display Appropriate Legal Notices, as required under
29  * Section 5 of the GNU Affero General Public License version 3.
30  * 
31  * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
32  * these Appropriate Legal Notices must retain the display of the "Powered by
33  * SugarCRM" logo. If the display of the logo is not reasonably feasible for
34  * technical reasons, the Appropriate Legal Notices must display the words
35  * "Powered by SugarCRM".
36  ********************************************************************************/
37
38
39 require_once("include/SugarTheme/cssmin.php");
40
41 class SugarSpriteBuilder
42 {
43         var $isAvailable = false;
44         var $silentRun = false;
45     var $fromSilentUpgrade = false;
46     var $writeToUpgradeLog = false;
47
48         var $debug = false;
49         var $fileName = 'sprites';
50         var $cssMinify = true;
51
52         // class supported image types
53         var $supportedTypeMap = array(
54                 IMG_GIF => IMAGETYPE_GIF,
55                 IMG_JPG => IMAGETYPE_JPEG,
56                 IMG_PNG => IMAGETYPE_PNG,
57         );
58
59         // sprite settings
60         var $pngCompression = 9;
61         var $pngFilter = PNG_NO_FILTER;
62         var $maxWidth = 75;
63         var $maxHeight = 75;
64         var $rowCnt = 30;
65
66         // processed image types
67         var $imageTypes = array();
68
69         // source files
70         var $spriteSrc = array();
71         var $spriteRepeat = array();
72
73         // sprite resource images
74         var $spriteImg;
75
76         // sprite_config collection
77         var $sprites_config = array();
78
79
80     public function __construct()
81     {
82                 // check if we have gd installed
83                 if(function_exists('imagecreatetruecolor'))
84         {
85                         $this->isAvailable = true;
86             foreach($this->supportedTypeMap as $gd_bit => $imagetype)
87             {
88                 if(imagetypes() & $gd_bit) {
89                     // swap gd_bit & imagetype
90                     $this->imageTypes[$imagetype] = $gd_bit;
91                 }
92             }
93                 }
94
95         if(function_exists('logThis') && isset($GLOBALS['path']))
96         {
97             $this->writeToUpgradeLog = true;
98         }
99         }
100
101
102     /**
103      * addDirectory
104      *
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
108      */
109         public function addDirectory($name, $dir) {
110
111                 // sprite namespace
112                 if(!array_key_exists($name, $this->spriteSrc))
113         {
114                         $this->spriteSrc[$name] = array();
115                 }
116
117                 // add files from directory
118                 $this->spriteSrc[$name][$dir] = $this->getFileList($dir);
119         }
120
121         /**
122      * getFileList
123      *
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
126      */
127         private function getFileList($dir) {
128                 $list = array();
129                 if(is_dir($dir)) {
130                         if($dh = opendir($dir)) {
131
132                                 // optional sprites_config.php file
133                                 $this->loadSpritesConfig($dir);
134
135                             while (($file = readdir($dh)) !== false)
136                 {
137                                         if ($file != "." && $file != ".." && $file != "sprites_config.php")
138                     {
139
140                                                 // file info & check supported image format 
141                                                 if($info = $this->getFileInfo($dir, $file)) {
142
143                                                         // skip excluded files
144                                                         if(isset($this->sprites_config[$dir]['exclude']) && array_search($file, $this->sprites_config[$dir]['exclude']) !== false)
145                             {
146                                 global $mod_strings;
147                                 $msg = string_format($mod_strings['LBL_SPRITES_EXCLUDING_FILE'], array("{$dir}/{$file}"));
148                                                                 $GLOBALS['log']->debug($msg);
149                                 $this->logMessage($msg);
150                                                         } else {
151                                                                 // repeatable sprite ?
152                                                                 $isRepeat = false;
153
154                                                                 if(isset($this->sprites_config[$dir]['repeat']))
155                                 {
156                                                                         foreach($this->sprites_config[$dir]['repeat'] as $repeat)
157                                     {
158                                                                                 if($info['x'] == $repeat['width'] && $info['y'] == $repeat['height'])
159                                         {
160                                                                                         $id = md5($repeat['width'].$repeat['height'].$repeat['direction']);
161                                                                                         $isRepeat = true;
162                                                                                         $this->spriteRepeat['repeat_'.$repeat['direction'].'_'.$id][$dir][$file] = $info;
163                                                                                 }
164                                                                         }
165                                                                 }
166
167                                                                 if(!$isRepeat)
168                                 {
169                                                                         $list[$file] = $info;
170                                 }
171                                                         }
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);
175                         }
176                                 }
177                                 }
178                         }
179                     closedir($dh);
180                 }
181                 return $list;
182         }
183
184
185     /**
186      * loadSpritesConfig
187      *
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.
191      *
192      * @param $dir String value of the directory containing the custom sprites_config.php file
193      */
194         private function loadSpritesConfig($dir) {
195                 $sprites_config = array();
196                 if(file_exists("$dir/sprites_config.php"))
197         {
198                         include("$dir/sprites_config.php");
199                         if(count($sprites_config)) {
200                                 $this->sprites_config = array_merge($this->sprites_config, $sprites_config);
201                         }
202                 }
203         }
204
205
206         /**
207      * getFileInfo
208      *
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.
211      *
212      * @return array of file info entries containing file information (x, y, type) if image type is supported
213      */
214         private function getFileInfo($dir, $file) {
215                 $result = false;
216                 $info = @getimagesize($dir.'/'.$file);
217                 if($info) {
218
219                         // supported image type ? 
220                         if(isset($this->imageTypes[$info[2]]))
221             {
222                                 $w = $info[0];
223                                 $h = $info[1];
224                                 $surface = $w * $h;
225
226                                 // be sure we have an image size
227                                 $addSprite = false;
228                                 if($surface)
229                 {
230                                         // sprite dimensions
231                                         if($w <= $this->maxWidth && $h <= $this->maxHeight)
232                     {
233                                                 $addSprite = true;
234                                         }
235                                 }
236
237                                 if($addSprite)
238                 {
239                                         $result = array();
240                                         $result['x'] = $w;
241                                         $result['y'] = $h;
242                                         $result['type'] = $info[2];
243                                 }
244                         } else {
245                 $msg = "Unsupported image file type ({$info[2]}) for file {$file}";
246                 $GLOBALS['log']->error($msg);
247                 $this->logMessage($msg);
248             }
249                 }
250                 return $result;
251         }
252
253
254     /**
255      * createSprites
256      *
257      * This is the public function to allow the sprites to be built.
258      *
259      * @return $result boolean value indicating whether or not sprites were created
260      */
261         public function createSprites() {
262
263         global $mod_strings;
264
265                 if(!$this->isAvailable)
266         {
267                         if(!$this->silentRun)
268             {
269                 $msg = $mod_strings['LBL_SPRITES_NOT_SUPPORTED'];
270                 $GLOBALS['log']->warn($msg);
271                 $this->logMessage($msg);
272             }
273                         return false;
274                 }
275
276                 // add repeatable sprites
277                 if(count($this->spriteRepeat))
278         {
279                         $this->spriteSrc = array_merge($this->spriteSrc, $this->spriteRepeat);
280         }
281
282                 foreach($this->spriteSrc as $name => $dirs)
283         {
284                         if(!$this->silentRun)
285             {
286                 $msg = string_format($mod_strings['LBL_SPRITES_CREATING_NAMESPACE'], array($name));
287                 $GLOBALS['log']->debug($msg);
288                                 $this->logMessage($msg);
289             }
290
291                         // setup config for sprite placement algorithm
292                         if(substr($name, 0, 6) == 'repeat')
293             {
294                                 $isRepeat = true;
295                 $type = substr($name, 7, 10) == 'horizontal' ? 'horizontal' : 'vertical';
296                                 $config = array(
297                                         'type' => $type,
298                                 );
299                         } else {
300                                 $isRepeat = false;
301                                 $config = array(
302                                         'type' => 'boxed',
303                                         'width' => $this->maxWidth,
304                                         'height' => $this->maxHeight,
305                                         'rowcnt' => $this->rowCnt,
306                                 );
307                         }
308
309                         // use seperate class to arrange the images
310                         $sp = new SpritePlacement($dirs, $config);
311                         $sp->processSprites();
312
313                         //if(! $this->silentRun)
314                         //      echo " (size {$sp->width()}x{$sp->height()})<br />";
315
316                         // we need a target image size
317                         if($sp->width() && $sp->height())
318             {
319                                 // init sprite image
320                                 $this->initSpriteImg($sp->width(), $sp->height());
321
322                                 // add sprites based upon determined coordinates
323                                 foreach($dirs as $dir => $files)
324                 {
325                                         if(!$this->silentRun)
326                     {
327                         $msg = string_format($mod_strings['LBL_SPRITES_PROCESSING_DIR'], array($dir));
328                         $GLOBALS['log']->debug($msg);
329                         $this->logMessage($msg);
330                     }
331
332                                         foreach($files as $file => $info)
333                     {
334                                                 if($im = $this->loadImage($dir, $file, $info['type']))
335                         {
336                                                         // coordinates
337                                                         $dst_x = $sp->spriteMatrix[$dir.'/'.$file]['x'];
338                                                         $dst_y = $sp->spriteMatrix[$dir.'/'.$file]['y'];
339
340                                                         imagecopy($this->spriteImg, $im, $dst_x, $dst_y, 0, 0, $info['x'], $info['y']);
341                                                         imagedestroy($im);
342
343                                                         if(!$this->silentRun)
344                             {
345                                 $msg = string_format($mod_strings['LBL_SPRITES_ADDED'], array("{$dir}/{$file}"));
346                                 $GLOBALS['log']->debug($msg);
347                                 $this->logMessage($msg);
348                             }
349                                                 }
350                                         }
351                                 }
352         
353                                 // dir & filenames 
354                                 if($isRepeat)
355                 {
356                                         $outputDir = sugar_cached("sprites/Repeatable");
357                                         $spriteFileName = "{$name}.png";
358                                         $cssFileName = "{$this->fileName}.css";
359                                         $metaFileName = "{$this->fileName}.meta.php";
360                                         $nameSpace = "Repeatable";
361                                 } else { 
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}";
367                                 }
368
369                                 // directory structure
370                                 if(!is_dir(sugar_cached("sprites/$nameSpace")))
371                 {
372                                         sugar_mkdir(sugar_cached("sprites/$nameSpace"), 0775, true);
373                 }
374
375                                 // save sprite image
376                                 imagepng($this->spriteImg, "$outputDir/$spriteFileName", $this->pngCompression, $this->pngFilter);
377                                 imagedestroy($this->spriteImg);
378
379                                 /* generate css & metadata */
380
381                                 $head = '';
382                                 $body = '';
383                                 $metadata = '';
384
385                                 foreach($sp->spriteSrc as $id => $info)
386                 {
387                                         // sprite id
388                                         $hash_id = md5($id);
389
390                                         // header
391                                         $head .= "span.spr_{$hash_id},\n";
392
393                                         // image size
394                                         $w = $info['x'];
395                                         $h = $info['y'];
396
397                                         // image offset
398                                         $offset_x = $sp->spriteMatrix[$id]['x'];
399                                         $offset_y = $sp->spriteMatrix[$id]['y'];
400
401                                         // sprite css
402                                         $body .= "/* {$id} */
403 span.spr_{$hash_id} {
404 width: {$w}px;
405 height: {$h}px;
406 background-position: -{$offset_x}px -{$offset_y}px;
407 }\n";
408
409                                         $metadata .= '$sprites["'.$id.'"] = array ("class"=>"'.$hash_id.'","width"=>"'.$w.'","height"=>"'.$h.'");'."\n";
410                                 } 
411
412                                 // common css header
413                                 //$head .= "span.spr_bogus {background: url('{$GLOBALS['sugar_config']['site_url']}/index.php?entryPoint=getImage&imageName={$spriteFileName}&spriteNamespace={$nameSpace}') no-repeat;display: inline-block";
414                                 $head .= "span.spr_bogus {background: url('../../../index.php?entryPoint=getImage&imageName={$spriteFileName}&spriteNamespace={$nameSpace}'); no-repeat;display:inline-block;\n";
415
416                                 // append mode for repeatable sprites
417                 $fileMode = $isRepeat ? 'a' : 'w';
418
419                                 // save css
420                                 $css_content = "\n/* autogenerated sprites - $name */\n".$head.$body;
421                                 if($this->cssMinify)
422                 {
423                                         $css_content = cssmin::minify($css_content);
424                 }
425                                 $fh = fopen("$outputDir/$cssFileName", $fileMode);
426                                 fwrite($fh, $css_content);
427                                 fclose($fh);
428
429                                 /* save metadata */
430                                 $add_php_tag = (file_exists("$outputDir/$metaFileName") && $isRepeat) ? false : true;
431                                 $fh = fopen("$outputDir/$metaFileName", $fileMode);
432                                 if($add_php_tag)
433                 {
434                                         fwrite($fh, '<?php');
435                 }
436                                 fwrite($fh, "\n/* sprites metadata - $name */\n");
437                                 fwrite($fh, $metadata."\n");
438                                 fclose($fh);
439
440                         // if width & height
441                         } else {
442
443                                 if(!$this->silentRun)
444                 {
445                     $msg = string_format($mod_strings['LBL_SPRITES_ADDED'], array($name));
446                     $GLOBALS['log']->debug($msg);
447                     $this->logMessage($msg);
448                 }
449
450                         }
451
452                 }
453                 return true;
454         }
455
456
457         /**
458      * initSpriteImg
459      *
460      * @param w int value representing width of sprite
461      * @param h int value representing height of sprite
462      * Private function to initialize creating the sprite canvas image
463      */
464         private function initSpriteImg($w, $h) {
465                 $this->spriteImg = imagecreatetruecolor($w,$h);
466                 $transparent = imagecolorallocatealpha($this->spriteImg, 0, 0, 0, 127);
467                 imagefill($this->spriteImg, 0, 0, $transparent);
468                 imagealphablending($this->spriteImg, false);
469                 imagesavealpha($this->spriteImg, true);
470         } 
471
472
473         /**
474      * loadImage
475      *
476      * private function to load image resources
477      *
478      * @param $dir String value of directory where image is located
479      * @param $file String value of file
480      * @param $type String value of the file type (IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG)
481      *
482      */
483         private function loadImage($dir, $file, $type) {
484                 $path_file = $dir.'/'.$file;
485                 switch($type) {
486                         case IMAGETYPE_GIF:
487                                 return imagecreatefromgif($path_file);
488                         case IMAGETYPE_JPEG:
489                                 return imagecreatefromjpeg($path_file);
490                         case IMAGETYPE_PNG:
491                                 return imagecreatefrompng($path_file);
492                         default:
493                                 return false;
494                 }
495         }
496
497     /**
498      * private logMessage
499      *
500      * This is a private function used to log messages generated from this class.  Depending on whether or not
501      * silentRun or fromSilentUpgrade is set to true/false then it will either output to screen or write to log file
502      *
503      * @param $msg String value of message to log into file or echo into output buffer depending on the context
504      */
505     private function logMessage($msg)
506     {
507         if(!$this->silentRun && !$this->fromSilentUpgrade)
508         {
509             echo $msg . '</br>';
510         } else if ($this->fromSilentUpgrade && $this->writeToUpgradeLog) {
511             logThis($msg, $GLOBALS['path']);
512         } else if(!$this->silentRun) {
513             echo $msg . "\n";
514         }
515     }
516 }
517
518
519 /**
520  * SpritePlacement
521  * 
522  */
523 class SpritePlacement
524 {
525
526         // occupied space
527         var $spriteMatrix = array();
528
529         // minimum surface
530         var $minSurface = 0;
531
532         // sprite src (flattened array)
533         var $spriteSrc = array();
534
535         // placement config array
536         /*
537                 type =  boxed
538                                 horizontal
539                                 vertical
540                 
541                 required params for
542                 type 1  -> width
543                                 -> height
544                                 -> rowcnt
545
546         */
547         var $config = array();
548
549         function __construct($spriteSrc, $config) {
550
551                 // convert spriteSrc to flat array 
552                 foreach($spriteSrc as $dir => $files) {
553                         foreach($files as $file => $info) {
554                                 // use full path as identifier
555                                 $full_path = $dir.'/'.$file;
556                                 $this->spriteSrc[$full_path] = $info;
557                         }
558                 }
559
560                 $this->config = $config;
561         }
562
563         function processSprites() {
564
565                 foreach($this->spriteSrc as $id => $info) {
566
567                         // dimensions
568                         $x = $info['x'];
569                         $y = $info['y'];
570                         
571                         // update min surface
572                         $this->minSurface += $x * $y;
573
574                         // get coordinates where to add this sprite
575                         if($coor = $this->addSprite($x, $y)) {
576                                 $this->spriteMatrix[$id] = $coor;
577                         }
578                 }
579         }
580
581         // returns x/y coordinates to fit the sprite
582         function addSprite($w, $h) {
583                 $result = false;
584
585                 switch($this->config['type']) {
586
587                         // boxed
588                         case 'boxed':
589
590                                 $spriteX = $this->config['width'];
591                                 $spriteY = $this->config['height'];
592                                 $spriteCnt = count($this->spriteMatrix) + 1;
593                                 $y = ceil($spriteCnt / $this->config['rowcnt']);
594                                 $x = $spriteCnt - (($y - 1) * $this->config['rowcnt']);
595                                 $result = array(
596                                         'x' => ($x * $spriteX) + 1 - $spriteX, 
597                                         'y' => ($y * $spriteY) + 1 - $spriteY);
598
599                                 break;
600
601                         // horizontal -> align vertically
602                         case 'horizontal':
603                                 $result = array('x' => 1, 'y' => $this->height() + 1);
604                                 break;
605
606                         // vertical -> align horizontally
607                         case 'vertical':
608                                 $result = array('x' => $this->width() + 1, 'y' => 1);
609                                 break;
610
611                         default:
612                                 $GLOBALS['log']->warn(__CLASS__.": Unknown sprite placement algorithm -> {$this->config['type']}");
613                                 break;
614                 }
615
616                 return $result;
617         }
618
619         // calculate total width
620         function width() {
621                 return $this->getMaxAxis('x');
622         }
623
624         // calculate total height
625         function height() {
626                 return $this->getMaxAxis('y');
627         }
628
629         // helper function to get highest axis value
630         function getMaxAxis($axis) {
631                 $val = 0;
632                 foreach($this->spriteMatrix as $id => $coor) {
633                         $new_val = $coor[$axis] + $this->spriteSrc[$id][$axis] - 1;
634                         if($new_val > $val) {
635                                 $val = $new_val;
636                         }
637                 }
638                 return $val;
639         }
640 }
641
642 ?>