* @copyright creative commons attribution-shareAlike 3.0 unported * @license http://creativecommons.org/licenses/by-sa/3.0/ * @version 2.01.00 */ class qoob { /** * famework info */ const PACKAGE='open qoob', VERSION='2.01.00'; /** * error constants */ const E_Handler='Missing Callback', E_Pattern='Invalid routing pattern: %s', E_Fatal='Fatal error: %s', E_Open='Unable to open: %s', E_Loading='Failed loading: %s', E_Request='Invalid reqest type: %s', E_Routes='No routes specified', E_Method='Invalid method: %s', E_Gateway='Not implemented: %s'; /** * request types */ const REQUESTS='SYNC|AJAX'; /** * http verbs */ const VERBS='GET|HEAD|POST|PUT|DELETE'; /** * http status codes * @see http://www.rfc-editor.org/rfc/rfc2616.txt */ const // informational HTTP_100='Continue', HTTP_101='Switching Protocols', // successful HTTP_200='OK', HTTP_201='Created', HTTP_202='Accepted', HTTP_203='Non-Authorative Information', HTTP_204='No Content', HTTP_205='Reset Content', HTTP_206='Partial Content', // redirection HTTP_300='Multiple Choices', HTTP_301='Moved Permanently', HTTP_302='Found', HTTP_303='See Other', HTTP_304='Not Modified', HTTP_305='Use Proxy', HTTP_307='Temporary Redirect', // client error HTTP_400='Bad Request', HTTP_401='Unauthorized', HTTP_402='Payment Required', HTTP_403='Forbidden', HTTP_404='Not Found', HTTP_405='Method Not Allowed', HTTP_406='Not Acceptable', HTTP_407='Proxy Authentication Required', HTTP_408='Request Timeout', HTTP_409='Conflict', HTTP_410='Gone', HTTP_411='Length Required', HTTP_412='Precondition Failed', HTTP_413='Request Entity Too Large', HTTP_414='Request-URI Too Long', HTTP_415='Unsupported Media Type', HTTP_416='Requested Range Not Satisfiable', HTTP_417='Exception Failed', // server error HTTP_500='Internal Server Error', HTTP_501='Not Implemented', HTTP_502='Bad Gateway', HTTP_503='Service Unavailable', HTTP_504='Gateway Timeout', HTTP_505='HTTP Version Not Supported'; /** * status * echo http header and return status message * @param int $code status code * @return string status message */ function status($code) { // account for system thrown exceptions if(!defined('self::HTTP_'.$code)) { $code = 500; } // no commandline headers if (PHP_SAPI!='cli') { header('HTTP/1.1 '.$code); header('X-Powered-By: '.self::PACKAGE.'/'.self::VERSION); } library::set('STATUS.code', $code); return @constant('self::HTTP_'.$code); } /** * config * parse a php ini into the library. autoload classes if defined. * @param string $file file name */ function config($file) { if(!file_exists($file)) { throw new Exception(sprintf(self::E_Loading, $file), 500); } $ini = parse_ini_file($file, true); if(count($ini)>0) { foreach ($ini as $key => $val) { if(is_array($val)) { if(strtolower($key)=='autoload') { foreach ($val as $k => $v) { $this->load($v); } } foreach ($val as $k => $v) { library::set('CONFIG.'.strtoupper($key).'.'.$k, $v); } } else { library::set('CONFIG.'.$key, $val); } } } } /** * load * load namespace aware classes into the framework * @param string $class class name */ function load($class) { // nullbyte poisoning check $class = str_replace(chr(0), '', $class); if(class_exists($class)) { // remove namespace from class name $name = explode('\\', $class); $name = $name[count($name)-1]; if(!library::exists($name)) { // create class and set a reference to it library::set('CLASS.'.$name, new $class); $this->$name = library::get('CLASS.'.$name); } } else { throw new Exception(sprintf(self::E_Loading, $class), 500); } } /** * route * add a route pattern * @param string $pattern route * @param mixed $handler closure function or class->method reference */ function route($pattern, $handler) { if(empty($handler)) { throw new Exception(self::E_Handler, 500); } $parts = explode(' ', trim($pattern)); foreach ($this->split($parts[0]) as $verb) { if (!preg_match('/'.self::VERBS.'/', strtoupper($verb))) { throw new Exception(sprintf(self::E_Gateway, $verb), 500); } if(!isset($parts[1])) { throw new Exception(sprintf(self::E_Pattern, $pattern), 500); } $type = isset($parts[2])?str_replace(array('[',']'), '', strtoupper($parts[2])):'SYNC'; if (!preg_match('/'.self::REQUESTS.'/', $type)) { throw new Exception(sprintf(self::E_Request, $type), 500); } library::set( 'ROUTES', array( 'verb' => strtoupper($verb), 'type' => $type, 'handler' => $handler, 'pattern' => rtrim($parts[1], '/') ) ); } } /** * parse routes * mine the current request against the routes in the library */ function parseRoutes() { if(isset($this->benchmark)) { $this->benchmark->mark('parseStart'); } library::set('QOOB.url', 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); library::set('QOOB.domain', 'http://'.dirname($_SERVER["HTTP_HOST"].$_SERVER["SCRIPT_NAME"])); library::set('REQUEST.verb', $_SERVER['REQUEST_METHOD']); library::set('REQUEST.uri', rtrim(str_replace('?'.$_SERVER['QUERY_STRING'], '', str_replace(library::get('QOOB.domain'), '', library::get('QOOB.url'))), '/')); library::set('REQUEST.ajax', (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])&&strtolower($_SERVER['HTTP_X_REQUESTED_WITH'])=='xmlhttprequest')?'AJAX':'SYNC'); $found = false; foreach(library::get('ROUTES') as $route) { // regular expression to identify uri requests formatted like '/users/:uid/posts/:pid' $pattern = "@^".preg_replace('/\\\:[a-zA-Z0-9\_\-]+/', '([a-zA-Z0-9\-\_]+)', preg_quote($route['pattern']))."$@D"; $args = array(); // check if the current request matches the expression if(library::get('REQUEST.verb') == $route['verb'] && preg_match($pattern, library::get('REQUEST.uri'), $matches)) { if($route['type'] == library::get('REQUEST.ajax')) { // remove the first match array_shift($matches); $found = true; // correlate route regex with uri parameters preg_match_all('/\\\:[a-zA-Z0-9\_\-]+/', preg_quote($route['pattern']), $names); for($i=0;$iparseRequest(library::get('REQUEST.verb')); $args = array_merge_recursive($requests, $args); break; } } } if(isset($this->benchmark)) { $this->benchmark->mark('parseEnd'); } if(!$found) { throw new Exception(self::HTTP_404, 404); } else { $this->call($route, $args); } } /** * call * execute a route handler * @param array $route route information * @param array $args url arguments */ function call($route, $args) { if(isset($this->benchmark)) { $this->benchmark->mark('callStart'); } // closure style if(is_callable($route['handler'])) { call_user_func_array($route['handler'], array($args)); } // class creation if(is_string($route['handler']) && preg_match('/(.+)\h*(->|::)\h*(.+)/s', $route['handler'], $parts)) { if (!class_exists($parts[1]) || !method_exists($parts[1], $parts[3])) { throw new Exception(self::HTTP_404, 404); } call_user_func_array(array(new $parts[1], $parts[3]), array($args)); } if(isset($this->benchmark)) { $this->benchmark->mark('callEnd'); } } /** * parse request * gets request arguments from the correct protocol for the given http verb * @param string $verb the http verb */ function parseRequest($verb) { $args = array(); switch ($verb) { case 'GET': case 'HEAD': $args = $_GET; break; case 'POST': $args = $_POST; break; case 'PUT': case 'DELETE': parse_str(file_get_contents('php://input'), $args); break; } return $args; } /** * run * framework execution */ function run() { $this->parseRoutes(); } /** * split * seperate a comma, semi-colon, or pipe delimited string * @param string $str * @param array $strs */ function split($str) { return array_map('trim', preg_split('/[,;|]/',$str,0,PREG_SPLIT_NO_EMPTY)); } /** * fatal error handler * gracefully respond to fatal errors. */ function fatal_handler() { if(@is_array($err = @error_get_last())) { $code = isset($err['type']) ? $err['type'] : 0; if($code>0) { $this->error_handler( $code, isset($err['message']) ? $err['message'] : '', isset($err['file']) ? $err['file'] : '', isset($err['line']) ? $err['line'] : '' ); } } } /** * exception handler * gracefully respond to exceptions. * @param object $exc the php exception object */ function exception_handler($exc) { $this->error_handler( $exc->getCode(), $exc->getMessage(), $exc->getFile(), $exc->getLine(), $exc->getTrace() ); } /** * error handler * gracefully respond to errors * @param int $num error code * @param string $str error message * @param string $file the file throwing the error * @param int $line line number in the file throwing the error * @param array $ctx error context */ function error_handler($num, $str, $file, $line, $ctx=array()) { // remove php error output if(ob_get_length()>0){ @ob_end_clean(); } $code = $this->status($num); if(isset($this->logz)) { $this->logz->changeFile('error.log'); $this->logz->write('error: '.$num.' - '.$str.' [file] '.$file.' [line] '.$line.' [context] '.trim(preg_replace('/\s+/', ' ', print_r($ctx, true)))); } $this->load('app\error'); $this->error->render($code, $num, $str, $file, $line, $ctx); die(); } /** * open qoob * get the singleton reference to the open qoob framework * @return class qoob */ static function open() { if (!library::exists('CLASS.'.$class=__CLASS__)) { library::set('CLASS.'.$class, new $class); } return library::get('CLASS.'.$class); } /** * clone * disabled */ private function __clone() {} /** * constructor * bootstraps the framework/core utils. initializes autoloading classes. set default variables. */ private function __construct() { //support non-namespaced classes set_include_path( implode( PATH_SEPARATOR, array( get_include_path(), dirname(__FILE__).DIRECTORY_SEPARATOR.'app' ) ) ); spl_autoload_extensions(".php,.inc"); spl_autoload_register(function($class) { $class = (string) str_replace('\\', DIRECTORY_SEPARATOR, $class); if(stream_resolve_include_path($class.'.php') !== false) { require $class.'.php'; } }); library::set('STATUS.code', 200); library::set('UI.dir', realpath('ui')); library::set('TMP.dir', realpath('tmp')); library::set('CONFIG.debug', false); } /** * destructor */ public function __destruct() {} } //_________________________________________________________________________ // object library /** * library * singleton object library * * @author xero harrison * @copyright creative commons attribution-shareAlike 3.0 unported * @license http://creativecommons.org/licenses/by-sa/3.0/ * @version 2.22.01 */ final class library { private static $catalog; static function expose() { return print_r(self::$catalog, true); } static function exists($key) { return isset(self::$catalog[$key]); } static function get($key) { return isset(self::$catalog[$key]) ? self::$catalog[$key] : false; } static function set($key, $value) { is_array($value) ? self::$catalog[$key][]=$value : self::$catalog[$key]=$value; } static function clear($key) { unset(self::$catalog[$key]); } function __construct() {} function __clone() {} } //_________________________________________________________________________ // open the qoob /** * setup global error handling * @return class qoob instance */ $qoob = qoob::open(); set_error_handler(array(&$qoob, 'error_handler')); set_exception_handler(array(&$qoob, 'exception_handler')); register_shutdown_function(array(&$qoob, 'fatal_handler')); return $qoob; ?>