<?php

/**
 *--------------------------------------
 * extension
 *--------------------------------------
 * @project		: loga
 * @author		: cblee
 * @created		: 2016-08-19
 * @copyright	: (c)2016 AsThis
 *--------------------------------------
 */
defined('PFA_PATH') or exit('Access Denied');

class ExtensionModl extends Modl {
	public function get_extensionMenu($eType = '') {
		$_EML = array();
		$where = array();
		if(!empty($eType)) {
			$where['e_type'] = array('EQ', $eType);
		}

		$_EL = $this->where($where)->field('e_manage_menu')->select();
		if(empty($_EL)) {
			return $_EML;
		}
		foreach($_EL as $e) {
			if(empty($e['e_manage_menu'])) {
				continue;
			}
			$pattern = '/\[(\S+)\|(\S+)\|(\S+)\]/isU';
			if(preg_match_all($pattern, $e['e_manage_menu'], $result)) {
				unset($result[0]);
				foreach($result[1] as $k => $v) {
					$_EML[] = array(
						'm_name' => L($v),
						'm_alias' => $result[2][$k],
						'm_url' => $result[3][$k],
						);
				}
			}
		}
		return $_EML;
	}

	public function get_extensionList($extensionType = '') {
		$_EL = array();
		$dh = dir(RUNTIME_PATH.D_S.'ext');
		while(false !== ($filename = $dh->read())) {
			$_t_ei = $this->get_extensionInfo($filename);
			if('' != $extensionType && $extensionType != $_t_ei['e_type']) {
				continue;
			}
			if('' != $_t_ei['e_hashcode']) {
				unset($_t_ei['e_instruction']);
				unset($_t_ei['e_install_sql']);
				unset($_t_ei['e_uninstall_sql']);
				unset($_t_ei['e_install_php']);
				unset($_t_ei['e_uninstall_php']);
				unset($_t_ei['file_list']);
				$_EL[$_t_ei['e_hashcode']] = $_t_ei;
			}
		}
		$dh->close();
		return $_EL;
	}

	public function get_extensionInfo($hashcode) {
		$_EI = '';
		$extDir = RUNTIME_PATH.D_S.'ext'.D_S.$hashcode;
		$filename = $extDir.D_S.'_extension.php';
		$installLockFilename = $extDir.D_S.'_lock.php';
		if(is_file($filename)) {
			$_EI = include_cache($filename);
			$_EI['e_instruction'] = base64_decode($_EI['e_instruction']);
			$_EI['e_install_sql'] = base64_decode($_EI['e_install_sql']);
			$_EI['e_uninstall_sql'] = base64_decode($_EI['e_uninstall_sql']);
			$_EI['e_install_php'] = base64_decode($_EI['e_install_php']);
			$_EI['e_uninstall_php'] = base64_decode($_EI['e_uninstall_php']);
			if(!empty($_EI['e_lang'])) {
				foreach($_EI['e_lang'] as $k => $lang) {
					$_EI['e_lang'][$k] = base64_decode($lang);
				}
			}
			$_EI['e_route'] = base64_decode($_EI['e_route']);
			$_EI['e_status'] = 0;
			if(is_file($installLockFilename)) {
				$_EI['e_status'] = 1;
				$_EI['e_install_datetime'] = filemtime($installLockFilename);
			}
		}
		return $_EI;
	}

	public function install_extension($_EI) {
		$result = array('data' => '', 'error' => '');

		$installLockFilename = RUNTIME_PATH.D_S.'ext'.D_S.$_EI['e_hashcode'].D_S.'_lock.php';
		$_t_ei = $this->where(array('e_hashcode' => array('EQ', $_EI['e_hashcode'])))->find();
		if(is_file($installLockFilename) or !empty($_t_ei)) {
			$result['error'] = L('EXTENSION_EXIST');
			return $result;
		}

		/* execute php operation */
		if(!empty($_EI['e_install_php'])) {
			@eval($_EI['e_install_php']);
		}

		$data = array(
			'e_hashcode' => $_EI['e_hashcode'],
			'e_name' => $_EI['e_name'],
			'e_alias' => $_EI['e_alias'],
			'e_type' => $_EI['e_type'],
			'e_manage_menu' => $_EI['e_manage_menu'],
			);
		$_t_id = $this->insert($data);
		if(false === $_t_id) {
			$result['error'] = L('INSTALL_FAILED');
			return $result;
		}
		$result['data'] = $_t_id;

		/* execute install SQL */
		vendor('Sql#class');
		$sql = str_replace(array(
			'{root_url}',
			'{-time-}',
			'{-ip-}'), array(
			__APP__,
			NOW_TIME,
			CLIENT_IP), $_EI['e_install_sql']);
		$sql = Sql::remove_comments(Sql::remove_remarks($sql));
		$query = trim_array(Sql::split_sql($sql));
		if(!empty($query)) {
			foreach($query as $q) {
				M()->execute($q, true);
			}
		}

		/* check language */
		if(!empty($_EI['e_lang'])) {
			$langset = get_langset();
			foreach($_EI['e_lang'] as $langName => $lang) {
				$langFile = LANG_PATH.'/'.$langName.'/lang.php';
				if(!array_key_exists($langName, $langset) or !file_exists($langFile)) {
					continue;
				}

				$_t_lang = array();
				$_lang_set = trim_array(explode("\n", $lang));
				foreach($_lang_set as $ls) {
					$_t_ls = trim_array(explode('=>', $ls));
					if(isset($_t_ls[1]) and !empty($_t_ls[1])) {
						$_t_lang[$_t_ls[0]] = $_t_ls[1];
					}
				}
				if(!empty($_t_lang)) {
					$this->_add_cfgFile($langFile, $_t_lang, $_EI);
				}
			}
		}

		/* check route */
		if(!empty($_EI['e_route'])) {
			$_t_route_base = array();
			$_t_route_lang = array();
			$_route_set = trim_array(explode("\n", $_EI['e_route']));
			foreach($_route_set as $rs) {
				$_t_rs = trim_array(explode('=>', $rs));
				if(isset($_t_rs[1]) and !empty($_t_rs[1])) {
					$_t_route_base[$_t_rs[0]] = $_t_rs[1];
					$_t_route_lang['<l:[a-z\\-]{2,11}>/'.$_t_rs[0]] = $_t_rs[1];
				}
			}
			if(!empty($_t_route_base)) {
				$this->_add_cfgFile(CFG_PATH.'/route.php', (C('URL.LANG_SUFFIX') ? $_t_route_lang : $_t_route_base), $_EI);
				$this->_add_cfgFile(CFG_PATH.'/route_base.php', $_t_route_base, $_EI);
				$this->_add_cfgFile(CFG_PATH.'/route_lang.php', $_t_route_lang, $_EI);
			}
		}

		/* backup file */
		if(!empty($_EI['file_list'])) {
			$fileListBackup = $this->_backup_file($_EI['file_list'], $_EI['e_hashcode']);
		}

		/* lock install */
		if(0 == $this->_lock_install($_EI, $fileListBackup)) {
			$result['error'] .= L('FILE_WRITE_FAILED', null, array('filename' => $installLockFilename));
		}

		return $result;
	}

	public function uninstall_extension($_EI) {
		$result = array('data' => '', 'error' => '');

		$installLockFilename = RUNTIME_PATH.D_S.'ext'.D_S.$_EI['e_hashcode'].D_S.'_lock.php';
		if(!is_file($installLockFilename)) {
			$result['error'] = L('EXTENSION_INEXISTENCE');
			return $result;
		}

		/* execute php operation */
		if(!empty($_EI['e_uninstall_php'])) {
			@eval($_EI['e_uninstall_php']);
		}

		if(false === $this->where(array('e_hashcode' => array('EQ', $_EI['e_hashcode'])))->delete()) {
			$result['error'] = L('UNINSTALL_FAILED');
			return $result;
		}

		/* execute uninstall SQL */
		vendor('Sql#class');
		$sql = str_replace(array(
			'{root_url}',
			'{-time-}',
			'{-ip-}'), array(
			__APP__,
			NOW_TIME,
			CLIENT_IP), $_EI['e_uninstall_sql']);
		$sql = Sql::remove_comments(Sql::remove_remarks($sql));
		$query = trim_array(Sql::split_sql($sql));
		if(!empty($query)) {
			foreach($query as $q) {
				M()->execute($q, true);
			}
		}

		/* restore file */
		$this->_restore_file($_EI['file_list'], include_cache($installLockFilename), filemtime($installLockFilename), $_EI['e_hashcode']);

		/* delete lock file */
		@unlink($installLockFilename);

		return $result;
	}

	private function _add_cfgFile($filename, $addon, &$_EI) {
		$_filename = str_replace(ROOT_PATH, '{root_path}', $filename);
		if(0 !== strpos($_filename, '{root_path}') or false !== strpos($_filename, '..')) {
			return;
		}
		$content = base64_encode("<?php\r\n".PFA_ACCESS_CHECK."return ".var_export(array_merge(include($filename), $addon), true).";\r\n?>");
		$_EI['file_list'][] = array(
			'filename' => $_filename,
			'overwrite' => 1,
		);
		file_put_contents(RUNTIME_PATH.D_S.'ext'.D_S.$_EI['e_hashcode'].D_S.md5($_filename), $content);
	}

	private function _backup_file($fileList, $hashcode) {
		$fileListBackup = array();
		load('encode_file#func');
		foreach($fileList as $file) {
			if(0 !== strpos($file['filename'], '{root_path}') or false !== strpos($file['filename'], '..')) {
				continue;
			}
			$filename = str_replace('{root_path}', ROOT_PATH, $file['filename']);
			if(is_file($filename) and (isset($file['overwrite']) and 1 == $file['overwrite'])) {
				$fileListBackup[] = array(
					'filename' => $file['filename'],
					'modify_time' => filemtime($filename),
					'access_time' => fileatime($filename)
				);
				file_put_contents(RUNTIME_PATH.D_S.'ext'.D_S.$hashcode.D_S.md5($file['filename']).'.bak', get_fileEncode($filename));
			}
			if(!is_file($filename) or (isset($file['overwrite']) and 1 == $file['overwrite'])) {
				if(false == dir_writable(dirname($filename))) {
					$fileListBackup['failed'][] = $file['filename'];
					continue;
				}
				$content = file_get_contents(RUNTIME_PATH.D_S.'ext'.D_S.$hashcode.D_S.md5($file['filename']));
				if(0 == file_put_contents($filename, base64_decode($content))) {
					$fileListBackup['failed'][] = $file['filename'];
					continue;
				}
			}
		}
		return $fileListBackup;
	}

	private function _restore_file($fileList, $fileListBackup, $installDatetime, $hashcode) {
		/* delete file */
		foreach($fileList as $file) {
			if(0 !== strpos($file['filename'], '{root_path}') or false !== strpos($file['filename'], '..')) {
				continue;
			}
			$filename = str_replace('{root_path}', ROOT_PATH, $file['filename']);
			if(filemtime($filename) <= $installDatetime) { /* whether the file has changed */
				@unlink($filename);
			}
		}
		/* restore file */
		foreach($fileListBackup as $file) {
			if(0 !== strpos($file['filename'], '{root_path}') or false !== strpos($file['filename'], '..')) {
				continue;
			}
			$filename = str_replace('{root_path}', ROOT_PATH, $file['filename']);
			if(filemtime($filename) <= $installDatetime) { /* whether the file has changed */
				$content = base64_decode(file_get_contents(RUNTIME_PATH.D_S.'ext'.D_S.$hashcode.D_S.md5($file['filename']).'.bak'));
				file_put_contents($filename, $content);
				touch($filename, $file['modify_time'], $file['access_time']);
			}
		}
	}

	private function _lock_install($_EI, $fileListBackup) {
		$installLockFilename = RUNTIME_PATH.D_S.'ext'.D_S.$_EI['e_hashcode'].D_S.'_lock.php';
		$content = "<?php\r\n";
		$content .= "//install information\r\n";
		$content .= "//----------------------------------------\r\n";
		$content .= "//extension name: ".$_EI['e_name']."\r\n";
		$content .= "//hashcode: ".$_EI['e_hashcode']."\r\n";
		$content .= "//time: ".date('Y-m-d H:i:s T', NOW_TIME)."\r\n";
		$content .= "//file list backup:\r\n";
		$content .= PFA_ACCESS_CHECK;
		$content .= "return ".var_export($fileListBackup, true).";\r\n";
		$content .= "?>";
		return file_put_contents($installLockFilename, $content);
	}

}

?>