Overview

Namespaces

  • RM
    • AssetsCollector
      • Compilers

Classes

  • AssetsCollector
  • Header
  • Overview
  • Namespace
  • Class
  • Tree
  1: <?php
  2: namespace RM;
  3: 
  4: use Nette\Object,
  5:     Nette\FileNotFoundException,
  6:     Nette\InvalidArgumentException,
  7:     Nette\Utils\Finder;
  8: 
  9: /**
 10:  * Class for collecting CSS and JS files in PHP framework Nette.
 11:  *
 12:  * @author Roman Mátyus
 13:  * @copyright (c) Roman Mátyus 2012
 14:  * @license MIT
 15:  */
 16: class AssetsCollector extends Object
 17: {
 18:     /** File type */
 19:     const CSS = "css";
 20: 
 21:     /** File type */
 22:     const JS = "js";
 23: 
 24:     /** @var array of attached css files */
 25:     protected $css = array();
 26: 
 27:     /** @var string base path for css files */
 28:     public $cssPath;
 29: 
 30:     /** @var array of attached js files */
 31:     protected $js = array();
 32: 
 33:     /** @var string base path for js files */
 34:     public $jsPath;
 35: 
 36:     /** @var string path for temporary folder */
 37:     public $webTemp;
 38: 
 39:     /** @var boolean remove all old files? */
 40:     public $removeOld;
 41: 
 42:     /** @var array functions for compile css files */
 43:     public $cssCompiler = array();
 44: 
 45:     /** @var array functions for compile js files */
 46:     public $jsCompiler = array();
 47: 
 48:     /** @var array packages */
 49:     private $packages;
 50: 
 51:     /** @var array enabled compilers */
 52:     public $enabledCompilers;
 53: 
 54:     /** @var array used packages of files */
 55:     private $usedPackages;
 56: 
 57:     /** @var boolean merge files to one file? */
 58:     public $mergeFiles;
 59: 
 60:     /**
 61:      * Add css files to header.
 62:      * @param   file string|array
 63:      * @param   dir null|string dir for find file by relative path
 64:      * @return  AssetsCollector
 65:      */
 66:     public function addCss($file,$dir=null)
 67:     {
 68:         if (is_string($file)) {
 69:             $f = self::findFile($file,array($this->cssPath,$dir));
 70:             $fileNameOutput = $this->getTempFromFile($f,self::CSS);
 71:             if (!in_array($fileNameOutput,$this->css))
 72:                 $this->css[] = $fileNameOutput;
 73:         } elseif (is_array($file)) {
 74:             foreach ($file as $item) {
 75:                 $f = self::findFile($item,array($this->cssPath,$dir));
 76:                 $fileNameOutput = $this->getTempFromFile($f,self::CSS);
 77:                 if (!in_array($fileNameOutput,$this->css))
 78:                     $this->css[] = $fileNameOutput;
 79:             }
 80:         }
 81:         return $this;
 82:     }
 83: 
 84:     /**
 85:      * Add js files to header.
 86:      * @param   file string|array
 87:      * @param   dir null|string direcory for find file by relative path
 88:      * @return  AssetsCollector
 89:      */
 90:     public function addJs($file,$dir=null)
 91:     {
 92:         if (is_string($file)) {
 93:             $f = self::findFile($file,array($this->jsPath,$dir));
 94:             $fileNameOutput = $this->getTempFromFile($f,self::JS);
 95:             if (!in_array($fileNameOutput,$this->js))
 96:                 $this->js[] = $fileNameOutput;
 97:         } elseif (is_array($file)) {
 98:             foreach ($file as $item) {
 99:                 $f = self::findFile($item,array($this->jsPath,$dir));
100:                 $fileNameOutput = $this->getTempFromFile($f,self::JS);
101:                 if (!in_array($fileNameOutput,$this->js))
102:                     $this->js[] = $fileNameOutput;
103:             }
104:         }
105:         return $this;
106:     }
107: 
108:     /**
109:      * Add CSS files to header from plain entry.
110:      * @param   content string
111:      * @return  AssetsCollector
112:      */
113:     public function addCssContent($content,$dir=null)
114:     {
115:         $fileNameOutput = $this->getTempFromContent($content,$dir=null,self::CSS);
116:         if (!in_array($fileNameOutput,$this->css))
117:             $this->css[] = $fileNameOutput;
118:         return $this;
119:     }
120: 
121:     /**
122:      * Add JS files to header from plain entry.
123:      * @param   content string
124:      * @param   dirs null|array where searches
125:      * @return  AssetsCollector
126:      */
127:     public function addJsContent($content,$dir=null)
128:     {
129:         $fileNameOutput = $this->getTempFromContent($content,$dir=null,self::JS);
130:         if (!in_array($fileNameOutput,$this->js))
131:             $this->js[] = $fileNameOutput;
132:         return $this;
133:     }
134: 
135:     /**
136:      * Find file in several directories.
137:      * @param   filename string name of file
138:      * @param   dirs null|array where searches
139:      * @return  string findet file
140:      */
141:     public static function findFile($filename, array $dirs = NULL)
142:     {
143:         if (!empty($dirs))
144:             foreach ($dirs as $dir)
145:                 if (file_exists($dir."/".$filename))
146:                     return realpath($dir."/".$filename);
147:         if (file_exists($filename))
148:             return realpath($filename);
149:         throw new FileNotFoundException("File '" . $filename . "' not found.");
150:     }
151: 
152:     /**
153:      * Method for add string in to file name e.g. filename.css => filename.string.css.
154:      * @param   filename string
155:      * @param   string string
156:      * @return  string new filename with string in filename
157:      */
158:     private function addToFileName($filename,$string)
159:     {
160:         $path = explode("/",$filename);
161:         $filename = array_pop($path);
162:         $filename = explode(".",$filename);
163:         $extension = array_pop($filename);
164:         $filename[] = $string;
165:         $filename = implode(".",$filename).".".$extension;
166:         return implode("/",$path)."/".$filename;
167:     }
168: 
169:     /**
170:      * Create temporary filename of CSS or JS files from path.
171:      * @param   source string filename with complete path
172:      * @param   type string type of files self::CSS or self::JS
173:      * @return  filename with complete path in temporary directory
174:      */
175:     private function getTempFromFile($source,$type)
176:     {
177:         $content = file_get_contents($source);
178:         $compile_function = 'compile'.$type;
179:         $content = $this->$compile_function($content,dirname($source));
180: 
181:         $md5 = md5($content);
182:         $filename = explode("/",self::addToFileName($source,$md5));
183:         $fileNameOutput = array_pop($filename);
184:         if (!file_exists($this->webTemp."/".$fileNameOutput))
185:             file_put_contents($this->webTemp."/".$fileNameOutput,$content);
186: 
187:         // Remove all old versions
188:         $this->removeAllOldFiles($source, $fileNameOutput);
189: 
190:         // return real path
191:         return substr($this->webTemp,strlen(WWW_DIR))."/".$fileNameOutput;
192:     }
193: 
194:     /**
195:      * Create temporary filename of CSS or JS files from content.
196:      * @param   content string content of generated file
197:      * @param   dirs null|array where searches
198:      * @param   type string type of files self::CSS or self::JS
199:      * @return filename with complete path in temporary directory
200:      */
201:     private function getTempFromContent($content,$dir=null,$type)
202:     {
203:         if (strlen($content)===0)
204:             throw new InvalidArgumentException("Content of generated file can not be empty.");
205: 
206:         // run compilers
207:         $compile_function = 'compile'.$type;
208:         $content = $this->$compile_function($content,$dir);
209: 
210:         $md5 = md5($content);
211:         $fileNameOutput = $md5.".".$type;
212:         if (!file_exists($this->webTemp."/".$fileNameOutput))
213:             file_put_contents($this->webTemp."/".$fileNameOutput,$content);
214: 
215:         // return real path
216:         return substr($this->webTemp,strlen(WWW_DIR))."/".$fileNameOutput;
217:     }
218: 
219:     /**
220:      * Get all CSS temporary files for use in header.
221:      * @return  array of all files includet to header
222:      */
223:     public function getCss()
224:     {
225:         if ($this->mergeFiles)
226:             $this->css = array($this->mergeFiles($this->css,self::CSS));
227:         $css_output = array();
228:         foreach ($this->css as $source => $temp) {
229:             $css_output[] = $temp;
230:         }
231:         return $css_output;
232:     }
233: 
234:     /**
235:      * Get all JS temporary files for use in header.
236:      * @return  array of all files includet to header
237:      */
238:     public function getJs()
239:     {
240:         if ($this->mergeFiles)
241:             $this->js = array($this->mergeFiles($this->js,self::JS));
242:         $js_output = array();
243:         foreach ($this->js as $source => $temp) {
244:             $js_output[] = $temp;
245:         }
246:         return $js_output;
247:     }
248: 
249:     /**
250:      * Remove all old files.
251:      * @param   source string source file
252:      * @param   fileNameOutput string of compiled source file
253:      */
254:     private function removeAllOldFiles($source, $fileNameOutput)
255:     {
256:         if ($this->removeOld) {
257:             $d = explode("/",$source);
258:             $f = explode(".",array_pop($d));
259:             $ext = array_pop($f);
260:             foreach (Finder::findFiles(implode(".",$f).'*.'.$ext)
261:                 ->exclude($fileNameOutput)->from($this->webTemp) as $file) {
262:                 unlink($file->getRealPath());
263:             }
264:         }
265:     }
266: 
267:     /**
268:      * Check requirements.
269:      */
270:     public function checkRequirements()
271:     {
272:         if (is_null($this->webTemp))
273:             throw new InvalidArgumentException("Directory for temporary files is not defined.");
274:         if (!is_dir($this->webTemp))
275:             throw new FileNotFoundException($this->webTemp." is not directory.");
276:         if (!is_writable($this->webTemp))
277:             throw new InvalidArgumentException("Directory '".$this->webTemp."' is not writeable.");
278:     }
279: 
280:     /**
281:      * Apply enabled CSS file compilers.
282:      * @param   content string input
283:      * @param   dirs null|array where searches
284:      * @return  string content after compile
285:      */
286:     private function compileCss($content,$dir=null)
287:     {
288:         foreach ($this->cssCompiler as $name => $compiler) {
289:             if (in_array($name,$this->enabledCompilers))
290:                 $content = $compiler($content,$dir);
291:         }
292:         return $content;
293:     }
294: 
295:     /**
296:      * Apply enabled JS file compilers.
297:      * @param   content string input
298:      * @param   dirs null|array where searches
299:      * @return  string content after compile
300:      */
301:     private function compileJs($content,$dir=null)
302:     {
303:         foreach ($this->jsCompiler as $compiler) {
304:             $content = $compiler($content,$dir);
305:         }
306:         return $content;
307:     }
308: 
309:     /**
310:      * Add CSS compiler.
311:      * @param   array with items \RM\AssetsCollector\Compilers\IAssetsCompiler
312:      */
313:     public function addCssCompiler(array $compilers)
314:     {
315:         foreach ($compilers as $compiler) {
316:             $name = explode("\\",get_class($compiler));
317:             $name = lcfirst(array_pop($name));
318:             if ($compiler instanceof \RM\AssetsCollector\Compilers\IAssetsCompiler)
319:                 $this->cssCompiler[$name] = callback($compiler, 'compile');
320:             else
321:                 throw new InvalidArgumentException("Compiler must be instance of \RM\AssetsCollector\Compilers\IAssetsCompiler");
322:         }
323:     }
324: 
325:     /**
326:      * Add JS compiler.
327:      * @param   compilers array with items \RM\AssetsCollector\Compilers\IAssetsCompiler
328:      */
329:     public function addJsCompiler(array $compilers)
330:     {
331:         foreach ($compilers as $compiler) {
332:             $name = explode("\\",get_class($compiler));
333:             $name = lcfirst(array_pop($name));
334:             if ($compiler instanceof \RM\AssetsCollector\Compilers\IAssetsCompiler)
335:                 $this->jsCompiler[$name] = callback($compiler, 'compile');
336:             else
337:                 throw new InvalidArgumentException("Compiler must be instance of \RM\AssetsCollector\Compilers\IAssetsCompiler");
338:         }
339:     }
340: 
341:     /**
342:      * Set package to service.
343:      * @param   name string name of package
344:      * @param   extends null|array of packages where this package extends
345:      * @param   css null|array of included CSS files
346:      * @param   js null|array of included JS files
347:      */
348:     public function setPackage($name, array $extends = null, array $css = null, array $js = null)
349:     {
350:         $this->packages[$name] = new \RM\AssetsCollector\Package($name, $extends, $css, $js);
351:     }
352: 
353:     /**
354:      * Set packages to service from array.
355:      * @param   packages array
356:      */
357:     public function setPackages(array $packages)
358:     {
359:         foreach ($packages as $name => $details) {
360:             (isset($details['extends']))?$extends=$details['extends']:$extends=null;
361:             (isset($details['css']))?$css=$details['css']:$css=null;
362:             (isset($details['js']))?$js=$details['js']:$js=null;
363:             if ($css===null AND $js===null and $extends===null)
364:                 throw new InvalidArgumentException("Package $name is empty.");
365:             if ($css===null AND $js===null and count($extends)<=1)
366:                 throw new InvalidArgumentException("Package $name duplicated package $extends[0].");
367:             $this->setPackage ($name, $extends, $css, $js);
368:         }
369:     }
370: 
371:     /**
372:      * Add package to service.
373:      * @param   package string with name of package
374:      */
375:     public function addPackage($package)
376:     {
377:         $this->usedPackages[] = $package;
378:         $dependecies = $this->getDependecies($package);
379:         if(!empty($dependecies['css']))
380:             foreach ($dependecies['css'] as $css)
381:                 $this->addCss($css);
382:         if(!empty($dependecies['js']))
383:             foreach ($dependecies['js'] as $js)
384:                 $this->addJs($js);
385:         $package = $this->packages[$package];
386:     }
387: 
388:     /**
389:      * Get all dependencies.
390:      * @param   string name of package
391:      * @return  array of all CSS and JS dependecies for package
392:      */
393:     public function getDependecies($package)
394:     {
395:         $dependencies = array("css"=>array(),"js"=>array());
396:         $package = $this->packages[$package];
397:         if(!empty($package->extends))
398:             foreach ($package->extends as $extend) {
399:                 $ex = $this->getDependecies($extend);
400:                     $dependencies['css'] = array_merge($dependencies['css'],$ex['css']);
401:                     $dependencies['js'] = array_merge($dependencies['js'],$ex['js']);
402:             }
403:         if(!empty($package->css))
404:             foreach ($package->css as $css)
405:                 $dependencies['css'][] = $css;
406:         if(!empty($package->js))
407:             foreach ($package->js as $js)
408:                 $dependencies['js'][] = $js;
409:         $dependencies['css'] = array_unique($dependencies['css']);
410:         $dependencies['js'] = array_unique($dependencies['js']);
411:         return $dependencies;
412:     }
413: 
414:     /**
415:      * Add packages to service.
416:      * @param   packages string|array
417:      * @return  AssetsCollector
418:      */
419:     public function addPackages($packages)
420:     {
421:         if (is_string($packages)) {
422:             $this->addPackage($packages);
423:         } elseif (is_array($packages)) {
424:             foreach ($packages as $package) {
425:                 $this->addPackage($package);
426:             }
427:         }
428:         return $this;
429:     }
430: 
431:     /**
432:      * Merge files to one file.
433:      * @param   files array of files for merged
434:      * @param   type string type of files self::CSS or self::JS
435:      * @return  path of merged files
436:      */
437:     public function mergeFiles(array $files, $type)
438:     {
439:         $content = "";
440: 
441:         foreach ($files as $file)
442:             $content .= file_get_contents(WWW_DIR.$file);
443: 
444:         $fileNameOutput = md5($content).".".$type;
445: 
446:         if (!file_exists($this->webTemp."/".$fileNameOutput))
447:             file_put_contents($this->webTemp."/".$fileNameOutput,$content);
448: 
449:         return substr($this->webTemp,strlen(WWW_DIR))."/".$fileNameOutput;
450:     }
451: }
452: 
API documentation generated by ApiGen 2.8.0