bored

A micro PHP framework
git clone git://git.bitsmanent.org/bored
Log | Files | Refs | README

bored.php (13722B)


      1 <?php
      2 
      3 $HT_DEFAULT_FMTS = [
      4 	'year_0' => "An year ago",
      5 	'years_0' => "%d years ago",
      6 	'year_1' => "An year from now",
      7 	'years_1' => "%d years from now",
      8 	'month_0' => "A month ago",
      9 	'months_0' => "%d months ago",
     10 	'month_1' => "A month from now",
     11 	'months_1' => "%d months from now",
     12 	'week_0' => "A week ago",
     13 	'weeks_0' => "%d weeks ago",
     14 	'week_1' => "A week from now",
     15 	'weeks_1' => "%d weeks from now",
     16 	'day_0' => "A day ago",
     17 	'days_0' => "%d days ago",
     18 	'day_1' => "A day from now",
     19 	'days_1' => "%d days from now",
     20 	'hour_0' => "An hour ago",
     21 	'hours_0' => "%d hours ago",
     22 	'hour_1' => "An hour from now",
     23 	'hours_1' => "%d hours from now",
     24 	'minute_0' => "A minute ago",
     25 	'minutes_0' => "%d minutes ago",
     26 	'minute_1' => "A minute from now",
     27 	'minutes_1' => "%d minutes from now",
     28 	'second_0' => "A second ago",
     29 	'seconds_0' => "%d seconds ago",
     30 	'second_1' => "A second from now",
     31 	'seconds_1' => "%d seconds from now",
     32 	'now_0' => "Just now",
     33 	'nows_0' => "Few seconds ago",
     34 	'now_1' => "Just now",
     35 	'nows_1' => "In a bit",
     36 ];
     37 
     38 $HT_DIVS = [
     39 	'year' => 60*60*24*365,
     40 	'month' => 60*60*24*30,
     41 	'week' => 60*60*24*7,
     42 	'day' => 60*60*24,
     43 	'hour' => 60*60,
     44 	'minute' => 60,
     45 	'second' => 30,
     46 	'now' => 0,
     47 ];
     48 
     49 $dblink = NULL;
     50 
     51 function route($method, $route, $func = NULL) {
     52         static $routes = [];
     53         if(!$func) {
     54                 $r = NULL;
     55                 $n = '';
     56                 $argv = [];
     57                 foreach(explode('/', $route) as $arg) {
     58                         $n .= ($n == '/' ? $arg : "/$arg");
     59                         if($r)
     60                                 $argv[] = $arg;
     61 			if(isset($routes[$method][$n])) {
     62                                 $r = $routes[$method][$n];
     63 				$argv = [];
     64 				if($route == $n)
     65 					break;
     66 			}
     67 			if(isset($routes[$method]["$n/"])) {
     68                                 $r = $routes[$method]["$n/"];
     69 				$argv = [];
     70 			}
     71                 }
     72 		if(!$r || (count($argv) < $r['mandatory']) || (count($argv) > $r['argc']))
     73 			exit(http_response_code(404));
     74                 return call_user_func_array($r['func'], $argv);
     75         }
     76         $name = [];
     77         $argc = 0;
     78         $mandatory = 0;
     79         foreach(explode('/', $route) as $arg) {
     80                 switch(@$arg[0]) {
     81 		case '!': ++$argc; ++$mandatory; break;
     82 		case '?': ++$argc; break;
     83 		default: $name[] = $arg; break;
     84                 }
     85         }
     86         $name = implode('/', $name);
     87 	if($argc)
     88 		$name .= '/';
     89         $routes[$method][$name] = ['func' => $func, 'argc' => $argc, 'mandatory' => $mandatory];
     90         return 0;
     91 }
     92 
     93 function dbopen($host, $user, $pass, $dbname) {
     94 	global $dblink;
     95 
     96 	if(!($r = @mysqli_connect($host, $user, $pass, $dbname)))
     97 		die('database error');
     98 	$dblink = $r;
     99 	return $r;
    100 }
    101 
    102 function dbquery($sql, $limit = 1, $multi = false) {
    103 	global $dblink;
    104 
    105 	if(!is_string($sql) || !($sql = trim($sql)))
    106 		return false;
    107 
    108 	$ck = md5($sql);
    109 	if(($ret = cache($ck)))
    110 		return $ret;
    111 
    112 	$cn = "$sql-$limit-$multi";
    113 	$cmd = strtolower(substr($sql, 0, strpos($sql, ' ')));
    114 	if($cmd == 'select') {
    115 		if($limit == -1)
    116 			$limit = '18446744073709551615';
    117 		$sql .= " limit $limit";
    118 	}
    119 
    120 	$res = $multi ? mysqli_multi_query($dblink, $sql) : mysqli_query($dblink, $sql);
    121 	if(!$res)
    122 		return false;
    123 	if($multi) {
    124 		$ret = [];
    125 		for($res = mysqli_use_result($dblink); $res; $res = mysqli_store_result($dblink)) {
    126 			$r = [];
    127 			while(($t = mysqli_fetch_assoc($res)))
    128 				$r[] = $t;
    129 			$ret[] = $r;
    130 			mysqli_free_result($res);
    131 			mysqli_next_result($dblink);
    132 		}
    133 		return $ret;
    134 	}
    135 	switch($cmd) {
    136 	case 'select':
    137 	case 'call':
    138 		$ret = [];
    139 		while(($t = mysqli_fetch_assoc($res)))
    140 			$ret[] = $t;
    141 		break;
    142 	case 'insert':
    143 		$ret = mysqli_insert_id($dblink);
    144 		if(!$ret)
    145 			$ret = true;
    146 		break;
    147 	case 'delete':
    148 		$ret = mysqli_affected_rows($dblink);
    149 		break;
    150 	default:
    151 		$ret = $res;
    152 		break;
    153 	}
    154 	return ($limit == 1 && is_array($ret) ? $ret[0] : $ret);
    155 }
    156 
    157 function dbping($l = NULL) {
    158 	global $dblink;
    159 
    160         if(!$l)
    161                 $l = $dblink;
    162 	return mysqli_ping($l);
    163 }
    164 
    165 function dberr($l = NULL) {
    166 	global $dblink;
    167 
    168         if(!$l)
    169                 $l = $dblink;
    170         return mysqli_error($l);
    171 }
    172 
    173 function dbin($s) {
    174 	global $dblink;
    175 
    176 	if($s === NULL)
    177 		return "NULL";
    178 	if($s == "CURRENT_TIMESTAMP")
    179 		return $s;
    180 	return "'".mysqli_real_escape_string($dblink, $s)."'";
    181 }
    182 
    183 function dbids() {
    184 	/* Assuming transactional DB (InnoDB) */
    185 	$sql = "select LAST_INSERT_ID() as s,LAST_INSERT_ID() + ROW_COUNT() - 1 as e";
    186 	$t = dbquery($sql)[0];
    187 	return range($t["s"], $t["e"]);
    188 }
    189 
    190 function dbins($tbl, $items) {
    191 	$values = [];
    192 	$fields = array_keys($items[0]);
    193 	foreach($items as $item) {
    194 		$vals = [];
    195 		foreach($item as $k => $v)
    196 			$vals[] = dbin($v);
    197 		$values[] = "(".implode(',', $vals).")";
    198 	}
    199 	$sql = "insert into `$tbl` (".implode(',', $fields).") values ".implode(',', $values);
    200 	return dbquery($sql);
    201 }
    202 
    203 function dbupd($tbl, $items, $pk = "id") {
    204 	$when = [];
    205 
    206 	/* collects longest available set of keys */
    207 	$keys = [];
    208 	foreach($items as $item)
    209 		foreach($item as $k => $v)
    210 			$keys[$k] = 1;
    211 	$keys = array_keys($keys);
    212 
    213 	foreach($items as $item) {
    214 		$pv = $item[$pk];
    215 		foreach($keys as $k) {
    216 			if($k == $pk)
    217 				continue;
    218 			$v = isset($item[$k]) ? dbin($item[$k]) : "`${k}`";
    219 			if(!isset($when[$k]))
    220 				$when[$k] = [];
    221 			$when[$k][] = "when '$pv' then $v";
    222 		}
    223 	}
    224 	$sets = [];
    225 	foreach($when as $k => $w)
    226 		$sets[] = "$k = case `$pk` ".implode(' ', $w)." else `$k` end";
    227 	$sql = "update `$tbl` set ".implode(',', $sets);
    228 	return dbquery($sql);
    229 }
    230 
    231 function dbdel($tbl, $ids, $pk = "id") {
    232 	$sql = "delete from `$tbl` where `$pk` IN(".implode(',', $ids).")";
    233 	return dbquery($sql);
    234 }
    235 
    236 function sizefitbox($src, $dst) {
    237 	list($ow, $oh) = explode('x', $src);
    238 	list($tow, $toh) = explode('x', $dst);
    239 
    240 	$rw = $tow / $ow;
    241 	$rh = $toh / $oh;
    242 	$ratio = min($rw, $rh);
    243 
    244 	$w = $ow * $ratio;
    245 	$h = $oh * $ratio;
    246 
    247 	$w = (int)$w;
    248 	$h = (int)$h;
    249 
    250 	return "${w}x${h}";
    251 }
    252 
    253 function imgresize($src, $saveas, $whxy = '64x64-0,0', $opts = NULL) {
    254 	$in = NULL;
    255 	$out = NULL;
    256 	$transparency = false;
    257 
    258 	$t = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $src);
    259 	$ext = explode('/', $t)[1];
    260 	//$ext = strtolower((string)@pathinfo($src)['extension']);
    261 
    262 	switch($ext) {
    263 	case 'jpg':
    264 	case 'jpeg':
    265 		$in = 'imagecreatefromjpeg';
    266 		$out = 'imagejpeg';
    267 		break;
    268 	case 'gif':
    269 		$in = 'imagecreatefromgif';
    270 		$out = 'imagegif';
    271 		/* imagegif() doesn't take a third param */
    272 		if($opts !== NULL)
    273 			$opts = NULL;
    274 		break;
    275 	case 'bmp':
    276 		$in = 'imagecreatefromwbmp';
    277 		$out = 'imagewbmp';
    278 		break;
    279 	case 'png':
    280 		$in = 'imagecreatefrompng';
    281 		$out = 'imagepng';
    282 		$transparency = true;
    283 		break;
    284 	default: /* unsupported image */ return -1;
    285 	}
    286 	if(!($oi = $in($src)))
    287 		return 2;
    288 
    289 	$t = explode('-', $whxy);
    290 	$wh = $t[0];
    291 	$wh = explode('x', $wh);
    292 	$xy = isset($t[1]) ? $t[1] : '0,0';
    293 	$xy = explode(',', $xy);
    294 
    295 	$w = (int)@$wh[0];
    296 	$h = isset($wh[1]) ? $wh[1] : $w;
    297 	$x = (int)@$xy[0];
    298 	$y = (int)@$xy[1];
    299 
    300 	list($iw, $ih) = getimagesize($src);
    301 	$ratio = [$iw / $ih, $w / $h];
    302 	if($x != 0 || $y != 0) {
    303 		$crop = imagecreatetruecolor($w, $h);
    304 		$cropW = $w;
    305 		$cropH = $h;
    306 
    307 		imagecopy($crop, $oi, 0, 0, (int)$x, (int)$y, $w, $h);
    308 		if($transparency) {
    309 			imagealphablending($crop, false);
    310 			imagesavealpha($crop, true);  
    311 		}
    312 	}
    313 	else if($ratio[0] != $ratio[1]) {
    314 		$scale = min((float)($iw / $w), (float)($ih / $h));
    315 		$cropX = (float)($iw - ($scale * $w));
    316 		$cropY = (float)($ih - ($scale * $h));
    317 		$cropW = (float)($iw - $cropX);
    318 		$cropH = (float)($ih - $cropY);
    319 		$crop = imagecreatetruecolor($cropW, $cropH);
    320 		if($transparency) {
    321 			imagealphablending($crop, false);
    322 			imagesavealpha($crop, true);  
    323 		}
    324 		imagecopy($crop, $oi, 0, 0, (int)($cropX / 2), (int)($cropY / 2), $cropW, $cropH);
    325 	}
    326 	$ni = imagecreatetruecolor($w, $h);
    327 	if($transparency) {
    328 		imagealphablending($ni, false);
    329 		imagesavealpha($ni, true);  
    330 	}
    331 	if(isset($crop)) {
    332 		imagecopyresampled($ni, $crop, 0, 0, 0, 0, $w, $h, $cropW, $cropH);
    333 		imagedestroy($crop);
    334 	}
    335 	else {
    336 		imagecopyresampled($ni, $oi, 0, 0, 0, 0, $w, $h, $iw, $ih);
    337 	}
    338 	imagedestroy($oi);
    339 	if($opts !== NULL)
    340 		$r = $out($ni, $saveas, $opts);
    341 	else
    342 		$r = $out($ni, $saveas);
    343 	imagedestroy($ni);
    344 	if($r === false)
    345 		return 1;
    346 	return 0;
    347 }
    348 
    349 function jsonerr($v) {
    350 	json('ko', $v);
    351 }
    352 
    353 function jsonok($v = "") {
    354 	json('ok', $v);
    355 }
    356 
    357 function json($state, $res) {
    358 	die(json_encode(['state' => $state, 'res' => $res]));
    359 }
    360 
    361 function sendmail($from, $to, $subj, $message, $files = NULL) {
    362 	ini_set('sendmail_from', $from);
    363 	$headers = "From: $from\n" .
    364 		   "Return-Path: <$from>\r\n" .
    365 		   "MIME-Version: 1.0\n";
    366 	if(!(is_array($files) || count($files))) {
    367 		$headers .= "Content-Type: text/html; charset=\"UTF-8\"\n" .
    368 			    "Content-Transfer-Encoding: 7bit\n\n";
    369 	}
    370 	else {
    371 		$semi_rand = md5(time());
    372 		$mime_boundary = "==Multipart_Boundary_x{$semi_rand}x";
    373 		$headers .= "Content-Type: multipart/mixed; boundary=\"{$mime_boundary}\"\n";
    374 		$message = "--{$mime_boundary}\n" .
    375 			   "Content-Type: text/plain; charset=\"UTF-8\"\n" .
    376 			   "Content-Transfer-Encoding: 7bit\n\n" .
    377 			   $message . "\n\n";
    378 		foreach($files as $fn) {
    379 			$f = basename($fn);
    380 			if(!is_file($fn))
    381 				continue;
    382 			$data = chunk_split(base64_encode(file_get_contents($fn)));
    383 			$message .= "--{$mime_boundary}\n" .
    384 				    "Content-Type: application/octet-stream; name=\"$f\"\n" .
    385 				    "Content-Description: $f\n" .
    386 				    "Content-Disposition: attachment; filename=\"$f\"; size=".filesize($fn).";\n" .
    387 				    "Content-Transfer-Encoding: base64\n\n" . $data . "\n\n";
    388 		}
    389 		$message .= "--{$mime_boundary}--";
    390 	}
    391 	return @mail($to, $subj, $message, $headers);
    392 }
    393 
    394 function store(&$to, $k, $v) {
    395 	$r = isset($to[$k]) ? $to[$k] : NULL;
    396 	if($v !== NULL)
    397 		$to[$k] = $v;
    398 	return $r;
    399 }
    400 
    401 function cache($k, $v = NULL) {
    402 	static $__cache = [];
    403 	return store($__cache, $k, $v);
    404 }
    405 
    406 function sess($k, $v = NULL) {
    407 	$r = isset($_SESSION[$k]) ? $_SESSION[$k] : NULL;
    408 	return store($_SESSION, $k, $v);
    409 }
    410 
    411 function pre($d) {
    412 	echo '<pre>'.print_r($d,1).'</pre>';
    413 }
    414 
    415 function humanstime($timestamp, $fmts = NULL) {
    416 	global $HT_DEFAULT_FMTS, $HT_DIVS;
    417 
    418 	$divs = $HT_DIVS;
    419 	$diff = time() - $timestamp;
    420 	$isfuture = ($diff < 0);
    421 	if($isfuture)
    422 		$diff = -$diff;
    423 	foreach($divs as $name => $delta)
    424 		if($diff >= $delta)
    425 			break;
    426 	$unit = (int)($delta ? $diff / $delta : $diff);
    427 	$ht = [
    428 		'name' => $name.($unit > 1 ? 's' : ''),
    429 		'unit' => $unit,
    430 		'isfuture' => $isfuture
    431 	];
    432 	if(!$fmts)
    433 		$fmts = $HT_DEFAULT_FMTS;
    434 	$k = $ht['name'].'_'.(int)$ht['isfuture'];
    435 	return sprintf($fmts[$k], $ht['unit']);
    436 }
    437 
    438 function curl_post($uri, $curlopts = []) {
    439 	$c = curl_init();
    440 
    441 	curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
    442 	curl_setopt($c, CURLOPT_URL, $uri);
    443 	curl_setopt($c, CURLOPT_POST, true);
    444 	if($curlopts)
    445 		foreach($curlopts as $k => $v)
    446 			curl_setopt($c, $k, $v);
    447 	$ret = curl_exec($c);
    448 	curl_close($c);
    449 	return $ret;
    450 }
    451 
    452 function prepare_form() {
    453 	$ret = [];
    454 	$idx = 0;
    455 	foreach($_FILES as $grp => $fds) {
    456 		foreach($fds as $k => $vals) {
    457 			foreach($vals as $i => $txt) {
    458 				$t = $idx + $i;
    459 				if(!isset($ret[$t])) {
    460 					$ret[$t] = [];
    461 					$ret[$t]["grp"] = $grp;
    462 					/* related info */
    463 					$ret[$t]["info"] = [];
    464 					foreach((array)@$_POST[$grp] as $ik => $info) {
    465 						if(!isset($info[$t])) {
    466 							/* XXX warn the user: possible fields mismatch */
    467 							continue;
    468 						}
    469 						$ret[$t]["info"][$ik] = $info[$t];
    470 					}
    471 				}
    472 				$ret[$t][$k] = $txt;
    473 			}
    474 		}
    475 		unset($_POST[$grp]); /* get rid of files-related POST data */
    476 		$idx += count($ret);
    477 	}
    478 	$_FILES = $ret;
    479 }
    480 
    481 function buildhier($items, $pk = 'parent_id', $ck = 'id', $subk = 'children') {
    482 	$map = [];
    483 	foreach($items as $k => &$item) {
    484 		$c = $item[$ck];
    485 		$map[$c] = &$item;
    486 	}
    487 	unset($item);
    488 	foreach($items as $item) {
    489 		$p = $item[$pk];
    490 		if(!$p)
    491 			continue;
    492 		$c = $item[$ck];
    493 		if(!isset($map[$p][$subk]))
    494 			$map[$p][$subk] = [];
    495 		$map[$p][$subk][$c] = $map[$c];
    496 		$map[$c]['__rm'] = 1;
    497 		$map[$c] = &$map[$p][$subk][$c];
    498 	}
    499 	foreach($items as $k => $item)
    500 		if(@$item['__rm'])
    501 			unset($items[$k]);
    502 	return $items;
    503 }
    504 
    505 /* Note: all variables here are accessible by the view (along with globals) */
    506 function viewinc($incname, $viewdata = []) {
    507 	if(!defined("VIEWDIR"))
    508 		die("VIEWDIR not defined");
    509         $viewfile = VIEWDIR.'/'.implode('/', explode('.', $incname)).'.php';
    510 	if(!file_exists($viewfile))
    511 		return NULL;
    512 
    513 	/* only provide specified variable names */
    514 	if($viewdata)
    515 		foreach($viewdata as $k => $v)
    516 			${$k} = $v;
    517 	unset($viewdata);
    518 	unset($incname);
    519 
    520         ob_start();
    521         require($viewfile);
    522         $d = ob_get_contents();
    523         ob_end_clean();
    524         return $d;
    525 }
    526 
    527 function lviewinc($name, $data = [], $layout = NULL, $layoutdata = []) {
    528 	if(!$layout) {
    529 		if(!defined('DEFAULT_LAYOUT'))
    530 			die('DEFAULT_LAYOUT not defined');
    531 		$layout = DEFAULT_LAYOUT;
    532 	}
    533 	$content = viewinc($name, $data);
    534 	if($content === NULL)
    535 		return NULL;
    536 	return viewinc($layout, [
    537 		"name" => $name,
    538 		"content" => $content,
    539 		"paths" => array_filter(explode('/', $_SERVER["REQUEST_URI"]), function($dir) {
    540 			return trim($dir);
    541 		})
    542 	]);
    543 }
    544 
    545 function view($name, $data = [], $layout = NULL, $layoutdata = NULL) {
    546 	if(!$layoutdata)
    547 		$layoutdata = $data;
    548 	return lviewinc($name, $data, $layout, $layoutdata);
    549 }
    550 
    551 function bored_init() {
    552 	setlocale(LC_CTYPE, "");
    553 	prepare_form();
    554 	session_start();
    555 	if(defined('DBHOST') && defined('DBUSER') && defined('DBPASS') && defined('DBNAME'))
    556 		dbopen(DBHOST, DBUSER, DBPASS, DBNAME);
    557 	register_shutdown_function(function() {
    558 		global $dblink;
    559 
    560 		if($dblink)
    561 			mysqli_close($dblink);
    562 		session_write_close();
    563 	});
    564 }
    565 
    566 function bored_run($noinit = 0) {
    567 	if(!$noinit)
    568 		bored_init();
    569 	echo route($_SERVER['REQUEST_METHOD'], (string)@explode('?', $_SERVER['REQUEST_URI'])[0]);
    570 }
    571 
    572 ?>