.gitignore000064400000000036144761607070006547 0ustar00/vendor/ /tests/ composer.lockcomposer.json000064400000000705144761607070007304 0ustar00{ "name": "boru/dhapi", "type": "library", "autoload": { "psr-4": { "boru\\dhapi\\": "src/" } }, "authors": [ { "name": "Daniel Hayes", "email": "dhayes@boruapps.com" } ], "require": { "boru/dhutils": ">=2.0.0" }, "repositories": [ { "type": "composer", "url": "https://satis.boruapps.com" } ] } instructions-composer.txt000064400000000300144761607070011703 0ustar00{ "require": { "boru/dhapi": "*" }, "repositories": [ { "type": "composer", "url": "https://satis.boruapps.com" } ] }src/ApiInterface.php000064400000000211144761607070010404 0ustar00"string", "args"=>[] ] */ public function route($api); }src/authenticators/BasicKey.php000064400000000611144761607070012605 0ustar00getHeader("x-api-key",false)) !== false) { $api->setUser(true,["all"=>true],["apiKey"=>$authKey]); return true; } return false; } }src/autoloaders/ClassPathLoader.php000064400000001261144761607070013413 0ustar00getBaseDir()); spl_autoload_register( [$this, 'load'] ); } public function load($className) { $parts = explode("_",strtolower($className)); //utilize dhLoader for the including parts.. dhLoader::includeOnce(implode(".",$parts)); if(!class_exists($className)) { echo json_encode(["error"=>true]); exit(); } } }src/dhAPI.php000064400000023261144761607070007011 0ustar00false, "permissions"=>[], "data"=>[] ]; public $status = array( 200 => 'OK', 403 => 'Not Authorized', 404 => 'Not Found', 405 => 'Method Not Allowed', 420 => 'Missing required Inputs', 500 => 'Internal Server Error', ); public $validMethods = ["DELETE","POST","GET","PUT"]; /** * Valid options: * * router - callable|RouterInterface|false * * autoloader -callable|AutoloaderInterface|false * * authenticator - callable|AuthenticatorInterface|false * * requireAuth - (false) boolean * * prettyResponse - (false) boolean * * outputResponse - (true) boolean * * endOnResponse - (true) boolean * @param array $options * @return void */ public function __construct($options=[]) { $this->setRouter(dhGlobal::getval($options,"router",null)); $this->setAutoloader(dhGlobal::getval($options,"autoloader",null)); $this->setAuthenticator(dhGlobal::getval($options,"authenticator",null)); $this->setRequireAuth(dhGlobal::getval($options,"requireAuth",false)); if(($baseDir = dhGlobal::getval($options,"baseDir",false)) !== false) { $this->setBaseDir($baseDir); } $this->setPrettyResponse(dhGlobal::getval($options,"prettyResponse",false)); $this->setOutputResponse(dhGlobal::getval($options,"outputResponse",true)); $this->setEndOnResponse(dhGlobal::getval($options,"endOnResponse",true)); } public function init() { $this->start_time = microtime(true); header("Access-Control-Allow-Orgin: *"); header("Access-Control-Allow-Methods: *"); header("Content-Type: application/json"); $this->request = dhRequest::fromGlobals(true); $this->initAutoloader(); $this->runAuthenticator(); if(($result = $this->runRouter()) !== false) { $className = $result["class"]; if(class_exists($className,true)) { $obj = new $className($this); $obj->process($result["args"]); } else { $this->response(["message"=>"endpoint unknown","status"=>false],404); } } else { //we shouldn't get here unless router is set to false. } } public function runRouter() { if(is_callable($this->router)) { //if router is a callback $fn = $this->router; echo $fn($this); return; } elseif($this->router instanceof RouterInterface) { //if router is a defined router return $this->router->route($this); } elseif($this->router !== false) { //default router $this->router = new ClassPathRouter(); return $this->router->route($this); } return false; } public function runAuthenticator() { $authed = null; if(is_callable($this->authenticator)) { //if authenticator is a callback $fn = $this->authenticator; $authed = $fn($this); } elseif($this->authenticator instanceof AuthenticatorInterface) { $authed = $this->authenticator->authenticate($this); } elseif($this->authenticator !== false) { //default authenticator $this->authenticator = new BasicKey(); $authed = $this->authenticator->authenticate($this); } if($this->requireAuth && !$authed) { throw new \Exception("Not authenticated"); } } public function initAutoloader() { if(is_callable($this->autoloader)) { $fn = $this->autoloader; $fn($this); } elseif($this->autoloader instanceof AutoloaderInterface) { $this->autoloader->init($this); } elseif($this->autoloader !== false) { $this->autoloader = new ClassPathLoader($this); $this->autoloader->init($this); } } public function response($data=[],$code=200) { $header = "HTTP/1.1 " . $code . " " . $this->_requestStatus($code); header($header); if($this->prettyResponse) { $output = json_encode($data,JSON_PRETTY_PRINT | JSON_PARTIAL_OUTPUT_ON_ERROR); } else { $output = json_encode($data, JSON_PARTIAL_OUTPUT_ON_ERROR); } if($this->outputResponse) { echo $output; } $response = []; $response["headers"][] = $header; $response["output"] = $output; $this->response = $response; if($this->endOnResponse) { exit(); } return $this->response; } //request aliases public function getMethod() { return $this->request->getMethod(); } public function getURI($what=null,$default=null) { if(is_null($what)) { return $this->request->get("uri"); } return $this->request->get("uri.".$what,$default); } public function getRequest($what=null,$default=null) { $query = $this->getQuery($what,null); $post = $this->getPost($what,null); $body = $this->getBody($what,null); $value = $default; $value = !is_null($query) ? $query : $value; $value = !is_null($post) ? $post : $value; $value = !is_null($body) ? $body : $value; return $value; } public function getQuery($what=null,$default=null) { if(is_null($what)) { return $this->request->get("get"); } return $this->request->get("get.".$what,$default); } public function getPost($what=null,$default=null) { if(is_null($what)) { return $this->request->get("post"); } return $this->request->get("post.".$what,$default); } public function getBody($what=null,$default=null) { if(is_null($what)) { return $this->request->get("body"); } return $this->request->get("body.".$what,$default); } public function getHeader($what=null,$default=null) { if(is_null($what)) { return $this->request->get("headers"); } return $this->request->get("headers.".$what,$default); } public function getServer($what=null,$default=null) { if(is_null($what)) { return $this->request->get("server"); } return $this->request->get("server.".$what,$default); } /** * Set the value of autoloader * * @param mixed $autoloader * @return self */ public function setAutoloader($autoloader) { $this->autoloader = $autoloader; return $this; } /** * Set the value of router * * @param mixed $router * @return self */ public function setRouter($router) { $this->router = $router; return $this; } /** * Set the value of authenticator * * @param mixed $authenticator * @return self */ public function setAuthenticator($authenticator) { $this->authenticator = $authenticator; return $this; } /** * Get the value of baseDir * * @return mixed */ public function getBaseDir() { return $this->baseDir; } /** * Set the value of baseDir * * @param mixed $baseDir * @return self */ public function setBaseDir($baseDir) { $this->baseDir = $baseDir; return $this; } /** * Set the value of pretty * * @param mixed $pretty * @return self */ public function setPrettyResponse($prettyResponse) { $this->prettyResponse = $prettyResponse; return $this; } /** * Set the value of output * * @param mixed $output * @return self */ public function setOutputResponse($outputResponse) { $this->outputResponse = $outputResponse; return $this; } public function setRequireAuth($reqAuth) { $this->requireAuth = $reqAuth; return $this; } public function getRequireAuth() { return $this->requireAuth; } public function isAuthed() { return dhGlobal::getVal($this->user,"authed",false); } public function getPerms($permissionKey=null,$default=false) { if(is_null($permissionKey)) { return dhGlobal::getVal($this->user,"permissions",$default); } return dhGlobal::getVal($this->user["permissions"],$permissionKey,$default); } public function setUser($authed=false,$permissions=[],$data=[]) { $this->user["authed"] = $authed; $this->user["permissions"] = $permissions; $this->user["data"] = $data; } private function _requestStatus($code) { return ($this->status[$code])?$this->status[$code]:$this->status[500]; } /** * Set the value of endOnResponse * * @param mixed $endOnResponse * @return self */ public function setEndOnResponse($endOnResponse) { $this->endOnResponse = $endOnResponse; return $this; } }src/routers/ClassPathRouter.php000064400000001153144761607070012646 0ustar00getQuery("PATH",false)) !== false) { $path = dhGlobal::trimString("/", $path,dhGlobal::TRIM_BOTH); $path = dhGlobal::trimString(".php", $path,dhGlobal::TRIM_END); $path = ucwords($path,"/"); $parts = explode("/",$path); $handlerClass = implode("_",$parts); return ["class"=>$handlerClass,"args"=>[]]; } } }src/routers/RegExRouter.php000064400000002533144761607070012001 0ustar00getQuery("PATH",false)) !== false) { $path = dhGlobal::trimString("/", $path,dhGlobal::TRIM_BOTH); $path = dhGlobal::trimString(".php", $path,dhGlobal::TRIM_END); foreach($this->routes as $regex=>$className) { if(preg_match($regex,$path,$matches) !== 0) { array_shift($matches); return ["class"=>$className,"args"=>$matches]; } } } if(!is_null($this->default)) { $className = $this->default; return ["class"=>$className,"args"=>[]]; } return false; } public function addRoute($regex,$className) { $this->routes[$regex]=$className; return $this; } /** * Set the value of default * * @param mixed $default * @return self */ public function defaultRoute($default) { $this->default = $default; return $this; } }src/v2/API.php000064400000017657144761607070007040 0ustar00request = $request; $this->router = $router; $this->container = new Container(); } private $baseClasss = ""; /** @return Request */ public static function requestFromGlobals() { $method = $_SERVER["REQUEST_METHOD"]; if($method == "POST" && array_key_exists('HTTP_X_HTTP_METHOD', $_SERVER)) { if(in_array($_SERVER['HTTP_X_HTTP_METHOD'],['DELETE','PUT','PATCH'])) { $method = $_SERVER['HTTP_X_HTTP_METHOD']; } else { return Response::fromError(405,"Unusual Request Method: ".$_SERVER['HTTP_X_HTTP_METHOD'],true,true); } } if(!in_array($method,static::$allowedMethods)) { Response::fromError(405,"Method Not Allowed",true,true); } $rawBody = file_get_contents("php://input"); $body = []; if(!empty($rawBody)) { $body = json_decode($rawBody,true); if($body === null) { return Response::fromError(400,"Invalid JSON",true,true); } } $path = "/"; if(isset($_GET[static::$requestVar])) { $path.= trim($_GET[static::$requestVar],"/"); unset($_GET[static::$requestVar]); } return new Request($_GET,$body,getallheaders(),$method,$path); } /** * @param Request $request * @return API */ public function setRequest($request) { $this->request = $request; return $this; } /** * @param Router $router * @return API */ public function setRouter($router) { $this->router = $router; return $this; } /** * @param Container $container * @return API */ public function setContainer($container) { $this->container = $container; return $this; } /** * @param Authenticator $authenticator * @return API */ public function setAuthenticator($authenticator) { $this->authenticator = $authenticator; return $this; } public function setClassBaseNamespace($namespace) { $this->baseClasss = $namespace; return $this; } public function getClassBaseNamespace() { return $this->baseClasss; } /** * @return Authenticator */ public function getAuthenticator() { if(is_null($this->authenticator)) { $this->authenticator = new Authenticator(); } return $this->authenticator; } public function getAuthenticated() { return $this->getAuthenticator()->getAuthed(); } public function isAuthed() { return $this->getAuthenticator()->isAuthed(); } /** @return Request */ public function getRequest() { if(is_null($this->request)) { $this->request = static::requestFromGlobals(); } if(!($this->request instanceof Request)) { throw new \Exception("Request is not a Request object"); } return $this->request; } /** @return Router */ public function getRouter() { return $this->router; } /** @return Container */ public function getContainer() { return $this->container; } public function getMethod() { return $this->request->getMethod(); } public function getPath() { return $this->request->getPath(); } public function body($key=null,$defaultValue=null) { return $this->request->body($key,$defaultValue); } public function get($key=null,$defaultValue=null) { return $this->request->body($key,$defaultValue); } public function header($key=null) { if(is_null($key)) { return $this->request->getHeaders(); } return $this->request->getHeader($key); } public function headers() { return $this->request->getHeaders(); } /** * @return Response */ public function process() { if(!is_null($this->getAuthenticator())) { $auth = $this->getAuthenticator()->authenticate($this); if($auth instanceof Response) { return $auth; } elseif($auth === false) { return Response::fromError("Authentication Failed",401,true,true); } } if(($route = $this->router->matchFromPath($this->getPath(), $this->getMethod(),$this)) !== false) { if($route instanceof Response) { return $route; } $parameters = $route->getParameters(); // $arguments = ['id' => 2] $arguments = $route->getVars(); if(is_array($parameters)) { $controllerName = $parameters[0]; if(substr($controllerName,0,1) !== "\\" && !class_exists($controllerName)) { $controllerName = $this->getClassBaseNamespace().$controllerName; } $methodName = $parameters[1] ? $parameters[1] : null; if(!class_exists($controllerName)) { return Response::fromError("Endpoint controller Not Found",404,true,true); } $controller = new $controllerName(); if(!is_callable([$controller,"setAPI"]) || !is_callable([$controller,"isPermissed"])) { return Response::fromError("Endpoint controller not valid",404,true,true); } $controller->setAPI($this); $permissed = $controller->isPermissed(); if($permissed instanceof Response) { return $permissed; } elseif($permissed === false) { return Response::fromError("Not permissed",401,true,true); } if (!is_callable($controller)) { $controller = [$controller, $methodName]; } if (!is_callable($controller)) { return Response::fromError("Endpoint controller method not found",404,true,true); } $response = $controller(...array_values($arguments)); } else { $controller = $parameters; if (!is_callable($controller)) { return Response::fromError("Endpoint controller method not found",404,true,true); } $response = $controller($this,...array_values($arguments)); } if(!($response instanceof Response)) { //If the response is not a response object, we didn't return valid data from our endpoint.. so not implemented ob_clean(); return Response::fromError("Endpoint method not implemented",404,true,true); } return $response; } else { return Response::fromError("Endpoint Not Found",404,true,true); } } /** * @param \mrr\api\v3\router\Route $route */ public function addRoute($route) { if(is_null($this->router)) { $this->router = new Router(); } $this->router->add($route); } }src/v2/Authenticator.php000064400000000440144761607070011217 0ustar00API = $API; } public function isPermissed($API,$route) { return true; } /** * Returns an array of routes for the given method * * The indexed array should have values that are either Route objects, or in the following fromat: * * [ * * * "path", // The path to match, eg "/hello/world/{id}" * * * ["GET","POST",...], // The methods to match, eg ["GET","POST","PUT","DELETE"] * * * "functionName" // The name of the function to call, eg "helloWorld" * * * true // Whether or not to require an authentication response other than false * * ], * @param string $method * @return array */ public function routes() { return [ ["/hello/world",["GET"],"helloWorld"], ["/hello/world/{idlist}",["GET"],"helloWorldWithIds"], ["/test/{id:idlist}",["GET","POST"], "testFunction",true], ]; } public function helloWorld() { return Response::fromSuccess(["message"=>"Hello World!"]); } public function helloWorldWithIds($args) { return Response::fromSuccess(["message"=>"Hello World!","args"=>$args]); } public function testFunction($args) { return Response::fromSuccess(["message"=>"test/endpoint2!","args"=>$args,"apiArgs"=>$this->API->body()]); } }src/v2/Controller.php000064400000000603144761607070010531 0ustar00API = $API; } public function helloWorld($args=null) { return Response::fromSuccess(["message"=>"Hello World!"]); } }src/v2/auth/CallbackAuthenticator.php000064400000003565144761607070013610 0ustar00callback = $callback; $this->permissedCallback = $permissedCallback; } public function authenticate($API) { if(is_null($this->callback)) { return false; } $callable = $this->callback; if(is_callable($callable)) { $this->authed = $callable($API); } else { $this->authed = $callable; } return $this->authed; } public function getAuthed() { return $this->authed; } public function isAuthed() { if(is_object($this->authed) && $this->authed instanceof Response) { return false; } return $this->authed === false ? false : true; } /** * @param API $API * @param Route $route * @return false|mixed */ public function permissed($API,$route) { if(is_null($this->permissedCallback)) { return true; } $callable = $this->permissedCallback; if(is_callable($callable)) { return $callable($API,$route); } else { return $callable; } } public function setCallback($callback) { $this->callback = $callback; } public function getCallback() { return $this->callback; } public function setPermissedCallback($callback) { $this->permissedCallback = $callback; } public function getPermissedCallback() { return $this->permissedCallback; } }src/v2/auth/HmacAuthenticator.php000064400000005764144761607070012767 0ustar00apiKey; } public function getApiSign() { return $this->apiSign; } public function getApiNonce() { return $this->apiNonce; } public function setApiKey($apiKey) { $this->apiKey = $apiKey; } public function setApiSign($apiSign) { $this->apiSign = $apiSign; } public function setApiNonce($apiNonce) { $this->apiNonce = $apiNonce; } public function authenticate($API) { /** @var API */ $this->API = $API; $this->apiKey = $API->header("x-api-key",false); $this->apiSign = $API->header("x-api-sign",false); $this->apiNonce = $API->header("x-api-nonce",false); if($this->validate()) { $this->updateKeyNonce(); $this->log(true); return true; } else { $this->log(false); return false; } return true; } public function getAuthed() { return $this->authed; } public function isAuthed() { return $this->authed; } public function getAuthMesage() { return $this->authMessage; } private function validate() { $keyInfo = $this->getKeyInfo(); if(is_null($keyInfo) || !is_array($keyInfo) || !isset($keyInfo["apikey"])) { $this->authed=false; $this->authMessage = "Invalid or missing API Key"; return false; } $sign_check = $this->signRequest($keyInfo["secret"]); if($sign_check != $this->apiSign) { $this->authed=false; $this->authMessage = [ "Signature Failure", "String to sign:", $this->getSignString(), "Expected signature:", $sign_check ]; return false; } if($keyInfo["nonce"] >= $this->apiNonce) { $this->authed=false; $this->authMessage = "Nonce failure"; return false; } return true; } protected function getSignString() { $path = "/".dhGlobal::trimString($this->API->getPath(),"/"); return $this->apiKey.$this->apiNonce.$path; } protected function signRequest($keySecret) { return hash_hmac("sha1", $this->getSignString(), $keySecret); } abstract public function updateKeyNonce(); abstract public function getKeyInfo($force=false); abstract public function log($success); abstract function permissed($API, $route); }src/v2/auth/VtigerAuthenticator.php000064400000001162144761607070013343 0ustar00accessKey = $useAccessKey; $this->userPassword = !$useAccessKey; } public function authenticate($API) { } public function getAuthed() { } public function isAuthed() { } public function permissed($API, $route) { } }src/v2/core/Container.php000064400000007406144761607070011270 0ustar00toArray(false); } $key = $this->camelKey($key); $value = dhGlobal::getVal($this->data,$key,$defaultValue); if(is_callable($value)) { return $value(); } return $value; } public function set($key,$value=null,$makePublic=true) { if(is_array($key)) { $this->data=$key; return $this; } $key = $this->camelKey($key); if(strpos($key,".")!==false) { if(!$this->dotExists($key)) { if(!$makePublic && !in_array($key,$this->nonPublicFields)) { $this->nonPublicFields[] = $key; } } dhGlobal::dotAssign($this->data,$key,$value); } else { if(!array_key_exists($key,$this->data)) { if(!$makePublic && !in_array($key,$this->nonPublicFields)) { $this->nonPublicFields[] = $key; } } $this->data[$key] = $value; } if(is_callable($value)) { $this->callableKeys[] = $key; } } public function getRaw($key=null,$defaultValue=null) { if(is_null($key)) { return $this->rawData; } return isset($this->rawData[$key]) ? $this->rawData[$key] : $defaultValue; } public function setRaw($key,$value=null) { if(is_array($key)) { $this->rawData=$key; return $this; } $this->rawData[$key] = $value; return $this; } public function toArray($hideNonPublic=true) { $data = $this->data; if($hideNonPublic) { foreach($this->nonPublicFields as $field) { dhGlobal::dotDelete($data,$field); } } foreach($this->callableKeys as $callableKey) { if($this->dotExists($callableKey)) { $value = $this->get($callableKey); dhGlobal::dotAssign($data,$callableKey,$value); } } return $data; } public function __toString() { return json_encode($this->toArray(),JSON_PRETTY_PRINT); } public function jsonSerialize() { return $this->toArray(); } public function setFromArray($array) { if(empty($row)) { return false; } $this->rawData = $array; foreach($row as $key=>$value) { //add it to our data set $this->set($key,$value,false); } } public function camelKey($key) { $parts = explode(".",trim($key)); $newKey = []; for($i=0;$idotExists(implode(".",$tempKey))) { $newPart = dhGlobal::camelize($part); $newKey[] = $newPart; } else { $newKey[] = $part; } } return implode(".",$newKey); } public function dotExists($dotString) { $pieces = explode(".", $dotString); $pointer = $this->data; for ($i = 0; $i < count($pieces); $i++) { if (isset($pointer[$pieces[$i]])) { $pointer = $pointer[$pieces[$i]]; } else { return false; } } return true; } }src/v2/core/ErrorResponse.php000064400000000374144761607070012153 0ustar00_cleanInputs($get); $data = array_merge($data,$this->_cleanInputs($body)); $data = $this->_parseDots($data); $this->setHeaders($headers); $this->setMethod($method); $this->setPath($path); $this->setData($data); } public function body($key=null,$defaultValue=null) { if($key === null) { return $this->data; } return dhGlobal::getVal($this->data,$key,$defaultValue); } public function getData() { return $this->data; } public function getHeader($key) { $key = strtolower($key); if(isset($this->headers[$key])) { return $this->headers[$key]; } return null; } public function getHeaders() { return $this->headers; } public function getMethod() { return $this->method; } public function getPath() { return $this->path; } public function setMethod($method) { $this->method = $method; } public function setPath($path) { $this->path = $path; } public function setData($data) { $this->data = $data; } public function setHeader($key,$value) { $key = strtolower($key); $this->headers[$key] = $value; if($key == "x-api-key") { $this->apiKey = $value; } elseif($key == "x-api-signature") { $this->apiSignature = $value; } elseif($key == "x-api-nonce") { $this->apiNonce = $value; } } public function setHeaders($headers) { foreach($headers as $k=>$v) { $this->setHeader($k,$v); } } //utility functions private function _cleanInputs($data) { $clean_input = Array(); if (is_array($data)) { foreach ($data as $k => $v) { if(strpos($k,"_") !== false) { $k = str_replace("_",".",$k); } $clean_input[$k] = $this->_cleanInputs($v); } } else { $clean_input = trim(strip_tags($data)); } return $clean_input; } private function _parseDots($array) { foreach($array as $k=>$v) { if(is_array($v)) { $array[$k] = $this->_parseDots($v); } if(strpos($k,".") !== false) { $this->_assignArrayByPath($array,$k,$v); unset($array[$k]); } } return $array; } private function _assignArrayByPath(&$arr, $path, $value, $separator='.') { $keys = explode($separator, $path); foreach ($keys as $key) { $arr = &$arr[$key]; } $arr = $value; } }src/v2/core/Response.php000064400000017634144761607070011150 0ustar00"OK", 400=>"Bad Request", 401=>"Unauthorized", 403=>"Forbidden", 404=>"Not Found", 405=>"Method Not Allowed", 406=>"Not Acceptable", 409=>"Conflict", 410=>"Gone", 411=>"Length Required", 412=>"Precondition Failed", 413=>"Payload Too Large", 414=>"URI Too Long", 415=>"Unsupported Media Type", 416=>"Range Not Satisfiable", 417=>"Expectation Failed", 418=>"I'm a teapot", 420=>"Missing Required Inputs", 421=>"Invalid Inputs", 422=>"Unprocessable Entity", 423=>"Locked", 424=>"Failed Dependency", 425=>"Too Early", 426=>"Upgrade Required", 428=>"Precondition Required", 429=>"Too Many Requests", 431=>"Request Header Fields Too Large", 451=>"Unavailable For Legal Reasons", 500=>"Internal Server Error", 501=>"Not Implemented", 502=>"Bad Gateway", 503=>"Service Unavailable", 504=>"Gateway Timeout", 505=>"HTTP Version Not Supported", 506=>"Variant Also Negotiates", 507=>"Insufficient Storage", 508=>"Loop Detected", 510=>"Not Extended", 511=>"Network Authentication Required", ]; protected $data = []; protected $statusCode = 200; protected $message = null; protected static $pretty = true; protected static $fieldList = []; public function __construct($data=[], $status=200, $message=null) { $this->setData($data); $this->setStatus($status); $this->setMessage($message); } public function emit($terminate=null) { $this->emitStatusCode(); header("Content-Type: application/json"); echo $this->jsonEncode(static::parseFieldList($this->data)); if(static::shouldTerminate($terminate)) { exit; } } private function jsonEncode($data) { return json_encode($data, $this->getJsonFlags()); } private function emitStatusCode() { if(!isset(self::$requestCodeStatus[$this->statusCode])) { $this->statusCode = 500; } header("HTTP/1.1 " .$this->statusCode ." " .self::$requestCodeStatus[$this->statusCode]); } private function getJsonFlags() { if(static::$pretty) { return JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR; } else { return JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR; } } public function setStatus($status) { $this->statusCode = $status; } public function setMessage($message) { $this->message = $message; } public static function setPretty($pretty) { static::$pretty = $pretty; } /** * Set the field list to use when returning data * @param array $fieldList the fields to return */ public static function setFieldList($fieldList) { if(!is_array($fieldList)) { $fieldList = Utils::splitToArray($fieldList); } static::$fieldList = $fieldList; } public function setData($data) { $this->data = $data; } private static function parseFieldList($data) { if(!empty(static::$fieldList) && is_array($data) && !empty($data)) { $newData = static::filterArray(json_decode(json_encode($data,JSON_PARTIAL_OUTPUT_ON_ERROR),true) , static::$fieldList); if(empty($newData)) { return null; } else { return $newData; } } else { return $data; } } private static function filterArray($array,$fieldFilter,$currentKey='') { $result = []; foreach ($array as $key => $value) { $newKey = $currentKey === '' ? $key : $currentKey . '.' . $key; $matchedFilter = ''; foreach ($fieldFilter as $filter) { $pattern = "(\.|\.[0-9]+\.)"; $prePattern = "([0-9]+\.)?"; $newFilter = str_replace('.', $pattern, $filter); $regex = '/^'.$prePattern. $newFilter . '(\.|$)/'; if (preg_match($regex, $newKey)) { $matchedFilter = $filter; break; } } if ($matchedFilter !== '') { $result[$key] = $value; } elseif (is_array($value)) { $filteredValue = static::filterArray($value, $fieldFilter, $newKey); if (!empty($filteredValue)) { $result[$key] = $filteredValue; } } } return $result; } // Static Methods /** * Create a new Response object from an error, emits and terminates (exit) by default. * @param string|array $error the error message or data array * @param int $code (default:400) the HTTP status code * @param bool $emit (default:true) emit the response (false) return the response object (useful for chaining) * @param bool $terminate (default:true) terminate the script after emitting (false) return the response object (useful for chaining) * @return Response */ public static function fromError($error, $code=400, $emit=null, $terminate=null) { $response = new ErrorResponse([ "error"=>$error, "code"=>$code, ], $code); if(static::shouldEmit($emit)) { $response->emit($terminate); } return $response; } /** * Create a new Response object from an exception, emits and terminates (exit) by default. * @param \Exception $exception the exception object * @param int $code (default:500) the HTTP status code * @param bool $emit (default:true) emit the response (false) return the response object (useful for chaining) * @param bool $terminate (default:true) terminate the script after emitting (false) return the response object (useful for chaining) * @return Response */ public static function fromException($exception, $code=500, $emit=null, $terminate=null) { $response = new ErrorResponse([ "error"=>$exception->getMessage(), "code"=>$code, ], $code); if(static::shouldEmit($emit)) { $response->emit($terminate); } return $response; } /** * Create a new Response object from a success, emits and terminates (exit) by default. * @param string|array $data the data to return * @param int $code (default:200) the HTTP status code * @param bool $emit (default:true) emit the response (false) return the response object (useful for chaining) * @param bool $terminate (default:true) terminate the script after emitting (false) return the response object (useful for chaining) * @return Response */ public static function fromSuccess($data, $code=200, $emit=null, $terminate=null) { $response = new Response($data, $code); if(static::shouldEmit($emit)) { $response->emit($terminate); } return $response; } public static function shouldEmit($emit=null) { if($emit===null) { return self::$autoEmit; } else { return $emit; } } public static function shouldTerminate($terminate=null) { if($terminate===null) { return self::$autoTerminate; } else { return $terminate; } } }src/v2/core/Utils.php000064400000000451144761607070010437 0ustar00name = $name; $this->path = $path; $this->methods = $methods; $this->parameters = $parameters; $this->requireAuth = $requireAuth; } public function setRouter($router) { $this->router = $router; } public function setRequireAuth($requireAuth) { $this->requireAuth = $requireAuth; } public function getRequireAuth() { return $this->requireAuth; } /** * * @param API $api * @param Router $router * @return bool|Response|mixed */ public function isPermissed($api) { if(!$this->requireAuth) { return true; } if($this->requireAuth && $api->isAuthed() === false) { return false; } return $api->getAuthenticator()->permissed($api,$this); } public function getAuth() { return $this->router->getAuthenticated(); } public function match($path, $method) { $regex = $this->getPath(); $this->varCallbacks = []; $regexPattern = "[^/]+"; foreach ($this->getVarsNames() as $variable) { $varName = trim($variable, '{\}'); $parts = explode("|",$varName); $paramName = array_shift($parts); $nameParts = explode(":",$paramName); if(count($nameParts) > 1) { $paramName = array_shift($nameParts); $shortcut = $this->router->getRegex($nameParts[0]); $regexPattern = $shortcut["regex"]; if($shortcut["callback"] !== null && (!isset($this->varCallbacks[$paramName]) || !in_array($shortcut["callback"],$this->varCallbacks[$paramName]))) { $this->varCallbacks[$paramName][] = $shortcut["callback"]; } } $regex = str_replace($variable, '(?P<' . $paramName . '>'.$regexPattern.'+)', $regex); if(!empty($parts)) { foreach($parts as $part) { if(!isset($this->varCallbacks[$paramName]) || !in_array($part,$this->varCallbacks[$paramName])) { $this->varCallbacks[$paramName][] = $part; } } } } if (in_array($method, $this->getMethods()) && preg_match('#^' . $regex . '$#sD', self::trimPath($path), $matches)) { $values = array_filter($matches, static function ($key) { return is_string($key); }, ARRAY_FILTER_USE_KEY); $this->vars = $values; $this->parseVarCallbacks(); return true; } return false; } public function getName() { return $this->name; } public function getPath() { return $this->path; } public function getParameters() { return $this->parameters; } public function getMethods() { return $this->methods; } public function getVarsNames() { preg_match_all('/{[^}]*}/', $this->path, $matches); $reset = reset($matches); return $reset ? $reset : []; } public function hasVars() { return $this->getVarsNames() !== []; } public function getVars() { return $this->vars; } public static function trimPath($path) { return '/' . rtrim(ltrim(trim($path), '/'), '/'); } public function parseVar($var,$value) { if(($callable = $this->router->getVariableCallback($var)) !== false) { if (!is_callable($callable)) { return $value; } return call_user_func($callable, $value); } return $value; } private function parseVarCallbacks() { foreach($this->varCallbacks as $varName=>$callbacks) { if(isset($this->vars[$varName])) { $value = $this->vars[$varName]; foreach($callbacks as $callback) { if(($callable = $this->router->getVariableCallback($callback)) !== false) { if (!is_callable($callable)) { continue; } $value = call_user_func($callable, $value); } } $this->vars[$varName] = $value; } } } public static function fromArray($path,$methods,$callable,$requireAuth = false) { return new Route($path,$path,$methods,$callable,$requireAuth); } public static function fromClass($classNameOrObject) { if(!is_object($classNameOrObject)) { $classNameOrObject = new $classNameOrObject(); } if(is_object($classNameOrObject) && !($classNameOrObject instanceof ClassControllerInterface)) { throw new \Exception("Class must implement ClassControllerInterface"); } $routeArray = $classNameOrObject->routes(); $routes = []; foreach($routeArray as $route) { if(!($route instanceof Route) && !is_array($route)) { throw new \Exception("Class must return array of Route objects"); } if(is_array($route)) { $className = get_class($classNameOrObject); $callable = [$className,$route[2]]; $route = self::fromArray($route[0],$route[1],$callable,isset($route[3]) ? $route[3] : false); } $routes[] = $route; } return $routes; } }src/v2/routing/Router.php000064400000007613144761607070011365 0ustar00 [ "regex" => "[0-9]+", "callback" => null, ], "idlist"=> [ "regex" => "[0-9,;]+", "callback" => "split", ], "string" => [ "regex" => "[a-zA-Z0-9]+", "callback" => null, ], "stringlist" => [ "regex" => "[a-zA-Z0-9,;]+", "callback" => "split", ], "alpha" => [ "regex" => "[a-zA-Z]+", "callback" => null, ], "alphalist" => [ "regex" => "[a-zA-Z,;]+", "callback" => "split", ], ]; private $variableCallbacks = [ "split" => ["\\boru\\dhapi\\v2\\routing\\Router","splitToArray"], ]; private $authenticationCallback = null; private $authenticated = false; /** * @var Route[] $routes */ public function __construct($routes=[],$authenticationCallaback=null) { foreach($routes as $route) { $this->add($route); } if(!is_null($authenticationCallaback)) { $this->setAuthenticationCallback($authenticationCallaback); } } public function setAuthenticated($authenticated) { $this->authenticated = $authenticated; } public function getAuthenticated() { return $this->authenticated; } public function setAuthenticationCallback($callback) { $this->authenticationCallback = $callback; } public function getAuthenticationCallback() { return is_null($this->authenticationCallback) ? false : $this->authenticationCallback; } /** * @var Route $route */ public function add($route) { $route->setRouter($this); $this->routes[$route->getName()] = $route; return $this; } public function addClass($className) { $routes = Route::fromClass($className); foreach($routes as $route) { $this->add($route); } } public function addRoute($routeName,$path,$methods,$parameters) { $route = new Route($routeName,$path,$methods,$parameters); $this->add($route); return $this; } public function addCallback($name,$callback) { $this->variableCallbacks[$name] = $callback; return $this; } public function addRegex($name,$regex,$callback=null) { $this->regexShortcuts[$name] = [ "regex" => $regex, "callback" => $callback, ]; return $this; } public function matchFromPath($pathString, $method, $api) { foreach ($this->routes as $route) { if ($route->match($pathString, $method) === false) { continue; } $permissed = $route->isPermissed($api); if($permissed === false) { return Response::fromError("Not authorized", 403); } elseif($permissed instanceof Response) { return $permissed; } return $route; } return false; } public function getRegex($name) { if(isset($this->regexShortcuts[$name])) { return $this->regexShortcuts[$name]; } return ["regex" => "[^/]+", "callback" => null]; } public function getVariableCallback($name) { if(isset($this->variableCallbacks[$name])) { return $this->variableCallbacks[$name]; } return false; } public static function splitToArray($idInput="") { if(empty($idInput)) return []; $delims = [",",";"]; $idInput = str_replace($delims,$delims[0],$idInput); return explode($delims[0],$idInput); } }test/init.php000064400000000073144761607070007213 0ustar00 RewriteEngine On RewriteRule ^index\.php$ - [L] RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule .+ - [L] #RewriteRule ^(.+)/?$ index.php?PATH=$1&%{QUERY_STRING} [L] RewriteRule ^(.+)/?$ v2test.php?APIREQ=$1&%{QUERY_STRING} [L] test/web/api/test/load.php000064400000000260144761607070011472 0ustar00"; } public function process() { echo "process!
"; } }test/web/api/whoami.php000064400000000467144761607070011071 0ustar00api = $api; } public function process($opts=[]) { $data = ["class"=>__CLASS__,"opts"=>$opts]; echo json_encode($data); } }test/web/index.php000064400000000455144761607070010140 0ustar00init(); */ $router = new RegExRouter(); $router->addRoute("/^whoami\/(?[0-9]+)$/","Whoami"); $api = new dhAPI(["router"=>$router]); $api->init();test/web/v2test.php000064400000002073144761607070010256 0ustar00add(new Route("helloWorld", "/helloworld", ["GET","POST"], ["\\boru\\dhapi\\v2\\Controller","helloWorld"])); $router->add(new Route("test", "/test/{id:idlist}", ["GET","POST"], function($api,$args=[]) { return Response::fromSuccess(["message"=>"test/endpoint2!","args"=>$args,"apiArgs"=>$api->body()]); })); $router->addCallback("dd",function($value) { $value[] = "dd"; return $value; }); */ $router->addClass("\\boru\\dhapi\\v2\\ClassController"); $request = API::requestFromGlobals(); $api = new API($request,$router); $authenticator = new Authenticator( function($api) { return true; }, function($api,$route) { print_r($api->headers()); return true; } ); $api->setAuthenticator($authenticator); $response = $api->process(); $response->emit(true);