<?php
namespace Lia\Addon;
/**
* A component for managing multiple css & js files for a single request.
*
*
* @todo Use config for default site title
* @todo move SEO code into it's own component
* @todo Auto-add charset=utf8 & viewport meta tags (with to disable)
* @todo Add url prefix for compiled files as a config option
* @todo allow specifying sort priority when ADDING a file
* @todo better routing
* @todo Separate large files from the block of compiled code.
* @todo minify js & css
* @todo Create resources object that handles concatenation, so this class is merely an integration of said resource object
* @todo rename Resources to Files? & rename some methods?
*/
class Resources extends \Lia\Addon {
public string $fqn = 'lia:server.resources';
// files, urls, codes, and sorter all have 'css' and 'js' indices expected.
public $files = [];
public $urls = [];
// public $codes = [];
public $sorter = [];
/** an instance of Lia\Addon\Cache
*/
public $cache;
/**
* to minify css & js or not
* @note not currently implemented
* @note $_GET['true'] or $_GET['false'] will change this setting
*/
public $minify = true;
/**
* Whether to enable cache or not.
*
* @tip disable this during development
*/
public $useCache = true;
public function __construct(?\Lia\Package $package=null){
parent::__construct($package);
if (isset($_GET['minify'])){
if ($_GET['minify']=='true')$this->minify = true;
else if ($_GET['minify']=='false')$this->minify = false;
}
}
public function init_lia(){
$lia = $this->lia;
$lia->methods['setResourceSorter'] = [$this,'setSorter'];
$lia->methods['addResourceFile'] = [$this,'addFile'];
$lia->methods['addResourceUrl'] = [$this,'addUrl'];
$lia->methods['getResourceHtml'] = [$this,'getHtml'];
//deprecated alias
$lia->methods['getHeadHtml'] = [$this,'getHtml'];
}
public function onPackageReady(){
$this->cache = new \Lia\Addon\Cache();
$this->cache->lia = $this->lia;
$this->cache->dir = $this->package->dir('cache-resources');
\Lia\FastFileRouter::file($this->cache->dir, $_SERVER['REQUEST_URI'], '.file');
}
////////
// Small simple methods
////////
protected function cleanExt(&$ext){
$ext = strtolower($ext);
if ($ext!='css'&&$ext!='js')throw new \Lia\Exception("Only js and css are allowed at this time, but '{$ext}' was given.");
}
protected function cacheMapName($ext){
return "lia.resource.{$ext}FileMap";
}
/////
// for users
/////
public function setSorter($ext, $sorter){
$this->cleanExt($ext);
$this->sorter[$ext] = $sorter;
}
/**
* Add a css or js file to be added to the final output
*
* @param string $file filename
*/
public function addFile(string $file){
if (!is_file($file)){
throw new \Exception("Cannot add file to Resources. Path '{$file}' is not a file");
}
$ext = pathinfo($file,PATHINFO_EXTENSION);
$this->cleanExt($ext);
$file = realpath($file);
$this->files[$ext][$file] = $file;
}
public function addUrl($url){
$path = parse_url($url,PHP_URL_PATH);
$ext = pathinfo($path,PATHINFO_EXTENSION);
$this->cleanExt($ext);
$this->urls[$ext][] = $url;
}
public function getHtml(){
$seo_html = isset($this->lia->methods['getSeoHtml']) ?
' '.str_replace("\n","\n ",$this->lia->getSeoHtml())."\n"
: '';
$html = "\n"
.' '.$this->getFileTag('js')."\n"
.' '.$this->getFileTag('css')."\n"
.' '.$this->getUrlTag('js')."\n"
.' '.$this->getUrlTag('css')."\n"
// .' '.$this->getScriptCodeTag()."\n"
// .' '.$this->getStyleCodeTag()."\n"
.$seo_html
;
$html = "\n ".trim($html)."\n";
return $html;
}
//////
// get html
//////
public function getFileTag($ext){
$this->cleanExt($ext);
$url = $this->getCompiledFilesUrl($ext);
if ($url==false)return '';
switch ($ext){
case 'css':
$html = '<link rel="stylesheet" href="'.$url.'" />';
break;
case 'js':
$html = '<script type="text/javascript" src="'.$url.'"></script>';
break;
default:
$html = '';
}
return $html;
}
public function getUrlTag($ext){
$this->cleanExt($ext);
$html = [];
foreach (($this->urls[$ext]??[]) as $url){
switch ($ext){
case 'css':
$html[] = '<link rel="stylesheet" href="'.$url.'" />';
break;
case 'js':
$html[] = '<script type="text/javascript" src="'.$url.'"></script>';
break;
default:
// $html[] = '';
}
}
$output = "\n ".implode("\n ",$html)."\n";
return $output;
}
//////
// terrible, awful methods that are essential for putting files together
// these definitely need refactoring
//////
public function getSortedFiles($ext){
// @TODO cache the sorted scripts list
$this->cleanExt($ext);
$files = $this->files[$ext] ?? [];
$sorter = $this->sorter[$ext] ?? null;
if ($sorter!==null){
$files = $sorter($files);
}
return $files;
}
public function concatenateFiles($ext){
$this->cleanExt($ext);
$files = $this->getSortedFiles($ext);
$separator = "\n";
$content = [];
foreach ($files as $file){
$content[] = file_get_contents($file);
}
return implode($separator, $content);
}
public function concatenateFileNames($ext){
$this->cleanExt($ext);
$files = $this->getSortedFiles($ext);
$longName = implode('--',$files);
return $longName;
}
public function compileFilesToCache($ext){
$this->cleanExt($ext);
$longName = $this->concatenateFileNames($ext);
if ($longName=='')return false;
$cachedMapName = $this->cacheMapName($ext);
$files = $this->cache->get_cache_file_content($cachedMapName);
$files = $files==false ? [] : json_decode($files, true);
$min = '';
if ($this->minify)$min = 'min.';
$shortName = $files[$longName] ?? 'lia-resource.'.uniqid().'.'.$min.$ext;
$cachedFile = $this->cache->get_cache_file_path($shortName);
if ($cachedFile&&$this->useCache)return $cachedFile;
$content = $this->concatenateFiles($ext);
if ($this->minify){
$content = $this->minifyFiles($content,$ext);
}
$path = $this->cache->cache_file($shortName, $content, 60*60*24*30);
$files[$longName] = $shortName;
$this->cache->cache_file($cachedMapName, json_encode($files), 60*60*24*30);
return $path;
}
public function getCompiledFilesUrl($ext){
$this->cleanExt($ext);
$file = $this->compileFilesToCache($ext);
if ($file==false)return false;
$longName = $this->concatenateFileNames($ext);
$files = $this->cache->get_cache_file_content($this->cacheMapName($ext));
$files = $files==false ? [] : json_decode($files, true);
$shortName = $files[$longName] ?? null;
return '/'.$shortName;
}
/**
* @warning There is no minification right now. This function is a placeholder
*/
public function minifyFiles(string $fileContent, string $ext){
// css + js minifier: https://github.com/matthiasmullie/minify
// css + js minifier: https://github.com/ceesvanegmond/minify
// css + js minifier (codeigniter): https://github.com/slav123/CodeIgniter-minify
if ($ext=='js')return $fileContent;
else if ($ext=='css'){
return $fileContent;
//css minification options:
// https://github.com/Cerdic/CSSTidy
//
// if (class_exists('csstidy',true)){
// $csstidy = new \csstidy();
//
// // Set some options :
// // $csstidy->set_cfg('optimise_shorthands', 2);
// $csstidy->set_cfg('template', 'high');
//
// // Parse the CSS
// $csstidy->parse($fileContent);
//
// // Get back the optimized CSS Code
// $css_code_opt = $csstidy->print->plain();
// return $css_code_opt;
// }
}
}
}