2024-02-03 14:08:03 +01:00
< ? php
class Template {
protected $template = null ;
protected $data = array ();
public function __construct ( string $template , ? array $parentData = null ) {
$this -> template = $template ;
if ( isset ( $parentData )) {
$this -> data = $parentData ;
}
}
public function set ( string $key , $value ) {
$this -> data [ $key ] = $value ;
}
public function render ( $options = null ) : string {
Logger :: log ( " Rendering template " . $this -> template , Logger :: IOREAD , " Template " );
$class = is_object ( $options ) && isset ( $options -> class ) ? new $options -> class ( $this , $options ) : new EmptyTemplate ( $this , $options );
$class -> load ();
2024-03-22 18:55:39 +01:00
$contents = Cache :: getTemplateFromCache ( $this -> template );
if ( is_null ( $contents )) {
$contents = file_get_contents ( TEMPLATE_DIR . " / " . $this -> template );
Cache :: setTemplateToCache ( $this -> template , $contents );
}
2024-02-03 14:08:03 +01:00
$class -> pretransform ();
// Replace includes of templates
$matches = array ();
2024-03-23 11:52:17 +01:00
preg_match_all ( " / { { (include|includeCached) (.* \ .html)(.*)}}/ " , $contents , $matches );
2024-02-03 14:08:03 +01:00
for ( $i = 0 ; $i < count ( $matches [ 1 ]); $i ++ ) {
$matchString = $matches [ 0 ][ $i ];
2024-03-23 11:52:17 +01:00
$method = $matches [ 1 ][ $i ];
$match = $matches [ 2 ][ $i ];
$options = trim ( $matches [ 3 ][ $i ]);
if ( $method === " includeCached " ) {
$crc = hash ( " crc32b " , $match . $options );
$include = Cache :: getIncludeCache ( $crc );
if ( ! is_null ( $include )) {
$contents = str_replace ( $matchString , $include , $contents );
continue ;
}
}
2024-02-03 14:08:03 +01:00
$include = new Template ( dirname ( $this -> template ) . " / " . $match , $this -> data );
2024-03-23 11:52:17 +01:00
$rendered = $include -> render (
2024-02-03 14:08:03 +01:00
! empty ( $options ) ? json_decode ( $options , false , 512 , JSON_THROW_ON_ERROR ) : null
2024-03-23 11:52:17 +01:00
);
if ( $method === " includeCached " ) {
Cache :: setIncludeCache ( $crc , $rendered );
}
$contents = str_replace ( $matchString , $rendered , $contents );
2024-02-03 14:08:03 +01:00
}
$class -> transform ();
// Replace variables
foreach ( $this -> data as $key => $value ) {
if ( is_string ( $value ) || is_numeric ( $value )) {
$contents = str_replace ( " { { " . $key . " }} " , $value , $contents );
} else {
$contents = str_replace ( " { { " . $key . " }} " , " " , $contents );
}
}
// Conditional tags are written as {{?<variablename>}}some content{{/<variablename>}}, find conditional tags and evaluate them if they exist in $this->data
$matches = array ();
preg_match_all ( " / { { \ ?(.*)}}(.*) { { \ /(.*)}}/sU " , $contents , $matches );
for ( $i = 0 ; $i < count ( $matches [ 1 ]); $i ++ ) {
$matchString = $matches [ 0 ][ $i ];
$match = $matches [ 1 ][ $i ];
$content = $matches [ 2 ][ $i ];
if ( isset ( $this -> data [ $match ])) {
$contents = str_replace ( $matchString , $content , $contents );
} else {
$contents = str_replace ( $matchString , " " , $contents );
}
}
$class -> save ();
return $contents ;
}
public function save ( string $filename , string $contents , bool $minify = true ) {
Logger :: log ( " Saving template to " . $filename , Logger :: IOWRITE , " Template " );
if ( $minify ) {
$size = strlen ( $contents );
$doc = new DOMDocument ();
$doc -> preserveWhiteSpace = false ;
$doc -> formatOutput = false ;
$doc -> normalizeDocument ();
libxml_use_internal_errors ( true );
$doc -> loadHTML ( $contents );
libxml_use_internal_errors ( false );
$options = [
'keep_whitespace_around' => [
// keep whitespace around inline elements
'b' , 'big' , 'i' , 'small' , 'tt' ,
'abbr' , 'acronym' , 'cite' , 'code' , 'dfn' , 'em' , 'kbd' , 'strong' , 'samp' , 'var' ,
'a' , 'bdo' , 'br' , 'img' , 'map' , 'object' , 'q' , 'span' , 'sub' , 'sup' ,
'button' , 'input' , 'label' , 'select' , 'textarea'
],
'keep_whitespace_in' => [ /*'script', 'style',*/ 'pre' ],
'remove_empty_attributes' => [ 'style' , 'class' ],
'indent_characters' => " \t "
];
//Comments,empty,whitespace
$xpath = new \DOMXPath ( $doc );
foreach ( $xpath -> query ( '//comment()' ) as $comment ) {
$comment -> parentNode -> removeChild ( $comment );
}
$xpath = new \DOMXPath ( $doc );
foreach ( $options [ 'remove_empty_attributes' ] as $attr ) {
foreach ( $xpath -> query ( '//*[@' . $attr . ']' ) as $el ) {
if ( trim ( $el -> getAttribute ( $attr )) == '' ) {
$el -> removeAttribute ( $attr );
}
}
}
$x = new \DOMXPath ( $doc );
$nodeList = $x -> query ( " //text() " );
foreach ( $nodeList as $node ) {
/** @var \DOMNode $node */
if ( in_array ( $node -> parentNode -> nodeName , $options [ 'keep_whitespace_in' ])) {
continue ;
};
$node -> nodeValue = str_replace ([ " \r " , " \n " , " \t " ], ' ' , $node -> nodeValue );
while ( strpos ( $node -> nodeValue , ' ' ) !== false ) {
$node -> nodeValue = str_replace ( ' ' , ' ' , $node -> nodeValue );
}
if ( ! in_array ( $node -> parentNode -> nodeName , $options [ 'keep_whitespace_around' ])) {
if ( ! ( $node -> previousSibling && in_array (
$node -> previousSibling -> nodeName ,
$options [ 'keep_whitespace_around' ]
))) {
$node -> nodeValue = ltrim ( $node -> nodeValue );
}
if ( ! ( $node -> nextSibling && in_array (
$node -> nextSibling -> nodeName ,
$options [ 'keep_whitespace_around' ]
))) {
$node -> nodeValue = rtrim ( $node -> nodeValue );
}
}
if (( strlen ( $node -> nodeValue ) == 0 )) {
$node -> parentNode -> removeChild ( $node );
}
}
$contents = $doc -> saveHTML ();
Logger :: log ( " Minified template from " . $size . " to " . strlen ( $contents ) . " bytes " , Logger :: METRICS , " Template " );
}
file_put_contents ( PUBLIC_DIR . " / " . $filename , $contents );
}
public function saveCss ( string $filename , string $contents , bool $minify = true )
{
Logger :: log ( " Saving css to " . $filename , Logger :: IOWRITE , " Template " );
if ( $minify ) {
$size = strlen ( $contents );
$contents = preg_replace ( '!/\*[^*]*\*+([^/][^*]*\*+)*/!' , '' , $contents );
$contents = str_replace ( ': ' , ':' , $contents );
$contents = str_replace ( array ( " \r \n " , " \r " , " \n " , " \t " , ' ' , ' ' , ' ' ), '' , $contents );
Logger :: log ( " Minified " . $filename . " from " . $size . " to " . strlen ( $contents ) . " bytes " , Logger :: METRICS , " Template " );
}
file_put_contents ( PUBLIC_DIR . " / " . $filename , $contents );
}
public function saveScript ( string $filename , string $contents , bool $minify = true )
{
Logger :: log ( " Saving script to " . $filename , Logger :: IOWRITE , " Template " );
if ( $minify ) {
$size = strlen ( $contents );
$contents = preg_replace ( '/([-\+])\s+\+([^\s;]*)/' , '$1 (+$2)' , $contents );
$contents = preg_replace ( " / \ s* \n \ s*/ " , " \n " , $contents );
$contents = preg_replace ( " / \ h+/ " , " " , $contents );
$contents = preg_replace ( " / \ h([^A-Za-z0-9 \ _ \$ ])/ " , '$1' , $contents );
$contents = preg_replace ( " /([^A-Za-z0-9 \ _ \$ ]) \ h/ " , '$1' , $contents );
$contents = preg_replace ( " / \ s?([ \ ( \ [ { ]) \ s?/ " , '$1' , $contents );
$contents = preg_replace ( " / \ s([ \ ) \ ]}])/ " , '$1' , $contents );
$contents = preg_replace ( " / \ s?([ \ .=: \ -+,]) \ s?/ " , '$1' , $contents );
$contents = preg_replace ( " /; \n / " , " ; " , $contents );
$contents = preg_replace ( '/;}/' , '}' , $contents );
$contents = preg_replace ( '/\s+/' , ' ' , $contents );
$contents = preg_replace ( '/\s*([;{}])\s*/' , '$1' , $contents );
$contents = preg_replace ( '/\s*(;{})\s*/' , '$1' , $contents );
$contents = str_replace ( array ( " \r \n " , " \r " , " \n " , " \t " , ' ' , ' ' , ' ' ), '' , $contents );
Logger :: log ( " Minified " . $filename . " from " . $size . " to " . strlen ( $contents ) . " bytes " , Logger :: METRICS , " Template " );
}
file_put_contents ( PUBLIC_DIR . " / " . $filename , $contents );
}
2024-03-23 13:12:33 +01:00
public function saveText ( string $filename , string $contents )
{
Logger :: log ( " Saving text to " . $filename , Logger :: IOWRITE , " Template " );
file_put_contents ( PUBLIC_DIR . " / " . $filename , $contents );
}
2024-02-03 14:08:03 +01:00
}