$v) { if (substr($k, 0, 5) == 'HTTP_') $headers[str_replace('_','-',(substr($k,5)))] = $v; } $method = strtolower(filter_input(INPUT_SERVER,'REQUEST_METHOD')); return array( 'method' => /*filter('request.method',*/$method/*)*/, 'uri' => filter('request.uri',$uri), 'query' => filter_input_array(INPUT_GET), 'post' => filter_input_array(INPUT_POST), 'cookies' => filter_input_array(INPUT_COOKIE), 'headers' => $headers, ); } function quit($status=500){ if (function_exists('http_response_code')){ http_response_code($status); } else { header("Status: $status",true,$status); } exit; } /** * RESPONSE */ function response(/* ... */){ static $headers = array(), $fragments = array(); $params = func_get_args(); $action = count($params) ? strtolower(array_shift($params)) : null; if (is_null($action)){ return array( 'headers' => array_values($headers), 'body' => implode('',$fragments), ); } else { switch($action){ case 'header': $headers[$params[0]] = "{$params[0]}: {$params[1]}"; break; case 'delete': $headers = $fragments = array(); break; case 'clear': case 'clean': $fragments = array(); break; case 'append': $fragments[] = $params[0]; break; case 'start': ob_start(); break; case 'stop': if (ob_get_level() > 1) { $fragments[] = ob_get_contents(); ob_end_clean(); } break; case 'append': $fragments[] = $params[0]; break; } } } /** * EVENTS */ function event($action, $event, $callback){ static $events = array(); switch(strtolower($action)){ case 'on' : $events[$event][] = $callback; break; case 'off' : if ($callback) { unset($events[$event][$callback]); } else { unset($events[$event]); } break; case 'trigger' : if (isset($events[$event]) && isset($events[$event])) { if (!is_array($callback)) $callback = array(); foreach($events[$event] as $handler) call_user_func_array($handler, $callback); } break; } } /** * EMAIL */ function email($from, $to, $subject, $body, array $extra_headers=array()){ $time = $_SERVER['REQUEST_TIME']; $headers = array(); foreach(array_merge(array( "From" => "{$from}", ),array( "Reply-To" => "{$from}", "Return-Path" => "{$from}", ), $extra_headers, array( "Content-type" => "text/html; charset=\"UTF-8\"", "Content-Transfer-Encoding" => "7bit", "Date" => "" . date('r', $time), "Message-ID" => "<$time".md5($time)."@".$_SERVER['SERVER_NAME'].">", "MIME-Version" => "1.0", )) as $k => $v) $headers[] = "{$k}: {$v}"; $head = implode("\r\n",$headers); die($head); $subject = '=?UTF-8?B?' . base64_encode(str_replace("\n", '', $subject)) . '?='; foreach((array)$to as $recipient){ $_to = str_replace("\n", '', $recipient); $results[$_to] = mail($_to,$subject,$body,$head); } return $results; } /** * FILTERS */ function filter($name, $callback = null){ static $filters = array(); if (is_callable($callback)) { // Set filter handler $filters[$name][] = $callback; } else { // Run filter $value = $callback; if (empty($filters[$name])) return $value; foreach($filters[$name] as $handler) $value = call_user_func($handler, $value); return $value; } } function on($event, $callback){ event('on', $event, $callback); } function off($event, $callback = null){ event('off', $event, $callback); } function trigger($event /* ... */){ event('trigger', $event, array_slice(func_get_args(), 1)); } function triggerOnce($event /* ... */){ event('trigger', $event, array_slice(func_get_args(), 1)); event('off', $event, null); } /** * TEMPLATE */ function template($name, $params=array()){ static $templates = array(); $use_cache = !!options('templates.cache.enabled'); $template_dir = rtrim(options('templates.dir')?:__DIR__.'/templates','/'); $name = trim($name,'/'); $template_file = "$template_dir/$name.html"; if ($use_cache) { $cache_dir = rtrim(options('templates.cache.dir')?:__DIR__.'/cache','/'); if (!is_dir($cache_dir)) @mkdir($cache_dir); $template_cache = "$cache_dir/$name.php"; if (file_exists($template_cache)) { $templates[$template_file] = file_get_contents($template_cache); } } if (!isset($templates[$template_file])) { $getParam = function($tok){return function_exists(trim(strtok($tok,'(')))?$tok:'@$'.trim(str_replace('.','->',$tok));}; $compiled = array(); $state = 'html'; $tokens = preg_split('~({{|}}|{%|%}|{#|#}|{&|&})~m',file_get_contents($template_file),-1,PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); foreach ($tokens as $tok){ if ($state == 'skip') { if ($tok == '#}') $state = 'html'; continue; } switch ($tok) { case '{{': $state = 'echo'; $compiled[] = ''; break; case '{#': $state = 'skip'; break; case '#}': $state = 'html'; break; case '{%': $state = 'code'; $compiled[] = ''; break; case '{&': $state = 'php'; $compiled[] = ''; break; default: switch ($state) { case 'skip': break; case 'code': $keywords = array_filter(preg_split('~\s+~',$tok)); $statement = array_shift($keywords); switch ($statement) { case 'for': if (strpos($keywords[2],'..')!==false){ $what = 'range('.preg_replace('~\s*\.\.\s*~', ',', $keywords[2]).')'; } else { $what = $getParam($keywords[2]); } $compiled[] = 'foreach('.$what.'?:array() as $'.$keywords[0].'){'; break; case 'end': $compiled[] = '}'; break; } break; case 'echo': $state = 'html'; $compiled[] = $getParam($tok); break; case 'php': $compiled[] = trim($tok); break; case 'html': $compiled[] = $tok; break; } break; } } $templates[$template_file] = implode('',$compiled); if ($use_cache) { file_put_contents($template_cache, $templates[$template_file]); } } $source = $templates[$template_file]; return call_user_func(function() use ($source, $params){ extract($params); ob_start(); eval('?>'.$source); $___BUFF___ = ob_get_contents(); ob_end_clean(); return $___BUFF___; }); } /** * ROUTES */ register_shutdown_function(function(){ route(); $response = response(); foreach ($response['headers'] as $value) header($value,true); echo $response['body']; trigger('app.exit'); }); function get($path, $callback){ route('get',$path,$callback); } function post($path, $callback){ route('post',$path,$callback); } function put($path, $callback){ route('put',$path,$callback); } function delete($path, $callback){ route('delete',$path,$callback); } function route($method='@', $path='', $callback=null){ static $routes = array(); if ($method == '@') { $request = request(); if (empty($routes[$request['method']])) { trigger(404); quit(404); } foreach ($routes[$request['method']] as $pattern => $route) { if (preg_match('#^/'.$pattern.'/?$#',$request['uri'],$captures)){ array_shift($captures); trigger('route.before',$route,$captures); response('start'); $results = call_user_func_array($route['callback'], $captures); response('stop'); if (is_array($results) || is_object($results)) { response('delete'); response('header','Content-Type','application/json'); response('append',json_encode($results, JSON_NUMERIC_CHECK)); } else { echo $results; } trigger('route.after',$route); return; } } trigger(404) or quit(404); } else { if($path) { $method = strtolower(trim($method)); $path = preg_replace_callback('#(:\w+)#', function($m){ return '([^/]+)'; }, str_replace('.','\.',trim($path,'/'))); $routes[$method][$path] = array( 'callback' => $callback ?: function(){}, ); } } } /** * ========================== * DATABASE * ========================== */ /** * Database module */ function database(/* ... */){ $args = func_get_args(); $action = array_shift($args); switch ($action) { case 'init': // Prepare new database service service('database', function() use ($args) { $pdo = new PDO( $args[0], isset($args[1])?$args[1]:'', isset($args[2])?$args[2]:'', array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, ) ); trigger('database.init',$pdo); return $pdo; }); break; } } /** * Standard database uses in-memory sqlite */ service('database', function(){ $pdo = new PDO('sqlite::memory:',array( PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => false, )); trigger('database.init',$pdo); return $pdo; }); /** * Fetch all results from sql query, via callback or as a whole. */ function sql_each($sql, $callback, $params=array()){ try { $db = service('database'); $statement = $db->prepare($sql); $statement->execute($params); if (is_callable($callback)){ while ($row = $statement->fetchObject()){ call_user_func($callback, $row); } } else { return $statement->fetchAll(PDO::FETCH_CLASS); } } catch (PDOException $e) { trigger('database.error',$e,$sql,$params); return false; } } /** * Fetch single row from sql results */ function sql_row($sql, $params=array()){ try { $db = service('database'); $statement = $db->prepare($sql); $statement->execute($params); return $statement->fetchObject(); } catch (PDOException $e) { trigger('database.error',$e,$sql,$params); return false; } } /** * Fetch column from sql results */ function sql_value($sql, $params=array(), $column=0){ try { $db = service('database'); $statement = $db->prepare($sql); $statement->execute($params); return $statement->fetchColumn($column); } catch (PDOException $e) { trigger('database.error',$e,$sql,$params); return false; } } /** * Execute raw sql code */ function sql($sql, $params=array()){ try { $db = service('database'); $statement = $db->prepare($sql); return $statement->execute($params); } catch (PDOException $e) { trigger('database.error',$e,$sql,$params); return false; } }