summaryrefslogblamecommitdiffstats
path: root/inc/render.inc.php
blob: a636382ea9b06eb8a1a27c3b5b2ac047fc558e89 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13

     

                        








                                        
 

            
 
           
                                
           
                                        

                                    

                                         
                                    



                                                
                                           
         
                                             
                                                                                 









                                                                  

         
                                                           



                                                                                                                           


                                              
                                             

                                                                 
                                                                 




                                                                          

                                                                                                                                     

                                             
                                                                                                                      


                                                      
                                         
                    
                                
                                  
                      
                                                                                                                                                     
                                               
                                                                             


                                                                                              


                                                     
                                                   

                                                                                                                         


                         
                                                                                       

                                                                    
                                                   
                                 


                             
                               

                      
                                                                                                      

                                                                              

                           
                             
                                                        
                                                               
                                                             
                                                           

                                               
                                                       

                                                                                                   


                         

                             

                        
                 
                               




                                         
                                                                                   
         

                                                       
                                              




                                                                        
                                                            




                                              
                                                                                                            
           
                                                            




                                              

                                                                                                        
                                                                                                        
         
                                                                        
         
 







                                                                                         
                                                                                                               



                                                          
                                                                 
                           
         

           

                                    
                                                       





                                                                                    
                                                                                                    
                                                            

                                                                                                           
                                           
           
                                                                                                                                
         
                                                                      

                                                                     
                                    
                                                              


                                                                                                                                                  
                                                           
                                                                                
                                                                                            

                                                   
                                                                                      
                                                                                                          
                                                                                                   
                                 
                                                                                                                        



                                                                               
                                               

                                                     
                                                         



                                                                                                    

                                                                            

                                                                                                           




                                                                                                                          
                                       
                                                               


           



                                                                                      
                                     











                                                                                                
                                                                            


                                             
                                       
                                                                                                             
                                               
                                   
                                                                                                                      




                                                                
                               
           
                                                                             
         


                                                         
                 
                                 
                                                                                                       
                                                   

                             
 


                                    
                                                                
         
                                           

         

                                                                 
















                                                                                                                       
 
<?php

declare(strict_types=1);

require_once('inc/util.inc.php');

require_once('Mustache/Autoloader.php');
Mustache_Autoloader::register();

/**
 * HTML rendering helper class
 */
Render::init();

class Render
{

	/**
	 * @var ?Mustache_Engine
	 */
	private static $mustache = null;
	private static $body = '';
	private static $header = '';
	/** @var ?array */
	private static $dashboard = null;
	private static $footer = '';
	private static $title = '';
	private static $templateCache = array();
	private static $tags = array();

	public static function init(): void
	{
		if (self::$mustache !== null)
			ErrorHandler::traceError('Called Render::init() twice!');
		$options = array();
		$tmp = '/tmp/bwlp-cache';
		$dir = is_dir($tmp);
		if (!$dir) {
			@mkdir($tmp, 0755, false);
		}
		if (($dir || is_dir($tmp)) && is_writable($tmp)) {
			$options['cache'] = $tmp;
		}
		self::$mustache = new Mustache_Engine($options);
	}

	private static function cssEsc(string $str): string
	{
		return str_replace(array('"', '&', '<', '>'), array('\\000022', '\\000026', '\\00003c', '\\00003e'), $str);
	}

	/**
	 * Output the buffered, generated page
	 */
	public static function output(): void
	{
		Header('Content-Type: text/html; charset=utf-8');
		$modules = array_reverse(Module::getActivated());
		$title = Property::get('page-title-prefix', '');
		$bgcolor = Property::get('logo-background', '');
		if (!empty($bgcolor) || !empty($title)) {
			self::$header .= '<style type="text/css">' . "\n";
			if (!empty($bgcolor)) {
				$fgcolor = self::readableColor($bgcolor);
				self::$header .= ".navbar-header{background-color:$bgcolor}a.navbar-brand{color:$fgcolor!important}";
			}
			if (!empty($title)) {
				self::$header .= '#navbar-sub:after{content:"' . self::cssEsc($title) . '";margin:0}';
			}
			self::$header .= "\n</style>";
		}
		ob_start('ob_gzhandler');
		echo
		'<!DOCTYPE html>
	<html lang="' . LANG . '">
		<head>
			<title>', $title, self::$title, (defined('CONFIG_PRODUCT_NAME_LONG') ? CONFIG_PRODUCT_NAME_LONG : 'OpenSLX Admin'), '</title>
			<meta charset="utf-8"> 
			<meta http-equiv="X-UA-Compatible" content="IE=edge">
			<meta name="viewport" content="width=device-width, initial-scale=1.0">
			<!-- Bootstrap -->
			<link href="style/bootstrap.min.css" rel="stylesheet" media="screen">
	';
		// Include any module specific styles
		foreach ($modules as $module) {
			$files = $module->getCss();
			foreach ($files as $file) {
				echo '<link href="', $module->getDir(), '/', $file, '" rel="stylesheet" media="screen">';
			}
		}
		echo '	
			<link href="style/default.css" rel="stylesheet" media="screen">
			<script type="text/javascript">
			var TOKEN = "' . Session::get('token') . '";
			var LANG = "'  . LANG . '";
			</script>
	',
		self::$header
		,
		'	</head>
		<body>
	',
		(self::$dashboard !== null ? self::parse('main-menu', self::$dashboard, 'main') : ''),
		'<div class="main" id="mainpage"><div class="container-fluid">
	',
		self::$body
		,
		'</div></div>
		<script src="script/jquery.js"></script>
		<script src="script/bootstrap.min.js"></script>
		<script src="script/taskmanager.js"></script>
		<script src="script/slx-fixes.js"></script>
	';
		foreach ($modules as $module) {
			$files = $module->getScripts();
			foreach ($files as $file) {
				echo '<script src="', $module->getDir(), '/', $file, '"></script>';
			}
		}
		echo
		self::$footer
		,
		'</body>
		</html>'
		;
		ob_end_flush();
	}

	/**
	 * Set the page title (title-tag)
	 */
	public static function setTitle(string $title, bool $override = true): void
	{
		if (!$override && !empty(self::$title))
			return;
		self::$title = $title . ' - ';
	}

	/**
	 * Add raw html data to the header-section of the generated page
	 */
	public static function addHeader(string $html): void
	{
		self::$header .= $html . "\n";
	}

	/**
	 * Add raw html data to the footer-section of the generated page (right before the closing body tag)
	 */
	public static function addFooter(string $html): void
	{
		self::$footer .= $html . "\n";
	}

	/**
	 * Add the given template to the output, using the given params for placeholders in the template
	 */
	public static function addTemplate(string $template, array $params = [], ?string $module = null)
	{
		self::$body .= self::parse($template, $params, $module);
	}

	/**
	 * Add a dialog to the page output.
	 * 
	 * @param string $title Title of the dialog window
	 * @param boolean $next URL to next dialog step, or false to hide the next button
	 * @param string $template template used to fill the dialog body
	 * @param array $params parameters for rendering the body template
	 */
	public static function addDialog(string $title, bool $next, string $template, array $params = []): void
	{
		self::addTemplate('dialog-generic', array(
			'title' => $title,
			'next' => $next,
			'body' => self::parse($template, $params)
		), 'main');
	}

	/**
	 * Add error message to page
	 */
	public static function addError($message): void
	{
		self::addTemplate('messagebox-error', array('message' => $message));
	}

	/**
	 * Parse template with given params and return; do not add to body
	 * @param string $template name of template, relative to templates/, without .html extension
	 * @param array $params tags to render into template
	 * @param ?string $module name of module to load template from; defaults to currently active module
	 * @param ?string $lang override language if not null
	 * @return string Rendered template
	 */
	public static function parse(string $template, array $params = [], ?string $module = null, ?string $lang = null): string
	{
		if ($module === null && class_exists('Page', false)) {
			$module = Page::getModule()->getIdentifier();
		}
		// Load html snippet
		$html = self::getTemplate($template, $module);
		if ($html === false) {
			return '<h3>Template ' . htmlspecialchars($template) . '</h3>' . nl2br(htmlspecialchars(print_r($params, true))) . '<hr>';
		}
		// Now find all language tags in this array
		if (preg_match_all('/{{\s*(lang_.+?)\s*}}/', $html, $out) > 0) {
			$dictionary = Dictionary::getArray($module, 'template-tags', $lang);
			$fallback = false;
			foreach ($out[1] as $tag) {
				if ($fallback === false && empty($dictionary[$tag])) {
					$fallback = true; // Fallback to general dictionary of main module
					$dictionary += Dictionary::getArray('main', 'global-tags');
				}
				// Add untranslated strings to the dictionary, so their tag is seen in the rendered page
				if (empty($dictionary[$tag])) {
					$dictionary[$tag] = '{{' . $tag . '}}';
				}
			}
			$params += $dictionary;
		}
		// Always add token to parameter list
		$params['token'] = Session::get('token');
		if (defined('LANG')) {
			// Likewise, add currently selected language (its two letter code) to params
			$params['current_lang'] = LANG;
		}
		// Add desired password field type
		$params['password_type'] = Property::getPasswordFieldType();
		// Branding
		$params['product_name'] = defined('CONFIG_PRODUCT_NAME') ? CONFIG_PRODUCT_NAME : 'OpenSLX';
		// Query string
		if (strpos($_SERVER['QUERY_STRING'], 'message[]=') !== false) {
			$_SERVER['QUERY_STRING'] = preg_replace('/message\[\]=[^&]+(&|$)/', '', $_SERVER['QUERY_STRING']);
		}
		$params['qstr_urlencode'] = rawurlencode('?' . $_SERVER['QUERY_STRING']);
		// Return rendered html
		return self::$mustache->render($html, $params);
	}

	/**
	 * Open the given html tag, optionally adding the passed assoc array of params
	 */
	public static function openTag($tag, $params = false)
	{
		self::$tags[] = $tag;
		if (!is_array($params)) {
			self::$body .= '<' . $tag . '>';
		} else {
			self::$body .= '<' . $tag;
			foreach ($params as $key => $val) {
				self::$body .= ' ' . $key . '="' . htmlspecialchars($val) . '"';
			}
			self::$body .= '>';
		}
	}

	/**
	 * Close the given tag. Will check if it matches the tag last opened
	 */
	public static function closeTag($tag)
	{
		if (empty(self::$tags))
			ErrorHandler::traceError('Tried to close tag ' . $tag . ' when no open tags exist.');
		$last = array_pop(self::$tags);
		if ($last !== $tag)
			ErrorHandler::traceError('Tried to close tag ' . $tag . ' when last opened tag was ' . $last);
		self::$body .= '</' . $tag . '>';
	}

	/**
	 * Private helper: Load the given template and return it
	 * @return false|string
	 */
	private static function getTemplate(string $template, string $module)
	{
		$id = "$template/$module";
		if (isset(self::$templateCache[$id])) {
			return self::$templateCache[$id];
		}
		// Load from disk
		$data = @file_get_contents('modules/' . $module . '/templates/' . $template . '.html');
		self::$templateCache[$id] =& $data;
		return $data;
	}

	/**
	 * Create the dashboard menu
	 */
	public static function setDashboard(array $params): void
	{
		self::$dashboard = $params;
	}

	public static function readableColor(string $hex): string
	{
		if (strlen($hex) <= 4) {
			$cnt = 1;
		} else {
			$cnt = 2;
		}
		if (preg_match('/^#?([a-f0-9]{'.$cnt.'})([a-f0-9]{'.$cnt.'})([a-f0-9]{'.$cnt.'})$/i', $hex, $out) != 1)
			return '#000';
		$chans = array();
		$f = ($cnt === 1 ? 17 : 1);
		for ($i = 1; $i <= 3; ++$i) {
			$out[$i] = (hexdec($out[$i]) * $f);
			$chans[] = $out[$i] ^ 0x80;
		}
		$b = (255 - (0.299 * $out[1] + 0.587 * $out[2] + 0.114 * $out[3])) * 2;
		return sprintf("#%02x%02x%02x", ($chans[0] + $b) / 3, ($chans[1] + $b) / 3, ($chans[2] + $b) / 3);
	}

}