Browse Source

merge fsc

master
filesite 3 years ago
parent
commit
509e68b0d1
  1. 38
      Nginx.conf.md
  2. 25
      bin/command.php
  3. 16
      conf/app.php
  4. 33
      controller/CommandController.php
  5. 179
      controller/Controller.php
  6. 23
      controller/SiteController.php
  7. 109
      lib/FSC.php
  8. 1
      plugins/README.md
  9. 13
      runtime/README.md
  10. 17
      themes/README.md
  11. 23
      themes/manual/controller/SiteController.php
  12. 26
      themes/manual/views/layout/main.php
  13. 10
      themes/manual/views/site/index.php
  14. 18
      views/layout/error.php
  15. 26
      views/layout/main.php
  16. 33
      views/site/index.php
  17. 1
      www/content/README.md
  18. 5
      www/css/main.css
  19. 0
      www/favicon.ico
  20. 13
      www/index.php
  21. 2
      www/js/js.cookie.min.js
  22. 40
      www/js/main.js

38
Nginx.conf.md

@ -0,0 +1,38 @@ @@ -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;
}
}
```

25
bin/command.php

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
<?php
require_once __DIR__ . '/../lib/FSC.php';
require_once __DIR__ . '/../controller/Controller.php';
$config = require_once __DIR__ . '/../conf/app.php';
$default_timezone = !empty($config['default_timezone']) ? $config['default_timezone'] : 'Asia/HongKong';
date_default_timezone_set($default_timezone);
//set variables
$action = isset($argv[1]) ? $argv[1] : 'index';
$_SERVER['REQUEST_URI'] = "/command/{$action}";
//GET parameters, format: foo=bar&hello=world
if (!empty($argv[2])) {
$_SERVER['QUERY_STRING'] = $argv[2];
$arr = explode('&', $argv[2]);
if (!isset($_GET)) {$_GET = array();}
foreach ($arr as $item) {
$ar = explode('=', $item);
$_GET[$ar[0]] = $ar[1];
}
}
//run app
FSC::run($config);

16
conf/app.php

@ -0,0 +1,16 @@ @@ -0,0 +1,16 @@
<?php
/**
* Config
*/
return array(
'default_timezone' => '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,
);

33
controller/CommandController.php

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
<?php
/**
* Command Controller
*/
Class CommandController extends Controller {
public function actionIndex() {
$commands = <<<eof
Actions:
- test
Usage:
php command.php action parameters
eof;
echo $commands;
exit;
}
public function actionTest() {
echo "## App variables:\n";
print_r(FSC::$app);
echo "\n";
echo "## GET parameters:\n";
print_r($this->get());
echo "\n";
exit;
}
}

179
controller/Controller.php

@ -0,0 +1,179 @@ @@ -0,0 +1,179 @@
<?php
/**
* Controller
*/
Class Controller {
protected $layout = 'main';
function __construct() {
//set default layout
if (!empty(FSC::$app['config']['default_layout'])) {
$this->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);
}
}

23
controller/SiteController.php

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
<?php
/**
* Site Controller
*/
Class SiteController extends Controller {
public function actionIndex() {
//alert message test
$this->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);
}
}

109
lib/FSC.php

@ -0,0 +1,109 @@ @@ -0,0 +1,109 @@
<?php
/**
* Class App
*/
Class FSC {
public static $app = array();
protected static $start_time = 0;
//call function in controller
public static function run($config = []) {
self::$start_time = !empty($config['start_time']) ? $config['start_time'] : microtime(true);
try {
self::loadController($config);
}catch(Exception $e) {
$content = htmlspecialchars($e->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);
}
}
}

1
plugins/README.md

@ -0,0 +1 @@ @@ -0,0 +1 @@
# 插件目录

13
runtime/README.md

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
# 运行时目录
需要设置此目录权限为777:
```
chmod -R 777 runtime/
```
或者允许php进程所属用户写入:
```
chown -R apache:apache runtime/
```
其中apache为php-fpm进程所属用户。

17
themes/README.md

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
# 皮肤目录
此目录结构为:
```
{皮肤目录名}/views/layout/
{皮肤目录名}/views/{视图目录名}/
```
## 示例
皮肤名:**night**,默认首页SiteController使用的视图名:**site**,
其目录结构为:
```
night/views/layout/
night/views/site/
```

23
themes/manual/controller/SiteController.php

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
<?php
/**
* Site Controller
*/
Class SiteController extends Controller {
public function actionIndex() {
//alert message test
$this->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);
}
}

26
themes/manual/views/layout/main.php

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
<!DocType html>
<html>
<head>
<meta charset="utf-8">
<title><?php echo $pageTitle;?></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link href="/css/main.css?v.1.0" rel="stylesheet">
</head>
<body>
<?php
//### Render view file
$viewFile = __DIR__ . '/../' . FSC::$app['controller'] . '/' . $viewName . '.php';
include_once $viewFile;
?>
<div class="footer">
&copy;FSC 2022 - execute time: {page_time_cost} ms
</div>
<script src="/js/js.cookie.min.js"></script>
<script src="/js/main.js?v.1.0"></script>
</body>
</html>

10
themes/manual/views/site/index.php

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
<style>
body{padding:10px;max-width:720px;margin:0 auto}
code{background-color:#EEE}
pre{padding:10px;background-color:#DDD}
.footer{text-align:center}
</style>
<h1>欢迎使用FileSite的皮肤Manual</h1>
<p><a href="https://filesite.io" target="_blank">FileSite.io</a>是一个帮助你快速创建文档、导航、图片、书籍、视频网站的PHP开源系统。</p>
<hr>

18
views/layout/error.php

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
<!DocType html>
<head>
<meta charset="utf-8">
<title><?php echo $errors['title']; ?></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<style>
body{max-width:768px;margin:0 auto}
h1{color:#FF0000}
.error{padding:4px;color:yellow;background-color:gray;min-height:40px}
</style>
</head>
<body>
<h1><?php echo $errors['title']; ?></h1>
<h3>Exception <?php echo $errors['code']; ?>:</h3>
<div class="error"><?php echo $errors['content']; ?></div>
</body>
</html>

26
views/layout/main.php

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
<!DocType html>
<html>
<head>
<meta charset="utf-8">
<title><?php echo $pageTitle;?></title>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link href="/css/main.css?v.1.0" rel="stylesheet">
</head>
<body>
<?php
//### Render view file
$viewFile = __DIR__ . '/../' . FSC::$app['controller'] . '/' . $viewName . '.php';
include_once $viewFile;
?>
<div class="footer">
&copy;FSC 2022 - execute time: {page_time_cost} ms
</div>
<script src="/js/js.cookie.min.js"></script>
<script src="/js/main.js?v.1.0"></script>
</body>
</html>

33
views/site/index.php

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
<style>
body{padding:10px;max-width:720px;margin:0 auto}
code{background-color:#EEE}
pre{padding:10px;background-color:#DDD}
.footer{text-align:center}
</style>
<h1>Welcome to FSC</h1>
<p>FSC is the core lib of <a href="https://filesite.io" target="_blank">filesite.io</a>, a small PHP Framework.</p>
<hr>
<h2>Download FSC</h2>
<p>
Source code:
<a href="https://git.filesite.io/filesite/fsc" target="_blank">https://git.filesite.io/filesite/fsc</a>,
download it via git:
</p>
<pre>
git clone "https://git.filesite.io/filesite/fsc.git"
</pre>
<h2>Sample code</h2>
<h3>App parameters</h3>
<code>&lt;?php print_r(FSC::$app); ?&gt;</code>
<pre>
<?php print_r(FSC::$app); ?>
</pre>
<h3>View parameters</h3>
<code>&lt;?php print_r($viewData); ?&gt;</code>
<pre>
<?php print_r($viewData); ?>
</pre>

1
www/content/README.md

@ -0,0 +1 @@ @@ -0,0 +1 @@
# 内容目录

5
www/css/main.css

@ -0,0 +1,5 @@ @@ -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}

0
www/favicon.ico

13
www/index.php

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
<?php
/* All php controller enter from here */
$start_time = microtime(true); //for time cost
$config = require_once __DIR__ . '/../conf/app.php';
$default_timezone = !empty($config['default_timezone']) ? $config['default_timezone'] : 'Asia/HongKong';
date_default_timezone_set($default_timezone);
$config['start_time'] = $start_time;
require_once __DIR__ . '/../lib/FSC.php';
require_once __DIR__ . '/../controller/Controller.php';
//run app
FSC::run($config);

2
www/js/js.cookie.min.js vendored

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
/*! js-cookie v3.0.0-rc.1 | MIT */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var n=e.Cookies,r=e.Cookies=t();r.noConflict=function(){return e.Cookies=n,r}}())}(this,function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)e[r]=n[r]}return e}var t={read:function(e){return e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}};return function n(r,o){function i(t,n,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape),n=r.write(n,t);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n+c}}return Object.create({set:i,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var n=document.cookie?document.cookie.split("; "):[],o={},i=0;i<n.length;i++){var c=n[i].split("="),u=c.slice(1).join("=");'"'===u[0]&&(u=u.slice(1,-1));try{var f=t.read(c[0]);if(o[f]=r.read(u,f),e===f)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){i(t,"",e({},n,{expires:-1}))},withAttributes:function(t){return n(this.converter,e({},this.attributes,t))},withConverter:function(t){return n(e({},this.converter,t),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(r)}})}(t,{path:"/"})});

40
www/js/main.js

@ -0,0 +1,40 @@ @@ -0,0 +1,40 @@
(function() {
var showAlertMsg = function(msgType, msgContent) {
var els_h1 = document.getElementsByTagName('h1');
var alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${msgType}`;
alertDiv.innerHTML = msgContent;
if (els_h1.length > 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) {}
}
})();
Loading…
Cancel
Save