<?php
/**
* 视图类
* 
* 采用原生PHP做为模板，支持短标签，不管php.ini中有没有开启
* 
* 对PHP危险函数进行检测并警告中断, 禁用的函数有， 文件与目录操作函数，session函数, stream流函数，w32api函数, curl函数，所有的数据库连接函数, php.ini操作函数, eval 函数，exec等执行操作系统命令的函数
* 
* 屏蔽了 \mvc::$cfg 中的一些危险配置，如 db,safe等, 在模板中禁止访问它们
* 
* 禁止直接使用 $_SESSION变量，改为 $session 只读访问
* 
* 原生PHP模板中建议使用 php自带的模板变量
*      - #if(): &nbsp; elseif: &nbsp; else: &nbsp; endif;
*      - #switch(): &nbsp; case xxx: &nbsp; default: &nbsp; endswitch;
*      - #for(): &nbsp; endfor;
*      - #foreach(): &nbsp; endforeach;
*      - #while(): &nbsp; endwhile;
* 
* 短标签
*      - #<?print_r($var)?>
*        内容可以紧靠<?或?>标签，有无空格无影响,无需要加php标识
*      - #<?=time()?> 或 <?=$name?>
*        单内容直接输出
* 
* 视图全局函数
*       - #viewSet($key,$value)
*         注入附加自定义设置的变量,一般来自视图模板本身
*         例: viewSet("headerTitle","父页面")
*             放置所有css,js的后面
*             viewSet("headerEnd",'<link href="2" rel="stylesheet"><script src="2"></script>')
*             放置所有css,js的前面
*             viewSet("headerBegin",'<link href="1" rel="stylesheet"><script src="1"></script>')
*         使用时: <?=$headerTitle?>
*       - #viewLoad($view) 
*         加载子视图
*         例: viewLoad('public/header') 将会加载  view目录/public/header.view.php
*       - #viewPlugins(...$plugins) 
*         加载UI 插件
*         例: viewPlugins('jqui','multiselect')
* @version 1.0.0
*/

class view{
	/**
	* 设置的要传递给模板的变量
	* @ignore
	*/
	protected static $viewVariables = []; 
	/**
	* PHP 危险函数列表
	* @ignore
	*/
	protected static $dangerFunctions= ['unlink','chgrp','chmod','chown','clearstatcache','copy','delete','dirname','disk_free_space','disk_total_space','diskfreespace','fclose','feof','fflush','fgetc','fgetcsv','fgets','fgetss','file_get_contents','file_put_contents','file','filegroup','fileinode','filemtime','fileowner','fileperms','filesize','filetype','flock','fnmatch','fopen','fpassthru','fputcsv','fputs','fread','fscanf','fseek','fstat','ftell','ftruncate','fwrite','chdir','chroot','dir','closedir','getcwd','opendir','readdir','rewinddir','scandir','glob','is_dir','is_writable','is_writeable','link','linkinfo','lstat','mkdir','move_uploaded_file','parse_ini_file','pathinfo','pclose','popen','readfile','readlink','realpath','rename','rewind','rmdir','set_file_buffer','stat','symlink','tempnam','tmpfile','touch','umask',

	'session_cache_expire','session_cache_limiter','session_commit','session_decode','session_destroy','session_encode','session_get_cookie_params','session_id','session_is_registered','session_module_name','session_name','session_regenerate_id','session_register','session_save_path','session_set_cookie_params','session_set_save_handler','session_start','session_unregister','session_unset','session_write_close',
	
	'stream_bucket_append','stream_bucket_make_writeable','stream_bucket_new','stream_bucket_prepend','stream_context_create','stream_context_get_default','stream_context_get_options','stream_context_set_option','stream_context_set_params','stream_copy_to_stream','stream_filter_append','stream_filter_prepend','stream_filter_register','stream_filter_remove','stream_get_contents','stream_get_filters','stream_get_line','stream_get_meta_data','stream_get_transports','stream_get_wrappers','stream_register_wrapper','stream_select','stream_set_blocking','stream_set_timeout','stream_set_write_buffer','stream_socket_accept','stream_socket_client','stream_socket_enable_crypto','stream_socket_get_name','stream_socket_pair','stream_socket_recvfrom','stream_socket_sendto','stream_socket_server','stream_wrapper_register','stream_wrapper_restore','stream_wrapper_unregister',
	
	'w32api_deftype','w32api_init_dtype','w32api_invoke_function','w32api_register_function','w32api_set_call_method','checkdnsrr','closelog','debugger_off','debugger_on','define_syslog_variables','dns_check_record','dns_get_mx','dns_get_','record','fsockopen','gethostbyaddr','gethostbyname','gethostbynamel','getmxrr','getprotobyname','getprotobynumber','getservbyname','getservbyp','ort','inet_ntop','inet_pton','ip2long','long2ip','openlog','pfsockopen','socket_get_status','socket_set_blocking','socket_set_timeout','syslog',

	'PDO','mysqli','mysqli_connect','mysql_connect','SQLiteDatabase','pg_connect','oci_connect',

	'curl_init','curl_close','curl_exec','curl_file_create','curl_copy_handle','CURLFile',

	'eval','exec','passthru','proc_close','proc_get_status','proc_nice','proc_open','proc_terminate','shell_exec','system','escapeshellarg','escapeshellcmd','ini_set','ini_restore','php_ini_scanned_files','putenv','restore_include_path','set_include_path','set_magic_quotes_runtime','set_time_limit'
	];

	/** 
	 * 给模板赋值
	 * 
	 * ```\lib\view\set('data',$data)```<br>
	 * ```多维 \lib\view\set([name1=>$value1,name2=>value2])```<br>
	 * ```极简模式 \lib\view\set(get_defined_vars()) 将当前函数下所有变量转递给模板```<br>
	 * 
	 * 关于 $_SESSION 为安全计，前端模板取值，使用 $session['变量'];只读，注:没有下划线<br>
	 * @param string $key  键   
	 * @param mixed $value  值
	 * @return  void
	 */
	public static function set($key, $value=null) 
	{
		if(is_array($key) && $value==null){
			foreach($key as $k=>$v){
				self::$viewVariables[$k] = $v;
			}
		}else{
			self::$viewVariables[$key] = $value;
		}
	}
	
	/**
	 * view视图输出
	 *
	 *模板文件路径 [站点]/view/类名/方法.view.php
	 *
	 *```<?=$path_xxxxx?>```<br>
	 *   例: 可以在模板中使用 ```<?=$path_files?>```访问 ```\mvc::$cfg['path']['files']``` 内容, 
	 *
	 * @param  $viewFile  模板名称,默认取当前类当前方法的模板 
	 * @return  void
	 * @example ```put(); //默认输出 [站点]/当前action类名(含命名空间)/方法名.view.php模板```
	 * @example ```put('user.register'); //输出 [站点]/view/user/register.view.php```
	 * @example ```put('car.model.list'); //输出 [站点]/view/car/model/list.view.php```
	 * @todo 如果开启php.ini短标签将获取最佳性能(快一丝丝,基本可以忽略)，一般用于已上线且变化不再频繁的项目的极端优化
	 */
	public static function put($viewFile='')
	{
		/* 当参数为空时，取默认的 action/method.php 模板路径 */
		if($viewFile==''){
			$viewFile = \mvc::$cfg['viewDefault'];
			$backtrace = debug_backtrace();
			$call = $backtrace[2];
            if($call['function'] == 'execute') $call = $backtrace[1];
			$viewFile = \mvc::getViewByAction($call['class'],$call['function']);
			unset($backtrace);
		}else{
			$viewFile = str_replace('.','/',$viewFile);
		}
		$viewFile .= '.view.php';
		$viewFile = \mvc::$cfg['dir']['view'] .'/'. $viewFile;
		//记录当前模板到mvc
		\mvc::$activeViewFile = $viewFile;

		//注入自定义设置的变量
		foreach(self::$viewVariables as $key=>$value)
		{
			$$key = $value;
		}
		//注入$req变量
		foreach(\mvc::$req as $key=>$value){
			$$key = $value;
		}
		!defined('SITE_GUID') && define('SITE_GUID',\mvc::$cfg['project'].'_'.\mvc::$cfg['site']);
		//注入配置中的dir内容为常量
		foreach(\mvc::$cfg['dir'] as $key=>$value){
			$key = strtoupper('DIR_'.$key);
			!defined($key) && define($key,$value);
		}
		//注入配置中的path内容为常量
		foreach(\mvc::$cfg['path'] as $key=>$value){
			$key = strtoupper('PATH_'.$key);
			!defined($key) && define($key,$value);
		}
		//注入SESSION里的内容到变量
		$key = 'session';
		$$key = $_SESSION;

		//对模板屏蔽危险配置
		$___cfg = \mvc::$cfg;
		\mvc::$cfg['db'] = null;
		\mvc::$cfg['safe'] = null;
		unset(\mvc::$cfg['db']);
		unset(\mvc::$cfg['safe']);

		if(!DEBUG){
			ob_clean();
		}
		//执行视图
		self::load($viewFile,get_defined_vars());
		//恢复全量配置
		\mvc::$cfg = $___cfg;
	}

	/**
	  * 得到处理模板后的结果(HTML)
     *
	  * @param  $tplFile  模板名称
	  * @param  $tplPath  模板目录[默认mvc::$cfg['PATH_TPL']]
	  * @author soul
	  */
	public static function fetch($viewFile='')
	{
		ob_start();
		self::put($viewFile);
		$html = ob_get_contents(); 
		ob_end_clean();
		return $html;
	}


	/**
	 * 检测PHP中的危险函数
	 * @param  $contents  模板内容
	 * @return  void
	 * @ignore
	 */
	public static function checkDanger($contents){
		$contents = preg_replace('/[\t\r\n]+/',' ',$contents);
		$partern='/\<\?(.*?)\?\>/';
		preg_match_all($partern,$contents,$m);
		foreach($m[1] as $block){
			$blockNew = $block;
			foreach(self::$dangerFunctions as $func){
				$partern = '/\b'.$func.'[\s]*\(/i';
				$blockNew = preg_replace($partern,lg('禁用').$func.'(',$blockNew);
			}
			$blockNew = str_ireplace('$_SESSION',lg('禁用').'$_SESSION', $blockNew);
			$blockNew = preg_replace('/new\s+([a-z0-9_\$\\\\]+)\(/i',lg('禁用').'new $1(',$blockNew);
			if($blockNew!=$block){
				throw new \Exception($blockNew);
			}
		}
	}
	/**
	 * 执行视图
	 * @param string $viewFile 视图模板文件
	 * @return  void
	 * @ignore
	 */
	public static function load($viewFile,$setVars=null){
		if($setVars!=null){
			//注入附加自定义设置的变量
			foreach($setVars as $key=>$value){
				$$key = $value;
			}
		}
		if(DEBUG || \mvc::$cfg['short_open_tag']==false){
			//使视图模板支持PHP短标签
			$contents = file_get_contents($viewFile);
			if(trim($contents)==''){
				throw new \Exception(lg("无效的视图文件 ").$viewFile);
			}
			if(DEBUG){
				//检测PHP模板中的危险函数,及是否有 $_SESSION变量,及 new 对象
				//如果存在，将直接抛出异常
				self::checkDanger($contents);
			}
			$contents = str_replace('<?','<?php ',$contents);
			$contents = str_replace('<?php =','<?php echo ',$contents);
			$php_errormsg=null;
			@eval( '?>' . $contents );
			if(DEBUG && $php_errormsg!=null){
                $type = substr($php_errormsg,0,strpos($php_errormsg,':'));
                if($type!='' && !in_array($type,['Undefined variable','Undefined index','Uncaught Exception'])){
                    dump($type);
					$backtrace = debug_backtrace();
					throw new \Exception($php_errormsg);
				}
			}
		}else{
			require($viewFile);
		}
	}
}


/** 
* 视图全局函数库
*/

$setVars = [];
/** 
* 加载子视图
* 
* @param string $view 例: public/header 将会加载  view目录/public/header.view.php
* @return void
*/
function viewLoad($view){
	global $setVars;
	foreach ($setVars as $k=>$v){
        view::set($k,$v);
	}
    view::put($view);
    return false;
    /*
	global $setVars;
	$viewFile = \mvc::$cfg['dir']['view'].'/'.ltrim($view,'/').'.view.php';
	view::load($viewFile,$setVars);
	*/
}
/** 
* 注入附加自定义设置的变量,一般来自视图模板本身
* 
* @param string $key
* @param string $value
* @return void
*/
function viewSet($key,$value){
	global $setVars;
	$setVars[$key] = $value;
}

/** 
* 加载UI插件js,css
* 
* @param ... $plugins 插件名集
* @return void
*/
function viewPlugins(...$plugins){
	global $setVars;
	$loadFiles = [];
	$cfg = require_once(\mvc::$cfg['dir']['project'].'/config/uiPlugins.php');
	foreach($plugins as $plugin){
		if(!isset($cfg[$plugin])){
            print_r($cfg);
			throw new \Exception(lg("没有配置的插件: ".\mvc::$cfg['dir']['project'].'/config/uiPlugins.php:"'.$plugin.'"'));
		}
		viewPluginsScriptFile($loadFiles, $plugin,$cfg);
	}
	$script = '';
	foreach($loadFiles as $file){
		$script .= $file.PHP_EOL;
	}
	if($script!=''){
		$setVars['headerPlugins'] = $script;
	}
}
/** 
* 加载UI插件js,css
* 
* @param string $plugins 插件名
* @param arrat $cfg 插件配置
* @return void
* @ignore
*/
function viewPluginsScriptFile(&$loadFiles, $plugin, $cfg){
	if(isset($cfg[$plugin]['must'])){
		foreach($cfg[$plugin]['must'] as $item){
			$arr = viewPluginsScriptFile($loadFiles, $item, $cfg);
			foreach($arr as $k=>$v){
				$loadFiles[$k]=$v;
			}
		}
	}
	if(isset($cfg[$plugin]['css'])){
		foreach($cfg[$plugin]['css'] as $item){
			$loadFiles[$item] = '<link href="'.$item.'" rel="stylesheet" />';
		}
	}
	if(isset($cfg[$plugin]['js'])){
		foreach($cfg[$plugin]['js'] as $item){
			$loadFiles[$item] = '<script src="'.$item.'"></script>';
		}
	}
}