<?php
namespace FluentForm\Framework\Http;
use Closure;
use Exception;
use WP_REST_Request;
use WP_REST_Response;
use InvalidArgumentException;
use FluentForm\Framework\Validator\ValidationException;
use FluentForm\Framework\Database\Orm\ModelNotFoundException;
class Route
{
protected $app = null;
protected $restNamespace = null;
protected $uri = null;
protected $compiled = null;
protected $name = '';
protected $meta = [];
protected $handler = null;
protected $method = null;
protected $options = [];
protected $wheres = [];
protected $policyHandler = null;
protected $predefinedNamedRegx = [
'int' => '[0-9]+',
'alpha' => '[a-zA-Z]+',
'alpha_num' => '[a-zA-Z0-9]+',
'alpha_num_dash' => '[a-zA-Z0-9-_]+'
];
public function __construct($app, $restNamespace, $uri, $handler, $method, $name = '')
{
$this->app = $app;
$this->restNamespace = $restNamespace;
$this->uri = $uri;
$this->handler = $handler;
$this->method = $method;
$this->name = $name;
}
public static function create($app, $namespace, $uri, $handler, $method, $name)
{
return new static($app, $namespace, $uri, $handler, $method, $name);
}
public function name($name)
{
$this->name .= $name;
return $this;
}
public function meta($meta)
{
$this->meta = $meta;
return $this;
}
public function getMeta($key = '')
{
if ($key) {
if (isset($this->meta[$key])) {
return $this->meta[$key];
}
return;
}
return $this->meta;
}
public function where($identifier, $value = null)
{
if (!is_null($value)) {
$this->wheres[$identifier] = $this->getValue($value);
} else {
foreach ($identifier as $key => $value) {
$this->wheres[$key] = $this->getValue($value);
}
}
return $this;
}
public function int($identifiers)
{
$identifiers = is_array($identifiers) ? $identifiers : func_get_args();
foreach ($identifiers as $identifier) {
$this->wheres[$identifier] = '[0-9]+';
}
return $this;
}
public function alpha($identifiers)
{
$identifiers = is_array($identifiers) ? $identifiers : func_get_args();
foreach ($identifiers as $identifier) {
$this->wheres[$identifier] = '[a-zA-Z]+';
}
return $this;
}
public function alphaNum($identifiers)
{
$identifiers = is_array($identifiers) ? $identifiers : func_get_args();
foreach ($identifiers as $identifier) {
$this->wheres[$identifier] = '[a-zA-Z0-9]+';
}
return $this;
}
public function alphaNumDash($identifiers)
{
$identifiers = is_array($identifiers) ? $identifiers : func_get_args();
foreach ($identifiers as $identifier) {
$this->wheres[$identifier] = '[a-zA-Z0-9-_]+';
}
return $this;
}
public function withPolicy($handler)
{
$this->policyHandler = $handler;
}
public function register()
{
$this->setOptions();
$uri = $this->compileRoute($this->uri);
return register_rest_route($this->restNamespace, "/{$uri}", $this->options);
}
protected function setOptions()
{
$this->options = [
'args' => [
'__meta__' => $this->meta
],
'methods' => $this->method,
'callback' => [$this, 'callback'],
'permission_callback' => [$this, 'permissionCallback']
];
}
protected function getValue($value)
{
if (array_key_exists($value, $this->predefinedNamedRegx)) {
return $this->predefinedNamedRegx[$value];
}
return $value;
}
protected function getPolicyHandler($policyHandler)
{
if ($policyHandler instanceof Closure) {
return function() use ($policyHandler) {
$policyHandler($this->app->request);
};
}
if (strpos($policyHandler, '@') !== false) return $policyHandler;
if (strpos($policyHandler, '::') !== false) return $policyHandler;
if (!function_exists($policyHandler)) {
if (is_string($this->handler) && strpos($this->handler, '@') !== false) {
list($_, $method) = explode('@', $this->handler);
$policyHandler = $policyHandler . '@' . $method;
} else if (is_array($this->handler)) {
$policyHandler = $policyHandler . '@' . $this->handler[1];
}
}
return $policyHandler;
}
protected function compileRoute($uri)
{
$params = [];
$compiledUri = preg_replace_callback('#/{(.*?)}#', function($match) use (&$params, $uri) {
// Default regx
$regx = '[^\s(?!/)]+';
$param = trim($match[1]);
if ($isOptional = strpos($param, '?')) {
$param = trim($param, '?');
}
if (in_array($param, $params)) {
throw new InvalidArgumentException(
"Duplicate parameter name '{$param}' found in {$uri}.", 500
);
}
$params[] = $param;
if (isset($this->wheres[$param])) {
$regx = $this->wheres[$param];
}
$pattern = "/(?P<" . $param . ">" . $regx . ")";
if ($isOptional) {
$pattern = "(?:" . $pattern . ")?";
}
$this->options['args'][$param]['required'] = !$isOptional;
return $pattern;
}, $uri);
return $this->compiled = $compiledUri;
}
public function callback(WP_REST_Request $request)
{
try {
$this->setRestRequest($request);
$response = $this->app->call(
$this->app->parseRestHandler($this->handler),
array_values($request->get_url_params())
);
if (!($response instanceof WP_REST_Response)) {
if (is_wp_error($response)) {
$response = $this->sendWPError($response);
} else {
$response = $this->app->response->sendSuccess($response);
}
}
return $response;
} catch (ValidationException $e) {
return $this->app->response->sendError(
$e->errors(), $e->getCode()
);
} catch (ModelNotFoundException $e) {
return $this->app->response->sendError([
'message' => $e->getMessage()
], 404);
} catch (Exception $e) {
return $this->app->response->sendError([
'message' => $e->getMessage()
], $e->getCode());
}
}
public function permissionCallback(WP_REST_Request $request)
{
$this->setRestRequest($request);
if (!$this->policyHandler) {
return true;
}
$policyHandler = $this->app->parsePolicyHandler(
$this->getPolicyHandler($this->policyHandler)
);
return $this->app->call($policyHandler, $request->get_url_params());
}
protected function setRestRequest($request)
{
if (!$this->app->bound('wprestrequest')) {
unset($this->options['args']['__meta__']);
$this->app->instance('wprestrequest', $request);
$this->app->instance('route', $this);
}
}
protected function sendWPError($response)
{
$code = $response->get_error_code();
return $this->app->response->sendError(
$response->get_error_messages(),
is_numeric($code) ? $code : null
);
}
}