diff --git a/Nginx.conf.md b/Nginx.conf.md new file mode 100644 index 0000000..01740e2 --- /dev/null +++ b/Nginx.conf.md @@ -0,0 +1,38 @@ +# Nginx config example + +* Domain: `fsc.org` +* Web directory: `/var/www/fscphp/www/` + +Nginx config: +``` + server { + listen 80; + server_name fsc.org; + + root /var/www/fscphp/www/; + index index.php index.html index.htm; + + location / { + try_files $uri $uri/ /index.php?$args; + } + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + location ~ ^/index\.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME /var/www/fscphp/www$fastcgi_script_name; + include fastcgi_params; + } + + # deny all other php + # + location ~ \.php { + deny all; + } + + # deny all hidden files + location ~ /\. { + deny all; + } + } +``` diff --git a/bin/command.php b/bin/command.php new file mode 100644 index 0000000..7102c96 --- /dev/null +++ b/bin/command.php @@ -0,0 +1,25 @@ + 'Asia/HonKong', //timezone + + 'content_directory' => 'content', //directory of contents + 'theme' => 'manual', //name of theme which is enabled + //when it's empty, use layout and views in directory views/ + 'default_layout' => 'main', //default layout + 'error_layout' => 'error', //exception layout, show error title and content + + //for debug, log directory: ../runtime/logs/ + 'debug' => false, +); diff --git a/controller/CommandController.php b/controller/CommandController.php new file mode 100644 index 0000000..293d1d3 --- /dev/null +++ b/controller/CommandController.php @@ -0,0 +1,33 @@ +get()); + echo "\n"; + + exit; + } + +} diff --git a/controller/Controller.php b/controller/Controller.php new file mode 100644 index 0000000..9abdf87 --- /dev/null +++ b/controller/Controller.php @@ -0,0 +1,179 @@ +layout = FSC::$app['config']['default_layout']; + } + } + + function __destruct() { + $this->logTimeCost(); + } + + //redirect url + protected function redirect($url, $code = 302) { + header("Location: {$url}", true, $code); + exit; + } + + //render view + protected function render($viewName, $viewData = array(), $pageTitle = '') { + $layoutFile = __DIR__ . '/../views/layout/' . $this->layout . '.php'; + if (!empty(FSC::$app['config']['theme'])) { + $layoutFile = __DIR__ . '/../themes/' . FSC::$app['config']['theme'] . '/views/layout/' . $this->layout . '.php'; + } + + //include layout and view + if (file_exists($layoutFile)) { + ob_start(); + include_once $layoutFile; + + $htmlCode = ob_get_contents(); + ob_end_clean(); + + //enable gzip + ob_start('ob_gzhandler'); + + //show time cost + $end_time = microtime(true); + $page_time_cost = ceil( ($end_time - FSC::$app['start_time']) * 1000 ); //ms + echo str_replace('{page_time_cost}', $page_time_cost, $htmlCode); + + ob_end_flush(); + }else { + $error_message = "Layout file {$this->layout}.php is not exist."; + if (!empty(FSC::$app['config']['theme'])) { + $error_message = "Layout file {$this->layout}.php in theme " . FSC::$app['config']['theme'] . " is not exist."; + } + throw new Exception($error_message, 500); + } + } + + //render json data + protected function renderJson($data) { + header("Content-Type: application/json; charset=utf-8"); + echo json_encode($data); + exit; + } + + //render m3u8 file + protected function renderM3u8($content) { + header("Content-Type: application/x-mpegURL; charset=utf-8"); + echo $content; + exit; + } + + //get params by key + protected function get($key = '', $defaultValue = '') { + if (empty($key)) { + return $_GET; + } + return !empty($_GET[$key]) ? $_GET[$key] : $defaultValue; + } + + //post params by key + protected function post($key = '', $defaultValue = '') { + if (empty($key)) { + return $_POST; + } + return !empty($_POST[$key]) ? $_POST[$key] : $defaultValue; + } + + //debug log + protected function logTimeCost() { + if (!empty(FSC::$app['config']['debug'])) { + $end_time = microtime(true); + $timeCost = ceil( ($end_time - FSC::$app['start_time']) * 1000 ); //ms + $thisUrl = FSC::$app['requestUrl']; + $logTime = date('Y-m-d H:i:s'); + $logDir = __DIR__ . '/../runtime/logs/'; + $logOk = @error_log("{$logTime}\t{$thisUrl}\ttime cost: {$timeCost} ms\n", 3, "{$logDir}debug.log"); + if (!$logOk) { //try to mkdir + @mkdir($logDir, 0700, true); + @error_log("{$logTime}\t{$thisUrl}\ttime cost: {$timeCost} ms\n", 3, "{$logDir}debug.log"); + } + } + } + + //get user real ip + protected function getUserIp() { + $ip = false; + + if (!empty($_SERVER["HTTP_CLIENT_IP"])) { + $ip = $_SERVER["HTTP_CLIENT_IP"]; + } + + if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ips = explode(', ', $_SERVER['HTTP_X_FORWARDED_FOR']); + if (!empty($ip)) { + array_unshift($ips, $ip); + $ip = false; + } + + if (!empty($ips)) { + for ($i = 0; $i < count($ips); $i++) { + if (!preg_match("/^(10│172\.16│192\.168)\./", $ips[$i])) { + $ip = $ips[$i]; + break; + } + } + } + } + + return !empty($ip) ? $ip : $_SERVER['REMOTE_ADDR']; + } + + //request url via curl + protected function request($url, $postFields = array(), $timeout = 10, $pc = false) { + $s = curl_init(); + + curl_setopt($s, CURLOPT_URL, $url); + curl_setopt($s, CURLOPT_TIMEOUT, $timeout); + curl_setopt($s, CURLOPT_RETURNTRANSFER, true); + + if (!empty($postFields)) { + curl_setopt($s, CURLOPT_POST, true); + curl_setopt($s, CURLOPT_POSTFIELDS, $postFields); + } + + //iphone client + $user_agent = 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'; + //mac os client + if ($pc) { + $user_agent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_0_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'; + } + curl_setopt($s, CURLOPT_USERAGENT, $user_agent); + curl_setopt($s, CURLOPT_REFERER, '-'); + + $curlResult = curl_exec($s); + $curlStatus = curl_getinfo($s, CURLINFO_HTTP_CODE); + curl_close($s); + + return array( + 'status' => $curlStatus, + 'result' => $curlResult, + ); + } + + //set cookie for message show + //type: info, warning, danger, success + protected function sendMsgToClient($msg, $type = 'info') { + $cookieKey = "alert_msg_{$type}"; + $expires = time() + 15; + $path = '/'; + + //empty character replace to "%20" + $encoded = urlencode($msg); + $noempty = str_replace('+', '%20', $encoded); + $val = base64_encode( $noempty ); + + setcookie($cookieKey, $val, $expires, $path); + } + +} diff --git a/controller/SiteController.php b/controller/SiteController.php new file mode 100644 index 0000000..b4f7c07 --- /dev/null +++ b/controller/SiteController.php @@ -0,0 +1,23 @@ +sendMsgToClient('Alert message - danger', 'danger'); + $this->sendMsgToClient('Alert message - warning', 'warning'); + $this->sendMsgToClient('Alert message - success', 'success'); + $this->sendMsgToClient('Alert message - info', 'info'); + + $pageTitle = "Welcome to FSC"; + $viewName = 'index'; + $params = array( + 'foo' => 'bar', + ); + return $this->render($viewName, $params, $pageTitle); + } + +} diff --git a/lib/FSC.php b/lib/FSC.php new file mode 100644 index 0000000..294b290 --- /dev/null +++ b/lib/FSC.php @@ -0,0 +1,109 @@ +getMessage(), ENT_QUOTES); + $code = $e->getCode(); + if (empty($code)) {$code = 500;} + $title = "HTTP/1.0 {$code} Internal Server Error"; + header($title, true, $code); + + //render error layout + $errors = compact('code', 'title', 'content'); + $layoutName = !empty($config['error_layout']) ? $config['error_layout'] : 'error'; + $error_layout = __DIR__ . "/../views/layout/{$layoutName}.php"; + if (!empty($config['theme'])) { + $theme_error_layout = __DIR__ . "/../themes/{$config['theme']}/views/layout/{$layoutName}.php"; + if (file_exists($theme_error_layout)) { + $error_layout = $theme_error_layout; + } + } + + ob_start(); + include_once $error_layout; + + $htmlCode = ob_get_contents(); + ob_end_clean(); + + //enable gzip + ob_start('ob_gzhandler'); + echo $htmlCode; + ob_end_flush(); + } + } + + //parse url to controller and action name + protected static function getControllerAndAction($url, $config) { + $arr = parse_url($url); + $path = !empty($arr['path']) ? $arr['path'] : '/'; + + @list(, $controller, $action) = explode('/', $path); + if (empty($controller)) { + $controller = !empty($config['defaultController']) ? $config['defaultController'] : 'site'; + }else if(preg_match('/\w+\.\w+/i', $controller)) { //not controller but some file not exist + throw new Exception("File {$controller} not exist.", 404); + }else { + $controller = preg_replace('/\W/', '', $controller); + } + + if (empty($action)) { + $action = 'index'; + }else { + $action = preg_replace('/\W/', '', $action); + } + + return compact('controller', 'action'); + } + + //add themes support + protected static function loadController($config) { + //parse url to controller and action + $requestUrl = $_SERVER['REQUEST_URI']; + $arr = self::getControllerAndAction($requestUrl, $config); + $controller = $arr['controller']; + $action = $arr['action']; + $start_time = self::$start_time; + + //set parameters + self::$app = compact('config', 'controller', 'action', 'requestUrl', 'start_time'); + + //call class and function + $className = ucfirst($controller) . 'Controller'; + $funName = 'action' . ucfirst($action); + $controllerFile = __DIR__ . "/../controller/{$className}.php"; + if (!empty($config['theme'])) { + $controllerFile = __DIR__ . "/../themes/{$config['theme']}/controller/{$className}.php"; + } + if (file_exists($controllerFile)) { + require_once $controllerFile; + $cls = new $className(); + if (method_exists($className, $funName)) { + $cls->$funName(); + }else { + $error_message = "Function {$funName} not exist in class {$className}."; + if (!empty($config['theme'])) { + $error_message = "Function {$funName} not exist in class {$className} of theme {$config['theme']}."; + } + throw new Exception($error_message, 500); + } + }else { + $error_message = "Controller {$className}.php not exist."; + if (!empty($config['theme'])) { + $error_message = "Controller {$className}.php not exist in theme {$config['theme']}."; + } + throw new Exception($error_message, 500); + } + } + +} diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..7d76005 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1 @@ +# 插件目录 diff --git a/runtime/README.md b/runtime/README.md new file mode 100644 index 0000000..4d4ad7d --- /dev/null +++ b/runtime/README.md @@ -0,0 +1,13 @@ +# 运行时目录 + +需要设置此目录权限为777: +``` +chmod -R 777 runtime/ +``` + +或者允许php进程所属用户写入: +``` +chown -R apache:apache runtime/ +``` + +其中apache为php-fpm进程所属用户。 diff --git a/themes/README.md b/themes/README.md new file mode 100644 index 0000000..c6b45ec --- /dev/null +++ b/themes/README.md @@ -0,0 +1,17 @@ +# 皮肤目录 + +此目录结构为: +``` +{皮肤目录名}/views/layout/ +{皮肤目录名}/views/{视图目录名}/ +``` + + +## 示例 + +皮肤名:**night**,默认首页SiteController使用的视图名:**site**, +其目录结构为: +``` +night/views/layout/ +night/views/site/ +``` \ No newline at end of file diff --git a/themes/manual/controller/SiteController.php b/themes/manual/controller/SiteController.php new file mode 100644 index 0000000..b4f7c07 --- /dev/null +++ b/themes/manual/controller/SiteController.php @@ -0,0 +1,23 @@ +sendMsgToClient('Alert message - danger', 'danger'); + $this->sendMsgToClient('Alert message - warning', 'warning'); + $this->sendMsgToClient('Alert message - success', 'success'); + $this->sendMsgToClient('Alert message - info', 'info'); + + $pageTitle = "Welcome to FSC"; + $viewName = 'index'; + $params = array( + 'foo' => 'bar', + ); + return $this->render($viewName, $params, $pageTitle); + } + +} diff --git a/themes/manual/views/layout/main.php b/themes/manual/views/layout/main.php new file mode 100644 index 0000000..84bd2ad --- /dev/null +++ b/themes/manual/views/layout/main.php @@ -0,0 +1,26 @@ + + + + +<?php echo $pageTitle;?> + + + + + + + + + + + + + + + diff --git a/themes/manual/views/site/index.php b/themes/manual/views/site/index.php new file mode 100644 index 0000000..7636d77 --- /dev/null +++ b/themes/manual/views/site/index.php @@ -0,0 +1,10 @@ + +

欢迎使用FileSite的皮肤Manual

+

FileSite.io是一个帮助你快速创建文档、导航、图片、书籍、视频网站的PHP开源系统。

+
+ diff --git a/views/layout/error.php b/views/layout/error.php new file mode 100644 index 0000000..a62d59b --- /dev/null +++ b/views/layout/error.php @@ -0,0 +1,18 @@ + + + +<?php echo $errors['title']; ?> + + + + + +

+

Exception :

+
+ + diff --git a/views/layout/main.php b/views/layout/main.php new file mode 100644 index 0000000..84bd2ad --- /dev/null +++ b/views/layout/main.php @@ -0,0 +1,26 @@ + + + + +<?php echo $pageTitle;?> + + + + + + + + + + + + + + + diff --git a/views/site/index.php b/views/site/index.php new file mode 100644 index 0000000..6fbe5cb --- /dev/null +++ b/views/site/index.php @@ -0,0 +1,33 @@ + +

Welcome to FSC

+

FSC is the core lib of filesite.io, a small PHP Framework.

+
+ +

Download FSC

+

+ Source code: + https://git.filesite.io/filesite/fsc, + download it via git: +

+
+git clone "https://git.filesite.io/filesite/fsc.git"
+
+ +

Sample code

+ +

App parameters

+<?php print_r(FSC::$app); ?> +
+
+
+ +

View parameters

+<?php print_r($viewData); ?> +
+
+
diff --git a/www/content/README.md b/www/content/README.md new file mode 100644 index 0000000..441754e --- /dev/null +++ b/www/content/README.md @@ -0,0 +1 @@ +# 内容目录 diff --git a/www/css/main.css b/www/css/main.css new file mode 100644 index 0000000..b162396 --- /dev/null +++ b/www/css/main.css @@ -0,0 +1,5 @@ +.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px} +.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6} +.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1} +.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} +.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1} diff --git a/www/favicon.ico b/www/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/www/index.php b/www/index.php new file mode 100644 index 0000000..a3b845a --- /dev/null +++ b/www/index.php @@ -0,0 +1,13 @@ + 0) { + var h1 = els_h1[0]; + h1.parentNode.insertBefore(alertDiv, h1.nextSibling); + }else { + var els_header = document.getElementsByClassName('header'); + if (els_header.length > 0) { + els_header[0].appendChild(alertDiv); + } + } + + setTimeout(function() { + alertDiv.remove(); + }, 5000); + }; + + //get alert message from cookie + var cookieKeys = { + 'info': 'alert_msg_info', + 'success': 'alert_msg_success', + 'warning': 'alert_msg_warning', + 'danger': 'alert_msg_danger' + }; + + var alert_msg = ''; + for (var key in cookieKeys) { + try { + alert_msg = decodeURIComponent( atob( Cookies.get(cookieKeys[key]) ) ); + if (alert_msg) { + showAlertMsg(key, alert_msg); + Cookies.remove(cookieKeys[key]); + } + }catch(e) {} + } +})();