commit 62fa3d6b6f6293ddc96a7b47b280407ca1e3f642
Author: Claudio Alessi <smoppy@gmail.com>
Date: Tue, 8 Dec 2015 22:05:04 +0100
init
Diffstat:
A | README.md | | | 132 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | bored.php | | | 539 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 671 insertions(+), 0 deletions(-)
diff --git a/README.md b/README.md
@@ -0,0 +1,132 @@
+Bored is a PHP micro-framework. It provides a simple framework but does not
+enforce any particular structure so you can bring things together in order to
+refine your own ad-hoc framework.
+
+In a nutshell:
+```
+include('bored.php');
+
+route('GET', '/hello/!name', function($name) {
+ return "Hello ${name}";
+});
+
+bored_run();
+```
+
+The main documentation is the source code.
+
+### Concepts
+Things bored will never have:
+
+* An ORM
+* Dependency injection
+* Templates system
+* Built-in database migration
+* Unit testing
+* More than 2000 SLOC.
+
+### Routing
+Routing handling. It works like this:
+
+```
+route('GET', '/hello/!name/?surname', function($name, $surname = '') {
+ $s = "Hello $name";
+ if($surname)
+ $s .= " $surname";
+ return $s;
+});
+```
+
+Argument prefixes means !mandatory and ?optional.
+
+### Database
+Database facilities are based on the mysqli PHP extension. It mainly consists
+of a single function dbquery() which allows few but very convenient ways to
+interact with the database. Some example:
+
+Fetch a single row:
+```
+$sql = "select id,username from users where id = 30";
+$user = dbquery($sql);
+print_r($user);
+```
+
+This will results in the following:
+
+```
+Array
+(
+ [id] => 30
+ [username] => userfoo
+ ...
+)
+```
+
+Fetch multiple rows:
+```
+$sql = "select id,username from users";
+$users = dbquery($sql, -1);
+print_r($users);
+```
+
+This produces an output like this:
+
+```
+Array
+(
+ [0] => Array
+ (
+ [id] => 248
+ [username] => userbar
+ ...
+ )
+
+ [1] => Array
+ (
+ [id] => 425
+ [username] => userbaz
+ ...
+ )
+ ...
+)
+```
+
+### Views
+...
+
+### Session
+...
+
+### Run-time cache
+...
+
+### Utils
+
+#### Mails
+...
+
+#### Output formatting
+...
+
+### Web server configuration
+I can't cover all scenarious, here is the most common where you a bored
+instance for a single domain:
+
+#### nginx
+
+```
+server {
+ root /usr/share/nginx/www/bored/app/public/;
+ index index.html index.php;
+ server_name bored;
+
+ location / {
+ try_files $uri $uri/ /index.php?$args;
+ location ~ \.php$ {
+ fastcgi_pass unix:/var/run/php5-fpm.sock;
+ fastcgi_param SCRIPT_FILENAME $request_filename;
+ include fastcgi_params;
+ }
+ }
+}
+```
diff --git a/bored.php b/bored.php
@@ -0,0 +1,539 @@
+<?php
+
+define('PATH_VIEWS', 'views');
+define('DEFAULT_LAYOUT', 'main');
+
+$HT_DEFAULT_FMTS = [
+ 'year_0' => "An year ago",
+ 'years_0' => "%d years ago",
+ 'year_1' => "An year from now",
+ 'years_1' => "%d years from now",
+ 'month_0' => "A month ago",
+ 'months_0' => "%d months ago",
+ 'month_1' => "A month from now",
+ 'months_1' => "%d months from now",
+ 'day_0' => "A day ago",
+ 'days_0' => "%d days ago",
+ 'day_1' => "A day from now",
+ 'days_1' => "%d days from now",
+ 'hour_0' => "An hour ago",
+ 'hours_0' => "%d hours ago",
+ 'hour_1' => "An hour from now",
+ 'hours_1' => "%d hours from now",
+ 'minute_0' => "A minute ago",
+ 'minutes_0' => "%d minutes ago",
+ 'minute_1' => "A minute from now",
+ 'minutes_1' => "%d minutes from now",
+ 'second_0' => "A second ago",
+ 'seconds_0' => "%d seconds ago",
+ 'second_1' => "A second from now",
+ 'seconds_1' => "%d seconds from now",
+];
+
+$HT_DIVS = [
+ 'year' => 60*60*24*365,
+ 'month' => 60*60*24*31,
+ 'day' => 60*60*24,
+ 'hour' => 60*60,
+ 'minute' => 60,
+ 'second' => 0
+];
+
+$dblink = null;
+
+function route($method, $route, $func = null) {
+ static $routes = [];
+ if(!$func) {
+ $r = null;
+ $n = '';
+ $argv = [];
+ foreach(explode('/', $route) as $arg) {
+ $n .= ($n == '/' ? $arg : "/$arg");
+ if($r)
+ $argv[] = $arg;
+ if(isset($routes[$method][$n])) {
+ $r = $routes[$method][$n];
+ $argv = [];
+ if($route == $n)
+ break;
+ }
+ if(isset($routes[$method]["$n/"])) {
+ $r = $routes[$method]["$n/"];
+ $argv = [];
+ }
+ }
+ if(!$r || (count($argv) < $r['mandatory']) || (count($argv) > $r['argc'])) {
+ header($_SERVER['SERVER_PROTOCOL'].' 404 Not Found');
+ header("Status: 404 Not Found"); /* CGI */
+ exit(1);
+ }
+ return call_user_func_array($r['func'], $argv);
+ }
+ $name = [];
+ $argc = 0;
+ $mandatory = 0;
+ foreach(explode('/', $route) as $arg) {
+ switch(@$arg[0]) {
+ case '!': ++$argc; ++$mandatory; break;
+ case '?': ++$argc; break;
+ default: $name[] = $arg; break;
+ }
+ }
+ $name = implode('/', $name);
+ if($argc)
+ $name .= '/';
+ $routes[$method][$name] = ['func' => $func, 'argc' => $argc, 'mandatory' => $mandatory];
+ return 0;
+}
+
+function dbopen($host, $user, $pass, $dbname) {
+ if(!($r = @mysqli_connect($host, $user, $pass, $dbname)))
+ die('database error');
+ global $dblink;
+ $dblink = $r;
+ return $r;
+}
+
+function dbquery($sql, $limit = 1, $multi = false) {
+ if(!is_string($sql) || !($sql = trim($sql)))
+ return false;
+
+ $ck = md5($sql);
+ if(($ret = cache($ck)))
+ return $ret;
+
+ global $dblink;
+ $cn = "$sql-$limit-$multi";
+
+ $cmd = strtolower(substr($sql, 0, strpos($sql, ' ')));
+
+ if($cmd == 'select') {
+ if($limit == -1)
+ $limit = '18446744073709551615';
+ $sql .= " limit $limit";
+ }
+
+ if($multi)
+ $res = mysqli_multi_query($dblink, $sql);
+ else
+ $res = mysqli_query($dblink, $sql);
+ if(!$res)
+ return false;
+
+ if($multi) {
+ $ret = [];
+
+ for($res = mysqli_use_result($dblink); $res; $res = mysqli_store_result($dblink)) {
+ $r = [];
+ while(($t = mysqli_fetch_assoc($res)))
+ $r[] = $t;
+ $ret[] = $r;
+ mysqli_free_result($res);
+ mysqli_next_result($dblink);
+ }
+ }
+ else {
+ switch($cmd) {
+ case 'select':
+ case 'call':
+ if($cmd == 'select' && $limit == '1') {
+ $ret = mysqli_fetch_assoc($res);
+ break;
+ }
+
+ $ret = [];
+ while(($t = mysqli_fetch_assoc($res)))
+ $ret[] = $t;
+ break;
+
+ case 'insert':
+ $ret = mysqli_insert_id($dblink);
+ if(!$ret)
+ $ret = true;
+ break;
+
+ case 'delete':
+ $ret = mysqli_affected_rows($dblink);
+ break;
+
+ default:
+ $ret = $res;
+ break;
+ }
+ }
+
+ return $ret;
+}
+
+function dbping($l = null) {
+ if(!$l) {
+ global $dblink;
+ $l = $dblink;
+ }
+ return mysqli_ping($l);
+}
+
+function dberror($l = null) {
+ if(!$l) {
+ global $dblink;
+ $l = $dblink;
+ }
+ return mysqli_error($l);
+}
+
+function dbin($s) {
+ return addslashes(htmlentities($s, ENT_QUOTES, 'UTF-8'));
+}
+
+function dbout($s) {
+ return stripslashes(html_entity_decode($s, ENT_QUOTES, 'UTF-8'));
+}
+
+function dbins($tbl, $info) {
+ $vals = [];
+ foreach($info as $k => $v) {
+ $v = dbin($v);
+ $vals["`$k`"] = "'$v'";
+ }
+ $sql = 'insert into `'.$tbl.'`
+ ('.implode(',', array_keys($vals)).')
+ values ('.implode(',', array_values($vals)).')';
+ return dbquery($sql);
+}
+
+function dbupd($tbl, $info, $id) {
+ $vals = [];
+ foreach($info as $k => $v) {
+ $v = dbin($v);
+ $vals[] = "`$k`= '$v'";
+ }
+ $vals = implode(',', $vals);
+ $sql = "update `$tbl` set $vals where id = $id";
+ return dbquery($sql);
+}
+
+function dbdel($tbl, $id) {
+ $sql = "delete from `$tbl` where id = $id";
+ return dbquery($sql);
+}
+
+function sizefitbox($src, $dst) {
+ list($ow, $oh) = explode('x', $src);
+ list($tow, $toh) = explode('x', $dst);
+
+ $rw = $tow / $ow;
+ $rh = $toh / $oh;
+ $ratio = min($rw, $rh);
+
+ $w = $ow * $ratio;
+ $h = $oh * $ratio;
+
+ $w = (int)$w;
+ $h = (int)$h;
+
+ return "${w}x${h}";
+}
+
+function thumb($src, $size = '64x64', $force = 0) {
+ if(strpos($size, 'x') === false)
+ $size = (int)$size.'x'.(int)$size;
+ $pi = pathinfo($src);
+ /* Since imgresize() use the extension to identify the file type, it's
+ * faster to returns here. */
+ if(!@$pi['extension'])
+ return null;
+ /* name.ext > name.size.ext */
+ $thumb = "${pi['dirname']}/${pi['filename']}.${size}.${pi['extension']}";
+ if(!$force && file_exists($thumb))
+ return $thumb;
+ $t = imgresize($src, $thumb, $size);
+ if($t)
+ return $t;
+ return $thumb;
+}
+
+function imgresize($src, $saveas, $whxy = '64x64-0,0', $opts = null) {
+ $in = null;
+ $out = null;
+ $transparency = false;
+ $ext = strtolower((string)@pathinfo($src)['extension']);
+ switch($ext) {
+ case 'jpg':
+ case 'jpeg':
+ $in = 'imagecreatefromjpeg';
+ $out = 'imagejpeg';
+ break;
+ case 'gif':
+ $in = 'imagecreatefromgif';
+ $out = 'imagegif';
+ /* imagegif() doesn't take a third param */
+ if($opts !== null)
+ $opts = null;
+ break;
+ case 'bmp':
+ $in = 'imagecreatefromwbmp';
+ $out = 'imagewbmp';
+ break;
+ case 'png':
+ $in = 'imagecreatefrompng';
+ $out = 'imagepng';
+ $transparency = true;
+ break;
+ default: /* unsupported image */ return -1;
+ }
+ if(!($oi = $in($src)))
+ return 2;
+
+ $t = explode('-', $whxy);
+ $wh = $t[0];
+ $wh = explode('x', $wh);
+ $xy = isset($t[1]) ? $t[1] : '0,0';
+ $xy = explode(',', $xy);
+
+ $w = (int)@$wh[0];
+ $h = isset($wh[1]) ? $wh[1] : $w;
+ $x = (int)@$xy[0];
+ $y = (int)@$xy[1];
+
+ list($iw, $ih) = getimagesize($src);
+ $ratio = [$iw / $ih, $w / $h];
+ if($x != 0 || $y != 0) {
+ $crop = imagecreatetruecolor($w, $h);
+ $cropW = $w;
+ $cropH = $h;
+
+ imagecopy($crop, $oi, 0, 0, (int)$x, (int)$y, $w, $h);
+ if($transparency) {
+ imagealphablending($crop, false);
+ imagesavealpha($crop, true);
+ }
+ }
+ else if($ratio[0] != $ratio[1]) {
+ $scale = min((float)($iw / $w), (float)($ih / $h));
+ $cropX = (float)($iw - ($scale * $w));
+ $cropY = (float)($ih - ($scale * $h));
+ $cropW = (float)($iw - $cropX);
+ $cropH = (float)($ih - $cropY);
+ $crop = imagecreatetruecolor($cropW, $cropH);
+ if($transparency) {
+ imagealphablending($crop, false);
+ imagesavealpha($crop, true);
+ }
+ imagecopy($crop, $oi, 0, 0, (int)($cropX / 2), (int)($cropY / 2), $cropW, $cropH);
+ }
+ $ni = imagecreatetruecolor($w, $h);
+ if($transparency) {
+ imagealphablending($ni, false);
+ imagesavealpha($ni, true);
+ }
+ if(isset($crop)) {
+ imagecopyresampled($ni, $crop, 0, 0, 0, 0, $w, $h, $cropW, $cropH);
+ imagedestroy($crop);
+ }
+ else {
+ imagecopyresampled($ni, $oi, 0, 0, 0, 0, $w, $h, $iw, $ih);
+ }
+ imagedestroy($oi);
+ if($opts !== null)
+ $r = $out($ni, $saveas, $opts);
+ else
+ $r = $out($ni, $saveas);
+ imagedestroy($ni);
+ if($r === false)
+ return 1;
+ return 0;
+}
+
+function jsonerr($v) {
+ json('ko', $v);
+}
+
+function jsonok($v) {
+ json('ok', $v);
+}
+
+function json($state, $res) {
+ die(json_encode(['state' => $state, 'res' => $res]));
+}
+
+function sendmail($from, $to, $subj, $message, $files = null) {
+ ini_set('sendmail_from', $from);
+ $headers = "From: $from\n" .
+ "Return-Path: <$from>\r\n" .
+ "MIME-Version: 1.0\n";
+
+ if(!(is_array($files) || count($files))) {
+ $headers .= "Content-Type: text/html; charset=\"UTF-8\"\n" .
+ "Content-Transfer-Encoding: 7bit\n\n";
+ }
+ else {
+ $semi_rand = md5(time());
+ $mime_boundary = "==Multipart_Boundary_x{$semi_rand}x";
+ $headers .= "Content-Type: multipart/mixed; boundary=\"{$mime_boundary}\"\n";
+ $message = "--{$mime_boundary}\n" .
+ "Content-Type: text/plain; charset=\"UTF-8\"\n" .
+ "Content-Transfer-Encoding: 7bit\n\n" .
+ $message . "\n\n";
+ foreach($files as $fn) {
+ $f = basename($fn);
+ if(!is_file($fn))
+ continue;
+ $data = chunk_split(base64_encode(file_get_contents($fn)));
+
+ $message .= "--{$mime_boundary}\n" .
+ "Content-Type: application/octet-stream; name=\"$f\"\n" .
+ "Content-Description: $f\n" .
+ "Content-Disposition: attachment; filename=\"$f\"; size=".filesize($fn).";\n" .
+ "Content-Transfer-Encoding: base64\n\n" . $data . "\n\n";
+ }
+ $message .= "--{$mime_boundary}--";
+ }
+
+ return @mail($to, $subj, $message, $headers);
+}
+
+function _store(&$to, $k, $v) {
+ $r = isset($to[$k]) ? $to[$k] : null;
+ if($v !== null)
+ $to[$k] = $v;
+ return $r;
+}
+
+function cache($k, $v = null) {
+ static $__cache = [];
+ return _store($__cache, $k, $v);
+}
+
+function sess($k, $v = null) {
+ $r = isset($_SESSION[$k]) ? $_SESSION[$k] : null;
+ return _store($_SESSION, $k, $v);
+}
+
+function pre($d) {
+ echo '<pre>';
+ print_r($d);
+ echo '</pre>';
+}
+
+function humanstime($timestamp, $fmts = null) {
+ global $HT_DIVS;
+
+ $divs = $HT_DIVS;
+ $diff = time() - $timestamp;
+ $isfuture = ($diff < 0);
+ if($isfuture)
+ $diff = -$diff;
+
+ foreach($divs as $name => $delta) {
+ if($diff >= $delta)
+ break;
+ }
+
+ $unit = $delta ? $diff / $delta : $diff;
+ $ht = [
+ 'name' => $name.($unit > 1 ? 's' : ''),
+ 'unit' => $unit,
+ 'isfuture' => $isfuture
+ ];
+
+ if(!$fmts) {
+ global $HT_DEFAULT_FMTS;
+ $fmts = $HT_DEFAULT_FMTS;
+ }
+
+ $k = $ht['name'].'_'.(int)$ht['isfuture'];
+ return sprintf($fmts[$k], $ht['unit']);
+}
+
+function prepare_form() {
+ $key = key($_FILES);
+ if(!$key)
+ return;
+ $files = @$_FILES[$key];
+ if(@$_POST[$key]) {
+ $files = array_merge($files, $_POST[$key]);
+ unset($_POST[$key]);
+ }
+ $ret = [];
+ foreach($files as $k => $unused) {
+ foreach($files[$k] as $i => $v) {
+ if(!isset($ret[$i]))
+ $ret[$i] = [];
+ $ret[$i][$k] = $v;
+ }
+ }
+ $_FILES = $ret;
+}
+
+function buildhier($items, $pk = 'parent_id', $ck = 'id', $subk = 'children') {
+ $map = [];
+ foreach($items as $k => &$item) {
+ $c = $item[$ck];
+ $map[$c] = &$item;
+ }
+ unset($item);
+ foreach($items as $item) {
+ $p = $item[$pk];
+ if(!$p)
+ continue;
+ $c = $item[$ck];
+ if(!isset($map[$p][$subk]))
+ $map[$p][$subk] = [];
+ $map[$p][$subk][$c] = $map[$c];
+ $map[$c]['__rm'] = 1;
+ $map[$c] = &$map[$p][$subk][$c];
+ }
+ foreach($items as $k => $item)
+ if(@$item['__rm'])
+ unset($items[$k]);
+ return $items;
+}
+
+function viewinc($name, $data = []) {
+ if(!defined('PATH_VIEWS'))
+ die('PATH_VIEWS not defined');
+ foreach($data as $k => $v)
+ ${$k} = $v;
+ $view = PATH_VIEWS.'/'.implode('/', explode('.', $name)).'.php';
+ ob_start();
+ require($view);
+ $d = ob_get_contents();
+ ob_end_clean();
+ return $d;
+}
+
+function viewlinc($name, $data = [], $layout = null, $layoutdata = []) {
+ if(!$layout) {
+ if(!defined('DEFAULT_LAYOUT'))
+ die('DEFAULT_LAYOUT not defined');
+ $layout = DEFAULT_LAYOUT;
+ }
+ $layoutdata['content'] = viewinc($name, $data);
+ return viewinc($layout, $layoutdata);
+}
+
+function view($name, $data = [], $layout = null, $layoutdata = []) {
+ return viewlinc($name, $data, $layout, $layoutdata);
+}
+
+/* called by the app */
+function bored_run() {
+ echo route($_SERVER['REQUEST_METHOD'], (string)@explode('?', $_SERVER['REQUEST_URI'])[0]);
+}
+
+function bored_init() {
+ prepare_form();
+ session_start();
+ if(defined('DBHOST') && defined('DBUSER') && defined('DBPASS') && defined('DBNAME'))
+ dbopen(DBHOST, DBUSER, DBPASS, DBNAME);
+ register_shutdown_function(function() {
+ global $dblink;
+ if(!$dblink)
+ return;
+ mysqli_close($dblink);
+ session_write_close();
+ });
+}
+
+bored_init();
+?>