commit version 22.12.12447
This commit is contained in:
File diff suppressed because one or more lines are too long
@ -62,7 +62,8 @@ namespace donatj\UserAgent {
|
||||
|
||||
if( preg_match('/\((.*?)\)/m', $u_agent, $parent_matches) ) {
|
||||
preg_match_all(<<<'REGEX'
|
||||
/(?P<platform>BB\d+;|Android|Adr|Symbian|Sailfish|CrOS|Tizen|iPhone|iPad|iPod|Linux|(Open|Net|Free)BSD|Macintosh|Windows(\ Phone)?|Silk|linux-gnu|BlackBerry|PlayBook|X11|(New\ )?Nintendo\ (WiiU?|3?DS|Switch)|Xbox(\ One)?)
|
||||
/(?P<platform>BB\d+;|Android|Adr|Symbian|Sailfish|CrOS|Tizen|iPhone|iPad|iPod|Linux|(?:Open|Net|Free)BSD|Macintosh|
|
||||
Windows(?:\ Phone)?|Silk|linux-gnu|BlackBerry|PlayBook|X11|(?:New\ )?Nintendo\ (?:WiiU?|3?DS|Switch)|Xbox(?:\ One)?)
|
||||
(?:\ [^;]*)?
|
||||
(?:;|$)/imx
|
||||
REGEX
|
||||
@ -88,23 +89,27 @@ REGEX
|
||||
$platform = 'Chrome OS';
|
||||
} elseif( $platform == 'Adr' ) {
|
||||
$platform = 'Android';
|
||||
} elseif( $platform === null ) {
|
||||
if(preg_match_all('%(?P<platform>Android)[:/ ]%ix', $u_agent, $result)) {
|
||||
$platform = $result[PLATFORM][0];
|
||||
}
|
||||
}
|
||||
|
||||
preg_match_all(<<<'REGEX'
|
||||
%(?P<browser>Camino|Kindle(\ Fire)?|Firefox|Iceweasel|IceCat|Safari|MSIE|Trident|AppleWebKit|
|
||||
TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|Edge|EdgA?|CriOS|UCBrowser|Puffin|
|
||||
TizenBrowser|(?:Headless)?Chrome|YaBrowser|Vivaldi|IEMobile|Opera|OPR|Silk|Midori|(?-i:Edge)|EdgA?|CriOS|UCBrowser|Puffin|
|
||||
OculusBrowser|SamsungBrowser|SailfishBrowser|XiaoMi/MiuiBrowser|
|
||||
Baiduspider|Applebot|Facebot|Googlebot|YandexBot|bingbot|Lynx|Version|Wget|curl|
|
||||
FxiOS|Vienna|
|
||||
Valve\ Steam\ Tenfoot|
|
||||
NintendoBrowser|PLAYSTATION\ (\d|Vita)+)
|
||||
NintendoBrowser|PLAYSTATION\ (?:\d|Vita)+)
|
||||
\)?;?
|
||||
(?:[:/ ](?P<version>[0-9A-Z.]+)|/[A-Z]*)%ix
|
||||
REGEX
|
||||
, $u_agent, $result);
|
||||
|
||||
// If nothing matched, return null (to avoid undefined index errors)
|
||||
if( !isset($result[BROWSER][0]) || !isset($result[BROWSER_VERSION][0]) ) {
|
||||
if( !isset($result[BROWSER][0], $result[BROWSER_VERSION][0]) ) {
|
||||
if( preg_match('%^(?!Mozilla)(?P<browser>[A-Z0-9\-]+)(/(?P<version>[0-9A-Z.]+))?%ix', $u_agent, $result) ) {
|
||||
return [ PLATFORM => $platform ?: null, BROWSER => $result[BROWSER], BROWSER_VERSION => empty($result[BROWSER_VERSION]) ? null : $result[BROWSER_VERSION] ];
|
||||
}
|
||||
@ -193,7 +198,7 @@ REGEX
|
||||
} elseif( $browser == 'AppleWebKit' ) {
|
||||
if( $platform == 'Android' ) {
|
||||
$browser = 'Android Browser';
|
||||
} elseif( strpos($platform, 'BB') === 0 ) {
|
||||
} elseif( strpos((string)$platform, 'BB') === 0 ) {
|
||||
$browser = 'BlackBerry Browser';
|
||||
$platform = 'BlackBerry';
|
||||
} elseif( $platform == 'BlackBerry' || $platform == 'PlayBook' ) {
|
||||
|
@ -91,13 +91,13 @@ class HelpScreen {
|
||||
|
||||
$pad = str_repeat(' ', $max + 3);
|
||||
while ($desc = array_shift($description)) {
|
||||
$formatted .= "\n${pad}${desc}";
|
||||
$formatted .= "\n{$pad}{$desc}";
|
||||
}
|
||||
|
||||
array_push($help, $formatted);
|
||||
}
|
||||
|
||||
return join("\n", $help);
|
||||
return implode("\n", $help);
|
||||
}
|
||||
|
||||
private function _consume($options) {
|
||||
@ -115,7 +115,7 @@ class HelpScreen {
|
||||
array_push($names, '-' . $alias);
|
||||
}
|
||||
|
||||
$names = join(', ', $names);
|
||||
$names = implode(', ', $names);
|
||||
$max = max(strlen($names), $max);
|
||||
$out[$names] = $settings;
|
||||
}
|
||||
|
662
libs/flight2/Engine.php
Normal file
662
libs/flight2/Engine.php
Normal file
@ -0,0 +1,662 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight;
|
||||
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use flight\core\Dispatcher;
|
||||
use flight\core\Loader;
|
||||
use flight\net\Request;
|
||||
use flight\net\Response;
|
||||
use flight\net\Router;
|
||||
use flight\template\View;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* The Engine class contains the core functionality of the framework.
|
||||
* It is responsible for loading an HTTP request, running the assigned services,
|
||||
* and generating an HTTP response.
|
||||
*
|
||||
* Core methods
|
||||
*
|
||||
* @method void start() Starts engine
|
||||
* @method void stop() Stops framework and outputs current response
|
||||
* @method void halt(int $code = 200, string $message = '') Stops processing and returns a given response.
|
||||
* @method void route(string $pattern, callable $callback, bool $pass_route = false) Routes a URL to a callback function.
|
||||
* @method Router router() Gets router
|
||||
*
|
||||
* Views
|
||||
* @method void render(string $file, array $data = null, string $key = null) Renders template
|
||||
* @method View view() Gets current view
|
||||
*
|
||||
* Request-response
|
||||
* @method Request request() Gets current request
|
||||
* @method Response response() Gets current response
|
||||
* @method void error(Exception $e) Sends an HTTP 500 response for any errors.
|
||||
* @method void notFound() Sends an HTTP 404 response when a URL is not found.
|
||||
* @method void redirect(string $url, int $code = 303) Redirects the current request to another URL.
|
||||
* @method void json(mixed $data, int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) Sends a JSON response.
|
||||
* @method void jsonp(mixed $data, string $param = 'jsonp', int $code = 200, bool $encode = true, string $charset = 'utf-8', int $option = 0) Sends a JSONP response.
|
||||
*
|
||||
* HTTP caching
|
||||
* @method void etag($id, string $type = 'strong') Handles ETag HTTP caching.
|
||||
* @method void lastModified(int $time) Handles last modified HTTP caching.
|
||||
*/
|
||||
class Engine
|
||||
{
|
||||
/**
|
||||
* Stored variables.
|
||||
*/
|
||||
protected array $vars;
|
||||
|
||||
/**
|
||||
* Class loader.
|
||||
*/
|
||||
protected Loader $loader;
|
||||
|
||||
/**
|
||||
* Event dispatcher.
|
||||
*/
|
||||
protected Dispatcher $dispatcher;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->vars = [];
|
||||
|
||||
$this->loader = new Loader();
|
||||
$this->dispatcher = new Dispatcher();
|
||||
|
||||
$this->init();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles calls to class methods.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param array $params Method parameters
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return mixed Callback results
|
||||
*/
|
||||
public function __call(string $name, array $params)
|
||||
{
|
||||
$callback = $this->dispatcher->get($name);
|
||||
|
||||
if (\is_callable($callback)) {
|
||||
return $this->dispatcher->run($name, $params);
|
||||
}
|
||||
|
||||
if (!$this->loader->get($name)) {
|
||||
throw new Exception("{$name} must be a mapped method.");
|
||||
}
|
||||
|
||||
$shared = empty($params) || $params[0];
|
||||
|
||||
return $this->loader->load($name, $shared);
|
||||
}
|
||||
|
||||
// Core Methods
|
||||
|
||||
/**
|
||||
* Initializes the framework.
|
||||
*/
|
||||
public function init(): void
|
||||
{
|
||||
static $initialized = false;
|
||||
$self = $this;
|
||||
|
||||
if ($initialized) {
|
||||
$this->vars = [];
|
||||
$this->loader->reset();
|
||||
$this->dispatcher->reset();
|
||||
}
|
||||
|
||||
// Register default components
|
||||
$this->loader->register('request', Request::class);
|
||||
$this->loader->register('response', Response::class);
|
||||
$this->loader->register('router', Router::class);
|
||||
$this->loader->register('view', View::class, [], function ($view) use ($self) {
|
||||
$view->path = $self->get('flight.views.path');
|
||||
$view->extension = $self->get('flight.views.extension');
|
||||
});
|
||||
|
||||
// Register framework methods
|
||||
$methods = [
|
||||
'start', 'stop', 'route', 'halt', 'error', 'notFound',
|
||||
'render', 'redirect', 'etag', 'lastModified', 'json', 'jsonp',
|
||||
'post', 'put', 'patch', 'delete',
|
||||
];
|
||||
foreach ($methods as $name) {
|
||||
$this->dispatcher->set($name, [$this, '_' . $name]);
|
||||
}
|
||||
|
||||
// Default configuration settings
|
||||
$this->set('flight.base_url');
|
||||
$this->set('flight.case_sensitive', false);
|
||||
$this->set('flight.handle_errors', true);
|
||||
$this->set('flight.log_errors', false);
|
||||
$this->set('flight.views.path', './views');
|
||||
$this->set('flight.views.extension', '.php');
|
||||
$this->set('flight.content_length', true);
|
||||
|
||||
// Startup configuration
|
||||
$this->before('start', function () use ($self) {
|
||||
// Enable error handling
|
||||
if ($self->get('flight.handle_errors')) {
|
||||
set_error_handler([$self, 'handleError']);
|
||||
set_exception_handler([$self, 'handleException']);
|
||||
}
|
||||
|
||||
// Set case-sensitivity
|
||||
$self->router()->case_sensitive = $self->get('flight.case_sensitive');
|
||||
// Set Content-Length
|
||||
$self->response()->content_length = $self->get('flight.content_length');
|
||||
});
|
||||
|
||||
$initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom error handler. Converts errors into exceptions.
|
||||
*
|
||||
* @param int $errno Error number
|
||||
* @param string $errstr Error string
|
||||
* @param string $errfile Error file name
|
||||
* @param int $errline Error file line number
|
||||
*
|
||||
* @throws ErrorException
|
||||
*/
|
||||
public function handleError(int $errno, string $errstr, string $errfile, int $errline)
|
||||
{
|
||||
if ($errno & error_reporting()) {
|
||||
throw new ErrorException($errstr, $errno, 0, $errfile, $errline);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom exception handler. Logs exceptions.
|
||||
*
|
||||
* @param Exception $e Thrown exception
|
||||
*/
|
||||
public function handleException($e): void
|
||||
{
|
||||
if ($this->get('flight.log_errors')) {
|
||||
error_log($e->getMessage());
|
||||
}
|
||||
|
||||
$this->error($e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a callback to a framework method.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param callback $callback Callback function
|
||||
*
|
||||
* @throws Exception If trying to map over a framework method
|
||||
*/
|
||||
public function map(string $name, callable $callback): void
|
||||
{
|
||||
if (method_exists($this, $name)) {
|
||||
throw new Exception('Cannot override an existing framework method.');
|
||||
}
|
||||
|
||||
$this->dispatcher->set($name, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a class to a framework method.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param string $class Class name
|
||||
* @param array $params Class initialization parameters
|
||||
* @param callable|null $callback $callback Function to call after object instantiation
|
||||
*
|
||||
* @throws Exception If trying to map over a framework method
|
||||
*/
|
||||
public function register(string $name, string $class, array $params = [], ?callable $callback = null): void
|
||||
{
|
||||
if (method_exists($this, $name)) {
|
||||
throw new Exception('Cannot override an existing framework method.');
|
||||
}
|
||||
|
||||
$this->loader->register($name, $class, $params, $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a pre-filter to a method.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param callback $callback Callback function
|
||||
*/
|
||||
public function before(string $name, callable $callback): void
|
||||
{
|
||||
$this->dispatcher->hook($name, 'before', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a post-filter to a method.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param callback $callback Callback function
|
||||
*/
|
||||
public function after(string $name, callable $callback): void
|
||||
{
|
||||
$this->dispatcher->hook($name, 'after', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a variable.
|
||||
*
|
||||
* @param string|null $key Key
|
||||
*
|
||||
* @return array|mixed|null
|
||||
*/
|
||||
public function get(?string $key = null)
|
||||
{
|
||||
if (null === $key) {
|
||||
return $this->vars;
|
||||
}
|
||||
|
||||
return $this->vars[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a variable.
|
||||
*
|
||||
* @param mixed $key Key
|
||||
* @param mixed|null $value Value
|
||||
*/
|
||||
public function set($key, $value = null): void
|
||||
{
|
||||
if (\is_array($key) || \is_object($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
$this->vars[$k] = $v;
|
||||
}
|
||||
} else {
|
||||
$this->vars[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a variable has been set.
|
||||
*
|
||||
* @param string $key Key
|
||||
*
|
||||
* @return bool Variable status
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->vars[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a variable. If no key is passed in, clear all variables.
|
||||
*
|
||||
* @param string|null $key Key
|
||||
*/
|
||||
public function clear(?string $key = null): void
|
||||
{
|
||||
if (null === $key) {
|
||||
$this->vars = [];
|
||||
} else {
|
||||
unset($this->vars[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a path for class autoloading.
|
||||
*
|
||||
* @param string $dir Directory path
|
||||
*/
|
||||
public function path(string $dir): void
|
||||
{
|
||||
$this->loader->addDirectory($dir);
|
||||
}
|
||||
|
||||
// Extensible Methods
|
||||
|
||||
/**
|
||||
* Starts the framework.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _start(): void
|
||||
{
|
||||
$dispatched = false;
|
||||
$self = $this;
|
||||
$request = $this->request();
|
||||
$response = $this->response();
|
||||
$router = $this->router();
|
||||
|
||||
// Allow filters to run
|
||||
$this->after('start', function () use ($self) {
|
||||
$self->stop();
|
||||
});
|
||||
|
||||
// Flush any existing output
|
||||
if (ob_get_length() > 0) {
|
||||
$response->write(ob_get_clean());
|
||||
}
|
||||
|
||||
// Enable output buffering
|
||||
ob_start();
|
||||
|
||||
// Route the request
|
||||
while ($route = $router->route($request)) {
|
||||
$params = array_values($route->params);
|
||||
|
||||
// Add route info to the parameter list
|
||||
if ($route->pass) {
|
||||
$params[] = $route;
|
||||
}
|
||||
|
||||
// Call route handler
|
||||
$continue = $this->dispatcher->execute(
|
||||
$route->callback,
|
||||
$params
|
||||
);
|
||||
|
||||
$dispatched = true;
|
||||
|
||||
if (!$continue) {
|
||||
break;
|
||||
}
|
||||
|
||||
$router->next();
|
||||
|
||||
$dispatched = false;
|
||||
}
|
||||
|
||||
if (!$dispatched) {
|
||||
$this->notFound();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP 500 response for any errors.
|
||||
*
|
||||
* @param Throwable $e Thrown exception
|
||||
*/
|
||||
public function _error($e): void
|
||||
{
|
||||
$msg = sprintf('<h1>500 Internal Server Error</h1>' .
|
||||
'<h3>%s (%s)</h3>' .
|
||||
'<pre>%s</pre>',
|
||||
$e->getMessage(),
|
||||
$e->getCode(),
|
||||
$e->getTraceAsString()
|
||||
);
|
||||
|
||||
try {
|
||||
$this->response()
|
||||
->clear()
|
||||
->status(500)
|
||||
->write($msg)
|
||||
->send();
|
||||
} catch (Throwable $t) {
|
||||
exit($msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the framework and outputs the current response.
|
||||
*
|
||||
* @param int|null $code HTTP status code
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _stop(?int $code = null): void
|
||||
{
|
||||
$response = $this->response();
|
||||
|
||||
if (!$response->sent()) {
|
||||
if (null !== $code) {
|
||||
$response->status($code);
|
||||
}
|
||||
|
||||
$response->write(ob_get_clean());
|
||||
|
||||
$response->send();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a URL to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callback $callback Callback function
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
*/
|
||||
public function _route(string $pattern, callable $callback, bool $pass_route = false): void
|
||||
{
|
||||
$this->router()->map($pattern, $callback, $pass_route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a URL to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callback $callback Callback function
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
*/
|
||||
public function _post(string $pattern, callable $callback, bool $pass_route = false): void
|
||||
{
|
||||
$this->router()->map('POST ' . $pattern, $callback, $pass_route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a URL to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callback $callback Callback function
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
*/
|
||||
public function _put(string $pattern, callable $callback, bool $pass_route = false): void
|
||||
{
|
||||
$this->router()->map('PUT ' . $pattern, $callback, $pass_route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a URL to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callback $callback Callback function
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
*/
|
||||
public function _patch(string $pattern, callable $callback, bool $pass_route = false): void
|
||||
{
|
||||
$this->router()->map('PATCH ' . $pattern, $callback, $pass_route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes a URL to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callback $callback Callback function
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
*/
|
||||
public function _delete(string $pattern, callable $callback, bool $pass_route = false): void
|
||||
{
|
||||
$this->router()->map('DELETE ' . $pattern, $callback, $pass_route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops processing and returns a given response.
|
||||
*
|
||||
* @param int $code HTTP status code
|
||||
* @param string $message Response message
|
||||
*/
|
||||
public function _halt(int $code = 200, string $message = ''): void
|
||||
{
|
||||
$this->response()
|
||||
->clear()
|
||||
->status($code)
|
||||
->write($message)
|
||||
->send();
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends an HTTP 404 response when a URL is not found.
|
||||
*/
|
||||
public function _notFound(): void
|
||||
{
|
||||
$this->response()
|
||||
->clear()
|
||||
->status(404)
|
||||
->write(
|
||||
'<h1>404 Not Found</h1>' .
|
||||
'<h3>The page you have requested could not be found.</h3>' .
|
||||
str_repeat(' ', 512)
|
||||
)
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirects the current request to another URL.
|
||||
*
|
||||
* @param string $url URL
|
||||
* @param int $code HTTP status code
|
||||
*/
|
||||
public function _redirect(string $url, int $code = 303): void
|
||||
{
|
||||
$base = $this->get('flight.base_url');
|
||||
|
||||
if (null === $base) {
|
||||
$base = $this->request()->base;
|
||||
}
|
||||
|
||||
// Append base url to redirect url
|
||||
if ('/' !== $base && false === strpos($url, '://')) {
|
||||
$url = $base . preg_replace('#/+#', '/', '/' . $url);
|
||||
}
|
||||
|
||||
$this->response()
|
||||
->clear()
|
||||
->status($code)
|
||||
->header('Location', $url)
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a template.
|
||||
*
|
||||
* @param string $file Template file
|
||||
* @param array|null $data Template data
|
||||
* @param string|null $key View variable name
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _render(string $file, ?array $data = null, ?string $key = null): void
|
||||
{
|
||||
if (null !== $key) {
|
||||
$this->view()->set($key, $this->view()->fetch($file, $data));
|
||||
} else {
|
||||
$this->view()->render($file, $data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a JSON response.
|
||||
*
|
||||
* @param mixed $data JSON data
|
||||
* @param int $code HTTP status code
|
||||
* @param bool $encode Whether to perform JSON encoding
|
||||
* @param string $charset Charset
|
||||
* @param int $option Bitmask Json constant such as JSON_HEX_QUOT
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _json(
|
||||
$data,
|
||||
int $code = 200,
|
||||
bool $encode = true,
|
||||
string $charset = 'utf-8',
|
||||
int $option = 0
|
||||
): void {
|
||||
$json = $encode ? json_encode($data, $option) : $data;
|
||||
|
||||
$this->response()
|
||||
->status($code)
|
||||
->header('Content-Type', 'application/json; charset=' . $charset)
|
||||
->write($json)
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a JSONP response.
|
||||
*
|
||||
* @param mixed $data JSON data
|
||||
* @param string $param Query parameter that specifies the callback name.
|
||||
* @param int $code HTTP status code
|
||||
* @param bool $encode Whether to perform JSON encoding
|
||||
* @param string $charset Charset
|
||||
* @param int $option Bitmask Json constant such as JSON_HEX_QUOT
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _jsonp(
|
||||
$data,
|
||||
string $param = 'jsonp',
|
||||
int $code = 200,
|
||||
bool $encode = true,
|
||||
string $charset = 'utf-8',
|
||||
int $option = 0
|
||||
): void {
|
||||
$json = $encode ? json_encode($data, $option) : $data;
|
||||
|
||||
$callback = $this->request()->query[$param];
|
||||
|
||||
$this->response()
|
||||
->status($code)
|
||||
->header('Content-Type', 'application/javascript; charset=' . $charset)
|
||||
->write($callback . '(' . $json . ');')
|
||||
->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles ETag HTTP caching.
|
||||
*
|
||||
* @param string $id ETag identifier
|
||||
* @param string $type ETag type
|
||||
*/
|
||||
public function _etag(string $id, string $type = 'strong'): void
|
||||
{
|
||||
$id = (('weak' === $type) ? 'W/' : '') . $id;
|
||||
|
||||
$this->response()->header('ETag', $id);
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
|
||||
$_SERVER['HTTP_IF_NONE_MATCH'] === $id) {
|
||||
$this->halt(304);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles last modified HTTP caching.
|
||||
*
|
||||
* @param int $time Unix timestamp
|
||||
*/
|
||||
public function _lastModified(int $time): void
|
||||
{
|
||||
$this->response()->header('Last-Modified', gmdate('D, d M Y H:i:s \G\M\T', $time));
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) &&
|
||||
strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) === $time) {
|
||||
$this->halt(304);
|
||||
}
|
||||
}
|
||||
}
|
117
libs/flight2/Flight.php
Normal file
117
libs/flight2/Flight.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
use flight\core\Dispatcher;
|
||||
use flight\Engine;
|
||||
use flight\net\Request;
|
||||
use flight\net\Response;
|
||||
use flight\net\Router;
|
||||
use flight\template\View;
|
||||
|
||||
/**
|
||||
* The Flight class is a static representation of the framework.
|
||||
*
|
||||
* Core.
|
||||
*
|
||||
* @method static void start() Starts the framework.
|
||||
* @method static void path($path) Adds a path for autoloading classes.
|
||||
* @method static void stop() Stops the framework and sends a response.
|
||||
* @method static void halt($code = 200, $message = '') Stop the framework with an optional status code and message.
|
||||
*
|
||||
* Routing.
|
||||
* @method static void route($pattern, $callback) Maps a URL pattern to a callback.
|
||||
* @method static Router router() Returns Router instance.
|
||||
*
|
||||
* Extending & Overriding.
|
||||
* @method static void map($name, $callback) Creates a custom framework method.
|
||||
* @method static void register($name, $class, array $params = array(), $callback = null) Registers a class to a framework method.
|
||||
*
|
||||
* Filtering.
|
||||
* @method static void before($name, $callback) Adds a filter before a framework method.
|
||||
* @method static void after($name, $callback) Adds a filter after a framework method.
|
||||
*
|
||||
* Variables.
|
||||
* @method static void set($key, $value) Sets a variable.
|
||||
* @method static mixed get($key) Gets a variable.
|
||||
* @method static bool has($key) Checks if a variable is set.
|
||||
* @method static void clear($key = null) Clears a variable.
|
||||
*
|
||||
* Views.
|
||||
* @method static void render($file, array $data = null, $key = null) Renders a template file.
|
||||
* @method static View view() Returns View instance.
|
||||
*
|
||||
* Request & Response.
|
||||
* @method static Request request() Returns Request instance.
|
||||
* @method static Response response() Returns Response instance.
|
||||
* @method static void redirect($url, $code = 303) Redirects to another URL.
|
||||
* @method static void json($data, $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSON response.
|
||||
* @method static void jsonp($data, $param = 'jsonp', $code = 200, $encode = true, $charset = "utf8", $encodeOption = 0, $encodeDepth = 512) Sends a JSONP response.
|
||||
* @method static void error($exception) Sends an HTTP 500 response.
|
||||
* @method static void notFound() Sends an HTTP 404 response.
|
||||
*
|
||||
* HTTP Caching.
|
||||
* @method static void etag($id, $type = 'strong') Performs ETag HTTP caching.
|
||||
* @method static void lastModified($time) Performs last modified HTTP caching.
|
||||
*/
|
||||
class Flight
|
||||
{
|
||||
/**
|
||||
* Framework engine.
|
||||
*/
|
||||
private static Engine $engine;
|
||||
|
||||
// Don't allow object instantiation
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
private function __destruct()
|
||||
{
|
||||
}
|
||||
|
||||
private function __clone()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles calls to static methods.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param array $params Method parameters
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return mixed Callback results
|
||||
*/
|
||||
public static function __callStatic(string $name, array $params)
|
||||
{
|
||||
$app = self::app();
|
||||
|
||||
return Dispatcher::invokeMethod([$app, $name], $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Engine Application instance
|
||||
*/
|
||||
public static function app(): Engine
|
||||
{
|
||||
static $initialized = false;
|
||||
|
||||
if (!$initialized) {
|
||||
require_once __DIR__ . '/autoload.php';
|
||||
|
||||
self::$engine = new Engine();
|
||||
|
||||
$initialized = true;
|
||||
}
|
||||
|
||||
return self::$engine;
|
||||
}
|
||||
}
|
21
libs/flight2/LICENSE
Normal file
21
libs/flight2/LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2011 Mike Cao <mike@mikecao.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
920
libs/flight2/README.md
Normal file
920
libs/flight2/README.md
Normal file
@ -0,0 +1,920 @@
|
||||
# What is Flight?
|
||||
|
||||
Flight is a fast, simple, extensible framework for PHP. Flight enables you to
|
||||
quickly and easily build RESTful web applications.
|
||||
|
||||
```php
|
||||
require 'flight/Flight.php';
|
||||
|
||||
Flight::route('/', function(){
|
||||
echo 'hello world!';
|
||||
});
|
||||
|
||||
Flight::start();
|
||||
```
|
||||
|
||||
[Learn more](http://flightphp.com/learn)
|
||||
|
||||
# Requirements
|
||||
|
||||
Flight requires `PHP 7.4` or greater.
|
||||
|
||||
# License
|
||||
|
||||
Flight is released under the [MIT](http://flightphp.com/license) license.
|
||||
|
||||
# Installation
|
||||
|
||||
1\. Download the files.
|
||||
|
||||
If you're using [Composer](https://getcomposer.org/), you can run the following command:
|
||||
|
||||
```
|
||||
composer require mikecao/flight
|
||||
```
|
||||
|
||||
OR you can [download](https://github.com/mikecao/flight/archive/master.zip) them directly
|
||||
and extract them to your web directory.
|
||||
|
||||
2\. Configure your webserver.
|
||||
|
||||
For *Apache*, edit your `.htaccess` file with the following:
|
||||
|
||||
```
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^(.*)$ index.php [QSA,L]
|
||||
```
|
||||
|
||||
**Note**: If you need to use flight in a subdirectory add the line `RewriteBase /subdir/` just after `RewriteEngine On`.
|
||||
|
||||
For *Nginx*, add the following to your server declaration:
|
||||
|
||||
```
|
||||
server {
|
||||
location / {
|
||||
try_files $uri $uri/ /index.php;
|
||||
}
|
||||
}
|
||||
```
|
||||
3\. Create your `index.php` file.
|
||||
|
||||
First include the framework.
|
||||
|
||||
```php
|
||||
require 'flight/Flight.php';
|
||||
```
|
||||
|
||||
If you're using Composer, run the autoloader instead.
|
||||
|
||||
```php
|
||||
require 'vendor/autoload.php';
|
||||
```
|
||||
|
||||
Then define a route and assign a function to handle the request.
|
||||
|
||||
```php
|
||||
Flight::route('/', function(){
|
||||
echo 'hello world!';
|
||||
});
|
||||
```
|
||||
|
||||
Finally, start the framework.
|
||||
|
||||
```php
|
||||
Flight::start();
|
||||
```
|
||||
|
||||
# Routing
|
||||
|
||||
Routing in Flight is done by matching a URL pattern with a callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/', function(){
|
||||
echo 'hello world!';
|
||||
});
|
||||
```
|
||||
|
||||
The callback can be any object that is callable. So you can use a regular function:
|
||||
|
||||
```php
|
||||
function hello(){
|
||||
echo 'hello world!';
|
||||
}
|
||||
|
||||
Flight::route('/', 'hello');
|
||||
```
|
||||
|
||||
Or a class method:
|
||||
|
||||
```php
|
||||
class Greeting {
|
||||
public static function hello() {
|
||||
echo 'hello world!';
|
||||
}
|
||||
}
|
||||
|
||||
Flight::route('/', array('Greeting', 'hello'));
|
||||
```
|
||||
|
||||
Or an object method:
|
||||
|
||||
```php
|
||||
class Greeting
|
||||
{
|
||||
public function __construct() {
|
||||
$this->name = 'John Doe';
|
||||
}
|
||||
|
||||
public function hello() {
|
||||
echo "Hello, {$this->name}!";
|
||||
}
|
||||
}
|
||||
|
||||
$greeting = new Greeting();
|
||||
|
||||
Flight::route('/', array($greeting, 'hello'));
|
||||
```
|
||||
|
||||
Routes are matched in the order they are defined. The first route to match a
|
||||
request will be invoked.
|
||||
|
||||
## Method Routing
|
||||
|
||||
By default, route patterns are matched against all request methods. You can respond
|
||||
to specific methods by placing an identifier before the URL.
|
||||
|
||||
```php
|
||||
Flight::route('GET /', function(){
|
||||
echo 'I received a GET request.';
|
||||
});
|
||||
|
||||
Flight::route('POST /', function(){
|
||||
echo 'I received a POST request.';
|
||||
});
|
||||
```
|
||||
|
||||
You can also map multiple methods to a single callback by using a `|` delimiter:
|
||||
|
||||
```php
|
||||
Flight::route('GET|POST /', function(){
|
||||
echo 'I received either a GET or a POST request.';
|
||||
});
|
||||
```
|
||||
|
||||
## Regular Expressions
|
||||
|
||||
You can use regular expressions in your routes:
|
||||
|
||||
```php
|
||||
Flight::route('/user/[0-9]+', function(){
|
||||
// This will match /user/1234
|
||||
});
|
||||
```
|
||||
|
||||
## Named Parameters
|
||||
|
||||
You can specify named parameters in your routes which will be passed along to
|
||||
your callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/@name/@id', function($name, $id){
|
||||
echo "hello, $name ($id)!";
|
||||
});
|
||||
```
|
||||
|
||||
You can also include regular expressions with your named parameters by using
|
||||
the `:` delimiter:
|
||||
|
||||
```php
|
||||
Flight::route('/@name/@id:[0-9]{3}', function($name, $id){
|
||||
// This will match /bob/123
|
||||
// But will not match /bob/12345
|
||||
});
|
||||
```
|
||||
|
||||
Matching regex groups `()` with named parameters isn't supported.
|
||||
|
||||
## Optional Parameters
|
||||
|
||||
You can specify named parameters that are optional for matching by wrapping
|
||||
segments in parentheses.
|
||||
|
||||
```php
|
||||
Flight::route('/blog(/@year(/@month(/@day)))', function($year, $month, $day){
|
||||
// This will match the following URLS:
|
||||
// /blog/2012/12/10
|
||||
// /blog/2012/12
|
||||
// /blog/2012
|
||||
// /blog
|
||||
});
|
||||
```
|
||||
|
||||
Any optional parameters that are not matched will be passed in as NULL.
|
||||
|
||||
## Wildcards
|
||||
|
||||
Matching is only done on individual URL segments. If you want to match multiple
|
||||
segments you can use the `*` wildcard.
|
||||
|
||||
```php
|
||||
Flight::route('/blog/*', function(){
|
||||
// This will match /blog/2000/02/01
|
||||
});
|
||||
```
|
||||
|
||||
To route all requests to a single callback, you can do:
|
||||
|
||||
```php
|
||||
Flight::route('*', function(){
|
||||
// Do something
|
||||
});
|
||||
```
|
||||
|
||||
## Passing
|
||||
|
||||
You can pass execution on to the next matching route by returning `true` from
|
||||
your callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/user/@name', function($name){
|
||||
// Check some condition
|
||||
if ($name != "Bob") {
|
||||
// Continue to next route
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Flight::route('/user/*', function(){
|
||||
// This will get called
|
||||
});
|
||||
```
|
||||
|
||||
## Route Info
|
||||
|
||||
If you want to inspect the matching route information, you can request for the route
|
||||
object to be passed to your callback by passing in `true` as the third parameter in
|
||||
the route method. The route object will always be the last parameter passed to your
|
||||
callback function.
|
||||
|
||||
```php
|
||||
Flight::route('/', function($route){
|
||||
// Array of HTTP methods matched against
|
||||
$route->methods;
|
||||
|
||||
// Array of named parameters
|
||||
$route->params;
|
||||
|
||||
// Matching regular expression
|
||||
$route->regex;
|
||||
|
||||
// Contains the contents of any '*' used in the URL pattern
|
||||
$route->splat;
|
||||
}, true);
|
||||
```
|
||||
|
||||
# Extending
|
||||
|
||||
Flight is designed to be an extensible framework. The framework comes with a set
|
||||
of default methods and components, but it allows you to map your own methods,
|
||||
register your own classes, or even override existing classes and methods.
|
||||
|
||||
## Mapping Methods
|
||||
|
||||
To map your own custom method, you use the `map` function:
|
||||
|
||||
```php
|
||||
// Map your method
|
||||
Flight::map('hello', function($name){
|
||||
echo "hello $name!";
|
||||
});
|
||||
|
||||
// Call your custom method
|
||||
Flight::hello('Bob');
|
||||
```
|
||||
|
||||
## Registering Classes
|
||||
|
||||
To register your own class, you use the `register` function:
|
||||
|
||||
```php
|
||||
// Register your class
|
||||
Flight::register('user', 'User');
|
||||
|
||||
// Get an instance of your class
|
||||
$user = Flight::user();
|
||||
```
|
||||
|
||||
The register method also allows you to pass along parameters to your class
|
||||
constructor. So when you load your custom class, it will come pre-initialized.
|
||||
You can define the constructor parameters by passing in an additional array.
|
||||
Here's an example of loading a database connection:
|
||||
|
||||
```php
|
||||
// Register class with constructor parameters
|
||||
Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'));
|
||||
|
||||
// Get an instance of your class
|
||||
// This will create an object with the defined parameters
|
||||
//
|
||||
// new PDO('mysql:host=localhost;dbname=test','user','pass');
|
||||
//
|
||||
$db = Flight::db();
|
||||
```
|
||||
|
||||
If you pass in an additional callback parameter, it will be executed immediately
|
||||
after class construction. This allows you to perform any set up procedures for your
|
||||
new object. The callback function takes one parameter, an instance of the new object.
|
||||
|
||||
```php
|
||||
// The callback will be passed the object that was constructed
|
||||
Flight::register('db', 'PDO', array('mysql:host=localhost;dbname=test','user','pass'), function($db){
|
||||
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
});
|
||||
```
|
||||
|
||||
By default, every time you load your class you will get a shared instance.
|
||||
To get a new instance of a class, simply pass in `false` as a parameter:
|
||||
|
||||
```php
|
||||
// Shared instance of the class
|
||||
$shared = Flight::db();
|
||||
|
||||
// New instance of the class
|
||||
$new = Flight::db(false);
|
||||
```
|
||||
|
||||
Keep in mind that mapped methods have precedence over registered classes. If you
|
||||
declare both using the same name, only the mapped method will be invoked.
|
||||
|
||||
# Overriding
|
||||
|
||||
Flight allows you to override its default functionality to suit your own needs,
|
||||
without having to modify any code.
|
||||
|
||||
For example, when Flight cannot match a URL to a route, it invokes the `notFound`
|
||||
method which sends a generic `HTTP 404` response. You can override this behavior
|
||||
by using the `map` method:
|
||||
|
||||
```php
|
||||
Flight::map('notFound', function(){
|
||||
// Display custom 404 page
|
||||
include 'errors/404.html';
|
||||
});
|
||||
```
|
||||
|
||||
Flight also allows you to replace core components of the framework.
|
||||
For example you can replace the default Router class with your own custom class:
|
||||
|
||||
```php
|
||||
// Register your custom class
|
||||
Flight::register('router', 'MyRouter');
|
||||
|
||||
// When Flight loads the Router instance, it will load your class
|
||||
$myrouter = Flight::router();
|
||||
```
|
||||
|
||||
Framework methods like `map` and `register` however cannot be overridden. You will
|
||||
get an error if you try to do so.
|
||||
|
||||
# Filtering
|
||||
|
||||
Flight allows you to filter methods before and after they are called. There are no
|
||||
predefined hooks you need to memorize. You can filter any of the default framework
|
||||
methods as well as any custom methods that you've mapped.
|
||||
|
||||
A filter function looks like this:
|
||||
|
||||
```php
|
||||
function(&$params, &$output) {
|
||||
// Filter code
|
||||
}
|
||||
```
|
||||
|
||||
Using the passed in variables you can manipulate the input parameters and/or the output.
|
||||
|
||||
You can have a filter run before a method by doing:
|
||||
|
||||
```php
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
// Do something
|
||||
});
|
||||
```
|
||||
|
||||
You can have a filter run after a method by doing:
|
||||
|
||||
```php
|
||||
Flight::after('start', function(&$params, &$output){
|
||||
// Do something
|
||||
});
|
||||
```
|
||||
|
||||
You can add as many filters as you want to any method. They will be called in the
|
||||
order that they are declared.
|
||||
|
||||
Here's an example of the filtering process:
|
||||
|
||||
```php
|
||||
// Map a custom method
|
||||
Flight::map('hello', function($name){
|
||||
return "Hello, $name!";
|
||||
});
|
||||
|
||||
// Add a before filter
|
||||
Flight::before('hello', function(&$params, &$output){
|
||||
// Manipulate the parameter
|
||||
$params[0] = 'Fred';
|
||||
});
|
||||
|
||||
// Add an after filter
|
||||
Flight::after('hello', function(&$params, &$output){
|
||||
// Manipulate the output
|
||||
$output .= " Have a nice day!";
|
||||
});
|
||||
|
||||
// Invoke the custom method
|
||||
echo Flight::hello('Bob');
|
||||
```
|
||||
|
||||
This should display:
|
||||
|
||||
Hello Fred! Have a nice day!
|
||||
|
||||
If you have defined multiple filters, you can break the chain by returning `false`
|
||||
in any of your filter functions:
|
||||
|
||||
```php
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
echo 'one';
|
||||
});
|
||||
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
echo 'two';
|
||||
|
||||
// This will end the chain
|
||||
return false;
|
||||
});
|
||||
|
||||
// This will not get called
|
||||
Flight::before('start', function(&$params, &$output){
|
||||
echo 'three';
|
||||
});
|
||||
```
|
||||
|
||||
Note, core methods such as `map` and `register` cannot be filtered because they
|
||||
are called directly and not invoked dynamically.
|
||||
|
||||
# Variables
|
||||
|
||||
Flight allows you to save variables so that they can be used anywhere in your application.
|
||||
|
||||
```php
|
||||
// Save your variable
|
||||
Flight::set('id', 123);
|
||||
|
||||
// Elsewhere in your application
|
||||
$id = Flight::get('id');
|
||||
```
|
||||
To see if a variable has been set you can do:
|
||||
|
||||
```php
|
||||
if (Flight::has('id')) {
|
||||
// Do something
|
||||
}
|
||||
```
|
||||
|
||||
You can clear a variable by doing:
|
||||
|
||||
```php
|
||||
// Clears the id variable
|
||||
Flight::clear('id');
|
||||
|
||||
// Clears all variables
|
||||
Flight::clear();
|
||||
```
|
||||
|
||||
Flight also uses variables for configuration purposes.
|
||||
|
||||
```php
|
||||
Flight::set('flight.log_errors', true);
|
||||
```
|
||||
|
||||
# Views
|
||||
|
||||
Flight provides some basic templating functionality by default. To display a view
|
||||
template call the `render` method with the name of the template file and optional
|
||||
template data:
|
||||
|
||||
```php
|
||||
Flight::render('hello.php', array('name' => 'Bob'));
|
||||
```
|
||||
|
||||
The template data you pass in is automatically injected into the template and can
|
||||
be reference like a local variable. Template files are simply PHP files. If the
|
||||
content of the `hello.php` template file is:
|
||||
|
||||
```php
|
||||
Hello, '<?php echo $name; ?>'!
|
||||
```
|
||||
|
||||
The output would be:
|
||||
|
||||
Hello, Bob!
|
||||
|
||||
You can also manually set view variables by using the set method:
|
||||
|
||||
```php
|
||||
Flight::view()->set('name', 'Bob');
|
||||
```
|
||||
|
||||
The variable `name` is now available across all your views. So you can simply do:
|
||||
|
||||
```php
|
||||
Flight::render('hello');
|
||||
```
|
||||
|
||||
Note that when specifying the name of the template in the render method, you can
|
||||
leave out the `.php` extension.
|
||||
|
||||
By default Flight will look for a `views` directory for template files. You can
|
||||
set an alternate path for your templates by setting the following config:
|
||||
|
||||
```php
|
||||
Flight::set('flight.views.path', '/path/to/views');
|
||||
```
|
||||
|
||||
## Layouts
|
||||
|
||||
It is common for websites to have a single layout template file with interchanging
|
||||
content. To render content to be used in a layout, you can pass in an optional
|
||||
parameter to the `render` method.
|
||||
|
||||
```php
|
||||
Flight::render('header', array('heading' => 'Hello'), 'header_content');
|
||||
Flight::render('body', array('body' => 'World'), 'body_content');
|
||||
```
|
||||
|
||||
Your view will then have saved variables called `header_content` and `body_content`.
|
||||
You can then render your layout by doing:
|
||||
|
||||
```php
|
||||
Flight::render('layout', array('title' => 'Home Page'));
|
||||
```
|
||||
|
||||
If the template files looks like this:
|
||||
|
||||
`header.php`:
|
||||
|
||||
```php
|
||||
<h1><?php echo $heading; ?></h1>
|
||||
```
|
||||
|
||||
`body.php`:
|
||||
|
||||
```php
|
||||
<div><?php echo $body; ?></div>
|
||||
```
|
||||
|
||||
`layout.php`:
|
||||
|
||||
```php
|
||||
<html>
|
||||
<head>
|
||||
<title><?php echo $title; ?></title>
|
||||
</head>
|
||||
<body>
|
||||
<?php echo $header_content; ?>
|
||||
<?php echo $body_content; ?>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
The output would be:
|
||||
```html
|
||||
<html>
|
||||
<head>
|
||||
<title>Home Page</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello</h1>
|
||||
<div>World</div>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
## Custom Views
|
||||
|
||||
Flight allows you to swap out the default view engine simply by registering your
|
||||
own view class. Here's how you would use the [Smarty](http://www.smarty.net/)
|
||||
template engine for your views:
|
||||
|
||||
```php
|
||||
// Load Smarty library
|
||||
require './Smarty/libs/Smarty.class.php';
|
||||
|
||||
// Register Smarty as the view class
|
||||
// Also pass a callback function to configure Smarty on load
|
||||
Flight::register('view', 'Smarty', array(), function($smarty){
|
||||
$smarty->template_dir = './templates/';
|
||||
$smarty->compile_dir = './templates_c/';
|
||||
$smarty->config_dir = './config/';
|
||||
$smarty->cache_dir = './cache/';
|
||||
});
|
||||
|
||||
// Assign template data
|
||||
Flight::view()->assign('name', 'Bob');
|
||||
|
||||
// Display the template
|
||||
Flight::view()->display('hello.tpl');
|
||||
```
|
||||
|
||||
For completeness, you should also override Flight's default render method:
|
||||
|
||||
```php
|
||||
Flight::map('render', function($template, $data){
|
||||
Flight::view()->assign($data);
|
||||
Flight::view()->display($template);
|
||||
});
|
||||
```
|
||||
# Error Handling
|
||||
|
||||
## Errors and Exceptions
|
||||
|
||||
All errors and exceptions are caught by Flight and passed to the `error` method.
|
||||
The default behavior is to send a generic `HTTP 500 Internal Server Error`
|
||||
response with some error information.
|
||||
|
||||
You can override this behavior for your own needs:
|
||||
|
||||
```php
|
||||
Flight::map('error', function(Exception $ex){
|
||||
// Handle error
|
||||
echo $ex->getTraceAsString();
|
||||
});
|
||||
```
|
||||
|
||||
By default errors are not logged to the web server. You can enable this by
|
||||
changing the config:
|
||||
|
||||
```php
|
||||
Flight::set('flight.log_errors', true);
|
||||
```
|
||||
|
||||
## Not Found
|
||||
|
||||
When a URL can't be found, Flight calls the `notFound` method. The default
|
||||
behavior is to send an `HTTP 404 Not Found` response with a simple message.
|
||||
|
||||
You can override this behavior for your own needs:
|
||||
|
||||
```php
|
||||
Flight::map('notFound', function(){
|
||||
// Handle not found
|
||||
});
|
||||
```
|
||||
|
||||
# Redirects
|
||||
|
||||
You can redirect the current request by using the `redirect` method and passing
|
||||
in a new URL:
|
||||
|
||||
```php
|
||||
Flight::redirect('/new/location');
|
||||
```
|
||||
|
||||
By default Flight sends a HTTP 303 status code. You can optionally set a
|
||||
custom code:
|
||||
|
||||
```php
|
||||
Flight::redirect('/new/location', 401);
|
||||
```
|
||||
|
||||
# Requests
|
||||
|
||||
Flight encapsulates the HTTP request into a single object, which can be
|
||||
accessed by doing:
|
||||
|
||||
```php
|
||||
$request = Flight::request();
|
||||
```
|
||||
|
||||
The request object provides the following properties:
|
||||
|
||||
```
|
||||
url - The URL being requested
|
||||
base - The parent subdirectory of the URL
|
||||
method - The request method (GET, POST, PUT, DELETE)
|
||||
referrer - The referrer URL
|
||||
ip - IP address of the client
|
||||
ajax - Whether the request is an AJAX request
|
||||
scheme - The server protocol (http, https)
|
||||
user_agent - Browser information
|
||||
type - The content type
|
||||
length - The content length
|
||||
query - Query string parameters
|
||||
data - Post data or JSON data
|
||||
cookies - Cookie data
|
||||
files - Uploaded files
|
||||
secure - Whether the connection is secure
|
||||
accept - HTTP accept parameters
|
||||
proxy_ip - Proxy IP address of the client
|
||||
host - The request host name
|
||||
```
|
||||
|
||||
You can access the `query`, `data`, `cookies`, and `files` properties
|
||||
as arrays or objects.
|
||||
|
||||
So, to get a query string parameter, you can do:
|
||||
|
||||
```php
|
||||
$id = Flight::request()->query['id'];
|
||||
```
|
||||
|
||||
Or you can do:
|
||||
|
||||
```php
|
||||
$id = Flight::request()->query->id;
|
||||
```
|
||||
|
||||
## RAW Request Body
|
||||
|
||||
To get the raw HTTP request body, for example when dealing with PUT requests, you can do:
|
||||
|
||||
```php
|
||||
$body = Flight::request()->getBody();
|
||||
```
|
||||
|
||||
## JSON Input
|
||||
|
||||
If you send a request with the type `application/json` and the data `{"id": 123}` it will be available
|
||||
from the `data` property:
|
||||
|
||||
```php
|
||||
$id = Flight::request()->data->id;
|
||||
```
|
||||
|
||||
# HTTP Caching
|
||||
|
||||
Flight provides built-in support for HTTP level caching. If the caching condition
|
||||
is met, Flight will return an HTTP `304 Not Modified` response. The next time the
|
||||
client requests the same resource, they will be prompted to use their locally
|
||||
cached version.
|
||||
|
||||
## Last-Modified
|
||||
|
||||
You can use the `lastModified` method and pass in a UNIX timestamp to set the date
|
||||
and time a page was last modified. The client will continue to use their cache until
|
||||
the last modified value is changed.
|
||||
|
||||
```php
|
||||
Flight::route('/news', function(){
|
||||
Flight::lastModified(1234567890);
|
||||
echo 'This content will be cached.';
|
||||
});
|
||||
```
|
||||
|
||||
## ETag
|
||||
|
||||
`ETag` caching is similar to `Last-Modified`, except you can specify any id you
|
||||
want for the resource:
|
||||
|
||||
```php
|
||||
Flight::route('/news', function(){
|
||||
Flight::etag('my-unique-id');
|
||||
echo 'This content will be cached.';
|
||||
});
|
||||
```
|
||||
|
||||
Keep in mind that calling either `lastModified` or `etag` will both set and check the
|
||||
cache value. If the cache value is the same between requests, Flight will immediately
|
||||
send an `HTTP 304` response and stop processing.
|
||||
|
||||
# Stopping
|
||||
|
||||
You can stop the framework at any point by calling the `halt` method:
|
||||
|
||||
```php
|
||||
Flight::halt();
|
||||
```
|
||||
|
||||
You can also specify an optional `HTTP` status code and message:
|
||||
|
||||
```php
|
||||
Flight::halt(200, 'Be right back...');
|
||||
```
|
||||
|
||||
Calling `halt` will discard any response content up to that point. If you want to stop
|
||||
the framework and output the current response, use the `stop` method:
|
||||
|
||||
```php
|
||||
Flight::stop();
|
||||
```
|
||||
|
||||
# JSON
|
||||
|
||||
Flight provides support for sending JSON and JSONP responses. To send a JSON response you
|
||||
pass some data to be JSON encoded:
|
||||
|
||||
```php
|
||||
Flight::json(array('id' => 123));
|
||||
```
|
||||
|
||||
For JSONP requests you, can optionally pass in the query parameter name you are
|
||||
using to define your callback function:
|
||||
|
||||
```php
|
||||
Flight::jsonp(array('id' => 123), 'q');
|
||||
```
|
||||
|
||||
So, when making a GET request using `?q=my_func`, you should receive the output:
|
||||
|
||||
```
|
||||
my_func({"id":123});
|
||||
```
|
||||
|
||||
If you don't pass in a query parameter name it will default to `jsonp`.
|
||||
|
||||
|
||||
# Configuration
|
||||
|
||||
You can customize certain behaviors of Flight by setting configuration values
|
||||
through the `set` method.
|
||||
|
||||
```php
|
||||
Flight::set('flight.log_errors', true);
|
||||
```
|
||||
|
||||
The following is a list of all the available configuration settings:
|
||||
|
||||
flight.base_url - Override the base url of the request. (default: null)
|
||||
flight.case_sensitive - Case sensitive matching for URLs. (default: false)
|
||||
flight.handle_errors - Allow Flight to handle all errors internally. (default: true)
|
||||
flight.log_errors - Log errors to the web server's error log file. (default: false)
|
||||
flight.views.path - Directory containing view template files. (default: ./views)
|
||||
flight.views.extension - View template file extension. (default: .php)
|
||||
|
||||
# Framework Methods
|
||||
|
||||
Flight is designed to be easy to use and understand. The following is the complete
|
||||
set of methods for the framework. It consists of core methods, which are regular
|
||||
static methods, and extensible methods, which are mapped methods that can be filtered
|
||||
or overridden.
|
||||
|
||||
## Core Methods
|
||||
|
||||
```php
|
||||
Flight::map($name, $callback) // Creates a custom framework method.
|
||||
Flight::register($name, $class, [$params], [$callback]) // Registers a class to a framework method.
|
||||
Flight::before($name, $callback) // Adds a filter before a framework method.
|
||||
Flight::after($name, $callback) // Adds a filter after a framework method.
|
||||
Flight::path($path) // Adds a path for autoloading classes.
|
||||
Flight::get($key) // Gets a variable.
|
||||
Flight::set($key, $value) // Sets a variable.
|
||||
Flight::has($key) // Checks if a variable is set.
|
||||
Flight::clear([$key]) // Clears a variable.
|
||||
Flight::init() // Initializes the framework to its default settings.
|
||||
Flight::app() // Gets the application object instance
|
||||
```
|
||||
|
||||
## Extensible Methods
|
||||
|
||||
```php
|
||||
Flight::start() // Starts the framework.
|
||||
Flight::stop() // Stops the framework and sends a response.
|
||||
Flight::halt([$code], [$message]) // Stop the framework with an optional status code and message.
|
||||
Flight::route($pattern, $callback) // Maps a URL pattern to a callback.
|
||||
Flight::redirect($url, [$code]) // Redirects to another URL.
|
||||
Flight::render($file, [$data], [$key]) // Renders a template file.
|
||||
Flight::error($exception) // Sends an HTTP 500 response.
|
||||
Flight::notFound() // Sends an HTTP 404 response.
|
||||
Flight::etag($id, [$type]) // Performs ETag HTTP caching.
|
||||
Flight::lastModified($time) // Performs last modified HTTP caching.
|
||||
Flight::json($data, [$code], [$encode], [$charset], [$option]) // Sends a JSON response.
|
||||
Flight::jsonp($data, [$param], [$code], [$encode], [$charset], [$option]) // Sends a JSONP response.
|
||||
```
|
||||
|
||||
Any custom methods added with `map` and `register` can also be filtered.
|
||||
|
||||
|
||||
# Framework Instance
|
||||
|
||||
Instead of running Flight as a global static class, you can optionally run it
|
||||
as an object instance.
|
||||
|
||||
```php
|
||||
require 'flight/autoload.php';
|
||||
|
||||
use flight\Engine;
|
||||
|
||||
$app = new Engine();
|
||||
|
||||
$app->route('/', function(){
|
||||
echo 'hello world!';
|
||||
});
|
||||
|
||||
$app->start();
|
||||
```
|
||||
|
||||
So instead of calling the static method, you would call the instance method with
|
||||
the same name on the Engine object.
|
1
libs/flight2/VERSION
Normal file
1
libs/flight2/VERSION
Normal file
@ -0,0 +1 @@
|
||||
2.0.0
|
15
libs/flight2/autoload.php
Normal file
15
libs/flight2/autoload.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2013, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
use flight\core\Loader;
|
||||
|
||||
require_once __DIR__ . '/core/Loader.php';
|
||||
|
||||
Loader::autoload(true, [dirname(__DIR__)]);
|
254
libs/flight2/core/Dispatcher.php
Normal file
254
libs/flight2/core/Dispatcher.php
Normal file
@ -0,0 +1,254 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\core;
|
||||
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* The Dispatcher class is responsible for dispatching events. Events
|
||||
* are simply aliases for class methods or functions. The Dispatcher
|
||||
* allows you to hook other functions to an event that can modify the
|
||||
* input parameters and/or the output.
|
||||
*/
|
||||
class Dispatcher
|
||||
{
|
||||
/**
|
||||
* Mapped events.
|
||||
*/
|
||||
protected array $events = [];
|
||||
|
||||
/**
|
||||
* Method filters.
|
||||
*/
|
||||
protected array $filters = [];
|
||||
|
||||
/**
|
||||
* Dispatches an event.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @param array $params Callback parameters
|
||||
*
|
||||
*@throws Exception
|
||||
*
|
||||
* @return mixed|null Output of callback
|
||||
*/
|
||||
final public function run(string $name, array $params = [])
|
||||
{
|
||||
$output = '';
|
||||
|
||||
// Run pre-filters
|
||||
if (!empty($this->filters[$name]['before'])) {
|
||||
$this->filter($this->filters[$name]['before'], $params, $output);
|
||||
}
|
||||
|
||||
// Run requested method
|
||||
$output = self::execute($this->get($name), $params);
|
||||
|
||||
// Run post-filters
|
||||
if (!empty($this->filters[$name]['after'])) {
|
||||
$this->filter($this->filters[$name]['after'], $params, $output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a callback to an event.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @param callback $callback Callback function
|
||||
*/
|
||||
final public function set(string $name, callable $callback): void
|
||||
{
|
||||
$this->events[$name] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an assigned callback.
|
||||
*
|
||||
* @param string $name Event name
|
||||
*
|
||||
* @return callback $callback Callback function
|
||||
*/
|
||||
final public function get(string $name): ?callable
|
||||
{
|
||||
return $this->events[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an event has been set.
|
||||
*
|
||||
* @param string $name Event name
|
||||
*
|
||||
* @return bool Event status
|
||||
*/
|
||||
final public function has(string $name): bool
|
||||
{
|
||||
return isset($this->events[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears an event. If no name is given,
|
||||
* all events are removed.
|
||||
*
|
||||
* @param string|null $name Event name
|
||||
*/
|
||||
final public function clear(?string $name = null): void
|
||||
{
|
||||
if (null !== $name) {
|
||||
unset($this->events[$name]);
|
||||
unset($this->filters[$name]);
|
||||
} else {
|
||||
$this->events = [];
|
||||
$this->filters = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks a callback to an event.
|
||||
*
|
||||
* @param string $name Event name
|
||||
* @param string $type Filter type
|
||||
* @param callback $callback Callback function
|
||||
*/
|
||||
final public function hook(string $name, string $type, callable $callback): void
|
||||
{
|
||||
$this->filters[$name][$type][] = $callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a chain of method filters.
|
||||
*
|
||||
* @param array $filters Chain of filters
|
||||
* @param array $params Method parameters
|
||||
* @param mixed $output Method output
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
final public function filter(array $filters, array &$params, &$output): void
|
||||
{
|
||||
$args = [&$params, &$output];
|
||||
foreach ($filters as $callback) {
|
||||
$continue = self::execute($callback, $args);
|
||||
if (false === $continue) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a callback function.
|
||||
*
|
||||
* @param array|callback $callback Callback function
|
||||
* @param array $params Function parameters
|
||||
*
|
||||
*@throws Exception
|
||||
*
|
||||
* @return mixed Function results
|
||||
*/
|
||||
public static function execute($callback, array &$params = [])
|
||||
{
|
||||
if (\is_callable($callback)) {
|
||||
return \is_array($callback) ?
|
||||
self::invokeMethod($callback, $params) :
|
||||
self::callFunction($callback, $params);
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid callback specified.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a function.
|
||||
*
|
||||
* @param callable|string $func Name of function to call
|
||||
* @param array $params Function parameters
|
||||
*
|
||||
* @return mixed Function results
|
||||
*/
|
||||
public static function callFunction($func, array &$params = [])
|
||||
{
|
||||
// Call static method
|
||||
if (\is_string($func) && false !== strpos($func, '::')) {
|
||||
return \call_user_func_array($func, $params);
|
||||
}
|
||||
|
||||
switch (\count($params)) {
|
||||
case 0:
|
||||
return $func();
|
||||
case 1:
|
||||
return $func($params[0]);
|
||||
case 2:
|
||||
return $func($params[0], $params[1]);
|
||||
case 3:
|
||||
return $func($params[0], $params[1], $params[2]);
|
||||
case 4:
|
||||
return $func($params[0], $params[1], $params[2], $params[3]);
|
||||
case 5:
|
||||
return $func($params[0], $params[1], $params[2], $params[3], $params[4]);
|
||||
default:
|
||||
return \call_user_func_array($func, $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes a method.
|
||||
*
|
||||
* @param mixed $func Class method
|
||||
* @param array $params Class method parameters
|
||||
*
|
||||
* @return mixed Function results
|
||||
*/
|
||||
public static function invokeMethod($func, array &$params = [])
|
||||
{
|
||||
[$class, $method] = $func;
|
||||
|
||||
$instance = \is_object($class);
|
||||
|
||||
switch (\count($params)) {
|
||||
case 0:
|
||||
return ($instance) ?
|
||||
$class->$method() :
|
||||
$class::$method();
|
||||
case 1:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0]) :
|
||||
$class::$method($params[0]);
|
||||
case 2:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1]) :
|
||||
$class::$method($params[0], $params[1]);
|
||||
case 3:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1], $params[2]) :
|
||||
$class::$method($params[0], $params[1], $params[2]);
|
||||
case 4:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1], $params[2], $params[3]) :
|
||||
$class::$method($params[0], $params[1], $params[2], $params[3]);
|
||||
case 5:
|
||||
return ($instance) ?
|
||||
$class->$method($params[0], $params[1], $params[2], $params[3], $params[4]) :
|
||||
$class::$method($params[0], $params[1], $params[2], $params[3], $params[4]);
|
||||
default:
|
||||
return \call_user_func_array($func, $params);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the object to the initial state.
|
||||
*/
|
||||
final public function reset(): void
|
||||
{
|
||||
$this->events = [];
|
||||
$this->filters = [];
|
||||
}
|
||||
}
|
233
libs/flight2/core/Loader.php
Normal file
233
libs/flight2/core/Loader.php
Normal file
@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\core;
|
||||
|
||||
use Exception;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* The Loader class is responsible for loading objects. It maintains
|
||||
* a list of reusable class instances and can generate a new class
|
||||
* instances with custom initialization parameters. It also performs
|
||||
* class autoloading.
|
||||
*/
|
||||
class Loader
|
||||
{
|
||||
/**
|
||||
* Registered classes.
|
||||
*/
|
||||
protected array $classes = [];
|
||||
|
||||
/**
|
||||
* Class instances.
|
||||
*/
|
||||
protected array $instances = [];
|
||||
|
||||
/**
|
||||
* Autoload directories.
|
||||
*/
|
||||
protected static array $dirs = [];
|
||||
|
||||
/**
|
||||
* Registers a class.
|
||||
*
|
||||
* @param string $name Registry name
|
||||
* @param callable|string $class Class name or function to instantiate class
|
||||
* @param array $params Class initialization parameters
|
||||
* @param callable|null $callback $callback Function to call after object instantiation
|
||||
*/
|
||||
public function register(string $name, $class, array $params = [], ?callable $callback = null): void
|
||||
{
|
||||
unset($this->instances[$name]);
|
||||
|
||||
$this->classes[$name] = [$class, $params, $callback];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters a class.
|
||||
*
|
||||
* @param string $name Registry name
|
||||
*/
|
||||
public function unregister(string $name): void
|
||||
{
|
||||
unset($this->classes[$name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a registered class.
|
||||
*
|
||||
* @param string $name Method name
|
||||
* @param bool $shared Shared instance
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return object Class instance
|
||||
*/
|
||||
public function load(string $name, bool $shared = true): ?object
|
||||
{
|
||||
$obj = null;
|
||||
|
||||
if (isset($this->classes[$name])) {
|
||||
[$class, $params, $callback] = $this->classes[$name];
|
||||
|
||||
$exists = isset($this->instances[$name]);
|
||||
|
||||
if ($shared) {
|
||||
$obj = ($exists) ?
|
||||
$this->getInstance($name) :
|
||||
$this->newInstance($class, $params);
|
||||
|
||||
if (!$exists) {
|
||||
$this->instances[$name] = $obj;
|
||||
}
|
||||
} else {
|
||||
$obj = $this->newInstance($class, $params);
|
||||
}
|
||||
|
||||
if ($callback && (!$shared || !$exists)) {
|
||||
$ref = [&$obj];
|
||||
\call_user_func_array($callback, $ref);
|
||||
}
|
||||
}
|
||||
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a single instance of a class.
|
||||
*
|
||||
* @param string $name Instance name
|
||||
*
|
||||
* @return object Class instance
|
||||
*/
|
||||
public function getInstance(string $name): ?object
|
||||
{
|
||||
return $this->instances[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a new instance of a class.
|
||||
*
|
||||
* @param callable|string $class Class name or callback function to instantiate class
|
||||
* @param array $params Class initialization parameters
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* @return object Class instance
|
||||
*/
|
||||
public function newInstance($class, array $params = []): object
|
||||
{
|
||||
if (\is_callable($class)) {
|
||||
return \call_user_func_array($class, $params);
|
||||
}
|
||||
|
||||
switch (\count($params)) {
|
||||
case 0:
|
||||
return new $class();
|
||||
case 1:
|
||||
return new $class($params[0]);
|
||||
case 2:
|
||||
return new $class($params[0], $params[1]);
|
||||
case 3:
|
||||
return new $class($params[0], $params[1], $params[2]);
|
||||
case 4:
|
||||
return new $class($params[0], $params[1], $params[2], $params[3]);
|
||||
case 5:
|
||||
return new $class($params[0], $params[1], $params[2], $params[3], $params[4]);
|
||||
default:
|
||||
try {
|
||||
$refClass = new ReflectionClass($class);
|
||||
|
||||
return $refClass->newInstanceArgs($params);
|
||||
} catch (ReflectionException $e) {
|
||||
throw new Exception("Cannot instantiate {$class}", 0, $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name Registry name
|
||||
*
|
||||
* @return mixed Class information or null if not registered
|
||||
*/
|
||||
public function get(string $name)
|
||||
{
|
||||
return $this->classes[$name] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the object to the initial state.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->classes = [];
|
||||
$this->instances = [];
|
||||
}
|
||||
|
||||
// Autoloading Functions
|
||||
|
||||
/**
|
||||
* Starts/stops autoloader.
|
||||
*
|
||||
* @param bool $enabled Enable/disable autoloading
|
||||
* @param mixed $dirs Autoload directories
|
||||
*/
|
||||
public static function autoload(bool $enabled = true, $dirs = []): void
|
||||
{
|
||||
if ($enabled) {
|
||||
spl_autoload_register([__CLASS__, 'loadClass']);
|
||||
} else {
|
||||
spl_autoload_unregister([__CLASS__, 'loadClass']);
|
||||
}
|
||||
|
||||
if (!empty($dirs)) {
|
||||
self::addDirectory($dirs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Autoloads classes.
|
||||
*
|
||||
* @param string $class Class name
|
||||
*/
|
||||
public static function loadClass(string $class): void
|
||||
{
|
||||
$class_file = str_replace(['\\', '_'], '/', $class) . '.php';
|
||||
|
||||
foreach (self::$dirs as $dir) {
|
||||
$file = $dir . '/' . $class_file;
|
||||
if (file_exists($file)) {
|
||||
require $file;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a directory for autoloading classes.
|
||||
*
|
||||
* @param mixed $dir Directory path
|
||||
*/
|
||||
public static function addDirectory($dir): void
|
||||
{
|
||||
if (\is_array($dir) || \is_object($dir)) {
|
||||
foreach ($dir as $value) {
|
||||
self::addDirectory($value);
|
||||
}
|
||||
} elseif (\is_string($dir)) {
|
||||
if (!\in_array($dir, self::$dirs, true)) {
|
||||
self::$dirs[] = $dir;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
320
libs/flight2/net/Request.php
Normal file
320
libs/flight2/net/Request.php
Normal file
@ -0,0 +1,320 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use flight\util\Collection;
|
||||
|
||||
/**
|
||||
* The Request class represents an HTTP request. Data from
|
||||
* all the super globals $_GET, $_POST, $_COOKIE, and $_FILES
|
||||
* are stored and accessible via the Request object.
|
||||
*
|
||||
* The default request properties are:
|
||||
* url - The URL being requested
|
||||
* base - The parent subdirectory of the URL
|
||||
* method - The request method (GET, POST, PUT, DELETE)
|
||||
* referrer - The referrer URL
|
||||
* ip - IP address of the client
|
||||
* ajax - Whether the request is an AJAX request
|
||||
* scheme - The server protocol (http, https)
|
||||
* user_agent - Browser information
|
||||
* type - The content type
|
||||
* length - The content length
|
||||
* query - Query string parameters
|
||||
* data - Post parameters
|
||||
* cookies - Cookie parameters
|
||||
* files - Uploaded files
|
||||
* secure - Connection is secure
|
||||
* accept - HTTP accept parameters
|
||||
* proxy_ip - Proxy IP address of the client
|
||||
*/
|
||||
final class Request
|
||||
{
|
||||
/**
|
||||
* @var string URL being requested
|
||||
*/
|
||||
public string $url;
|
||||
|
||||
/**
|
||||
* @var string Parent subdirectory of the URL
|
||||
*/
|
||||
public string $base;
|
||||
|
||||
/**
|
||||
* @var string Request method (GET, POST, PUT, DELETE)
|
||||
*/
|
||||
public string $method;
|
||||
|
||||
/**
|
||||
* @var string Referrer URL
|
||||
*/
|
||||
public string $referrer;
|
||||
|
||||
/**
|
||||
* @var string IP address of the client
|
||||
*/
|
||||
public string $ip;
|
||||
|
||||
/**
|
||||
* @var bool Whether the request is an AJAX request
|
||||
*/
|
||||
public bool $ajax;
|
||||
|
||||
/**
|
||||
* @var string Server protocol (http, https)
|
||||
*/
|
||||
public string $scheme;
|
||||
|
||||
/**
|
||||
* @var string Browser information
|
||||
*/
|
||||
public string $user_agent;
|
||||
|
||||
/**
|
||||
* @var string Content type
|
||||
*/
|
||||
public string $type;
|
||||
|
||||
/**
|
||||
* @var int Content length
|
||||
*/
|
||||
public int $length;
|
||||
|
||||
/**
|
||||
* @var Collection Query string parameters
|
||||
*/
|
||||
public Collection $query;
|
||||
|
||||
/**
|
||||
* @var Collection Post parameters
|
||||
*/
|
||||
public Collection $data;
|
||||
|
||||
/**
|
||||
* @var Collection Cookie parameters
|
||||
*/
|
||||
public Collection $cookies;
|
||||
|
||||
/**
|
||||
* @var Collection Uploaded files
|
||||
*/
|
||||
public Collection $files;
|
||||
|
||||
/**
|
||||
* @var bool Whether the connection is secure
|
||||
*/
|
||||
public bool $secure;
|
||||
|
||||
/**
|
||||
* @var string HTTP accept parameters
|
||||
*/
|
||||
public string $accept;
|
||||
|
||||
/**
|
||||
* @var string Proxy IP address of the client
|
||||
*/
|
||||
public string $proxy_ip;
|
||||
|
||||
/**
|
||||
* @var string HTTP host name
|
||||
*/
|
||||
public string $host;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $config Request configuration
|
||||
*/
|
||||
public function __construct(array $config = [])
|
||||
{
|
||||
// Default properties
|
||||
if (empty($config)) {
|
||||
$config = [
|
||||
'url' => str_replace('@', '%40', self::getVar('REQUEST_URI', '/')),
|
||||
'base' => str_replace(['\\', ' '], ['/', '%20'], \dirname(self::getVar('SCRIPT_NAME'))),
|
||||
'method' => self::getMethod(),
|
||||
'referrer' => self::getVar('HTTP_REFERER'),
|
||||
'ip' => self::getVar('REMOTE_ADDR'),
|
||||
'ajax' => 'XMLHttpRequest' === self::getVar('HTTP_X_REQUESTED_WITH'),
|
||||
'scheme' => self::getScheme(),
|
||||
'user_agent' => self::getVar('HTTP_USER_AGENT'),
|
||||
'type' => self::getVar('CONTENT_TYPE'),
|
||||
'length' => (int) self::getVar('CONTENT_LENGTH', 0),
|
||||
'query' => new Collection($_GET),
|
||||
'data' => new Collection($_POST),
|
||||
'cookies' => new Collection($_COOKIE),
|
||||
'files' => new Collection($_FILES),
|
||||
'secure' => 'https' === self::getScheme(),
|
||||
'accept' => self::getVar('HTTP_ACCEPT'),
|
||||
'proxy_ip' => self::getProxyIpAddress(),
|
||||
'host' => self::getVar('HTTP_HOST'),
|
||||
];
|
||||
}
|
||||
|
||||
$this->init($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize request properties.
|
||||
*
|
||||
* @param array $properties Array of request properties
|
||||
*/
|
||||
public function init(array $properties = [])
|
||||
{
|
||||
// Set all the defined properties
|
||||
foreach ($properties as $name => $value) {
|
||||
$this->$name = $value;
|
||||
}
|
||||
|
||||
// Get the requested URL without the base directory
|
||||
if ('/' !== $this->base && '' !== $this->base && 0 === strpos($this->url, $this->base)) {
|
||||
$this->url = substr($this->url, \strlen($this->base));
|
||||
}
|
||||
|
||||
// Default url
|
||||
if (empty($this->url)) {
|
||||
$this->url = '/';
|
||||
} else {
|
||||
// Merge URL query parameters with $_GET
|
||||
$_GET = array_merge($_GET, self::parseQuery($this->url));
|
||||
|
||||
$this->query->setData($_GET);
|
||||
}
|
||||
|
||||
// Check for JSON input
|
||||
if (0 === strpos($this->type, 'application/json')) {
|
||||
$body = self::getBody();
|
||||
if ('' !== $body && null !== $body) {
|
||||
$data = json_decode($body, true);
|
||||
if (is_array($data)) {
|
||||
$this->data->setData($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the body of the request.
|
||||
*
|
||||
* @return string Raw HTTP request body
|
||||
*/
|
||||
public static function getBody(): ?string
|
||||
{
|
||||
static $body;
|
||||
|
||||
if (null !== $body) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$method = self::getMethod();
|
||||
|
||||
if ('POST' === $method || 'PUT' === $method || 'DELETE' === $method || 'PATCH' === $method) {
|
||||
$body = file_get_contents('php://input');
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the request method.
|
||||
*/
|
||||
public static function getMethod(): string
|
||||
{
|
||||
$method = self::getVar('REQUEST_METHOD', 'GET');
|
||||
|
||||
if (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) {
|
||||
$method = $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'];
|
||||
} elseif (isset($_REQUEST['_method'])) {
|
||||
$method = $_REQUEST['_method'];
|
||||
}
|
||||
|
||||
return strtoupper($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the real remote IP address.
|
||||
*
|
||||
* @return string IP address
|
||||
*/
|
||||
public static function getProxyIpAddress(): string
|
||||
{
|
||||
static $forwarded = [
|
||||
'HTTP_CLIENT_IP',
|
||||
'HTTP_X_FORWARDED_FOR',
|
||||
'HTTP_X_FORWARDED',
|
||||
'HTTP_X_CLUSTER_CLIENT_IP',
|
||||
'HTTP_FORWARDED_FOR',
|
||||
'HTTP_FORWARDED',
|
||||
];
|
||||
|
||||
$flags = \FILTER_FLAG_NO_PRIV_RANGE | \FILTER_FLAG_NO_RES_RANGE;
|
||||
|
||||
foreach ($forwarded as $key) {
|
||||
if (\array_key_exists($key, $_SERVER)) {
|
||||
sscanf($_SERVER[$key], '%[^,]', $ip);
|
||||
if (false !== filter_var($ip, \FILTER_VALIDATE_IP, $flags)) {
|
||||
return $ip;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a variable from $_SERVER using $default if not provided.
|
||||
*
|
||||
* @param string $var Variable name
|
||||
* @param mixed $default Default value to substitute
|
||||
*
|
||||
* @return mixed Server variable value
|
||||
*/
|
||||
public static function getVar(string $var, $default = '')
|
||||
{
|
||||
return $_SERVER[$var] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query parameters from a URL.
|
||||
*
|
||||
* @param string $url URL string
|
||||
*
|
||||
* @return array Query parameters
|
||||
*/
|
||||
public static function parseQuery(string $url): array
|
||||
{
|
||||
$params = [];
|
||||
|
||||
$args = parse_url($url);
|
||||
if (isset($args['query'])) {
|
||||
parse_str($args['query'], $params);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
public static function getScheme(): string
|
||||
{
|
||||
if (
|
||||
(isset($_SERVER['HTTPS']) && 'on' === strtolower($_SERVER['HTTPS']))
|
||||
||
|
||||
(isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO'])
|
||||
||
|
||||
(isset($_SERVER['HTTP_FRONT_END_HTTPS']) && 'on' === $_SERVER['HTTP_FRONT_END_HTTPS'])
|
||||
||
|
||||
(isset($_SERVER['REQUEST_SCHEME']) && 'https' === $_SERVER['REQUEST_SCHEME'])
|
||||
) {
|
||||
return 'https';
|
||||
}
|
||||
|
||||
return 'http';
|
||||
}
|
||||
}
|
321
libs/flight2/net/Response.php
Normal file
321
libs/flight2/net/Response.php
Normal file
@ -0,0 +1,321 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* The Response class represents an HTTP response. The object
|
||||
* contains the response headers, HTTP status code, and response
|
||||
* body.
|
||||
*/
|
||||
class Response
|
||||
{
|
||||
/**
|
||||
* header Content-Length.
|
||||
*/
|
||||
public bool $content_length = true;
|
||||
|
||||
/**
|
||||
* @var array HTTP status codes
|
||||
*/
|
||||
public static array $codes = [
|
||||
100 => 'Continue',
|
||||
101 => 'Switching Protocols',
|
||||
102 => 'Processing',
|
||||
|
||||
200 => 'OK',
|
||||
201 => 'Created',
|
||||
202 => 'Accepted',
|
||||
203 => 'Non-Authoritative Information',
|
||||
204 => 'No Content',
|
||||
205 => 'Reset Content',
|
||||
206 => 'Partial Content',
|
||||
207 => 'Multi-Status',
|
||||
208 => 'Already Reported',
|
||||
|
||||
226 => 'IM Used',
|
||||
|
||||
300 => 'Multiple Choices',
|
||||
301 => 'Moved Permanently',
|
||||
302 => 'Found',
|
||||
303 => 'See Other',
|
||||
304 => 'Not Modified',
|
||||
305 => 'Use Proxy',
|
||||
306 => '(Unused)',
|
||||
307 => 'Temporary Redirect',
|
||||
308 => 'Permanent Redirect',
|
||||
|
||||
400 => 'Bad Request',
|
||||
401 => 'Unauthorized',
|
||||
402 => 'Payment Required',
|
||||
403 => 'Forbidden',
|
||||
404 => 'Not Found',
|
||||
405 => 'Method Not Allowed',
|
||||
406 => 'Not Acceptable',
|
||||
407 => 'Proxy Authentication Required',
|
||||
408 => 'Request Timeout',
|
||||
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',
|
||||
|
||||
422 => 'Unprocessable Entity',
|
||||
423 => 'Locked',
|
||||
424 => 'Failed Dependency',
|
||||
|
||||
426 => 'Upgrade Required',
|
||||
|
||||
428 => 'Precondition Required',
|
||||
429 => 'Too Many Requests',
|
||||
|
||||
431 => 'Request Header Fields Too Large',
|
||||
|
||||
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',
|
||||
];
|
||||
/**
|
||||
* @var int HTTP status
|
||||
*/
|
||||
protected int $status = 200;
|
||||
|
||||
/**
|
||||
* @var array HTTP headers
|
||||
*/
|
||||
protected array $headers = [];
|
||||
|
||||
/**
|
||||
* @var string HTTP response body
|
||||
*/
|
||||
protected string $body = '';
|
||||
|
||||
/**
|
||||
* @var bool HTTP response sent
|
||||
*/
|
||||
protected bool $sent = false;
|
||||
|
||||
/**
|
||||
* Sets the HTTP status of the response.
|
||||
*
|
||||
* @param int|null $code HTTP status code.
|
||||
*
|
||||
* @throws Exception If invalid status code
|
||||
*
|
||||
* @return int|object Self reference
|
||||
*/
|
||||
public function status(?int $code = null)
|
||||
{
|
||||
if (null === $code) {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
if (\array_key_exists($code, self::$codes)) {
|
||||
$this->status = $code;
|
||||
} else {
|
||||
throw new Exception('Invalid status code.');
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header to the response.
|
||||
*
|
||||
* @param array|string $name Header name or array of names and values
|
||||
* @param string|null $value Header value
|
||||
*
|
||||
* @return object Self reference
|
||||
*/
|
||||
public function header($name, ?string $value = null)
|
||||
{
|
||||
if (\is_array($name)) {
|
||||
foreach ($name as $k => $v) {
|
||||
$this->headers[$k] = $v;
|
||||
}
|
||||
} else {
|
||||
$this->headers[$name] = $value;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the headers from the response.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function headers()
|
||||
{
|
||||
return $this->headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes content to the response body.
|
||||
*
|
||||
* @param string $str Response content
|
||||
*
|
||||
* @return Response Self reference
|
||||
*/
|
||||
public function write(string $str): self
|
||||
{
|
||||
$this->body .= $str;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the response.
|
||||
*
|
||||
* @return Response Self reference
|
||||
*/
|
||||
public function clear(): self
|
||||
{
|
||||
$this->status = 200;
|
||||
$this->headers = [];
|
||||
$this->body = '';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets caching headers for the response.
|
||||
*
|
||||
* @param int|string $expires Expiration time
|
||||
*
|
||||
* @return Response Self reference
|
||||
*/
|
||||
public function cache($expires): self
|
||||
{
|
||||
if (false === $expires) {
|
||||
$this->headers['Expires'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
|
||||
$this->headers['Cache-Control'] = [
|
||||
'no-store, no-cache, must-revalidate',
|
||||
'post-check=0, pre-check=0',
|
||||
'max-age=0',
|
||||
];
|
||||
$this->headers['Pragma'] = 'no-cache';
|
||||
} else {
|
||||
$expires = \is_int($expires) ? $expires : strtotime($expires);
|
||||
$this->headers['Expires'] = gmdate('D, d M Y H:i:s', $expires) . ' GMT';
|
||||
$this->headers['Cache-Control'] = 'max-age=' . ($expires - time());
|
||||
if (isset($this->headers['Pragma']) && 'no-cache' == $this->headers['Pragma']) {
|
||||
unset($this->headers['Pragma']);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends HTTP headers.
|
||||
*
|
||||
* @return Response Self reference
|
||||
*/
|
||||
public function sendHeaders(): self
|
||||
{
|
||||
// Send status code header
|
||||
if (false !== strpos(\PHP_SAPI, 'cgi')) {
|
||||
header(
|
||||
sprintf(
|
||||
'Status: %d %s',
|
||||
$this->status,
|
||||
self::$codes[$this->status]
|
||||
),
|
||||
true
|
||||
);
|
||||
} else {
|
||||
header(
|
||||
sprintf(
|
||||
'%s %d %s',
|
||||
$_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.1',
|
||||
$this->status,
|
||||
self::$codes[$this->status]),
|
||||
true,
|
||||
$this->status
|
||||
);
|
||||
}
|
||||
|
||||
// Send other headers
|
||||
foreach ($this->headers as $field => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $v) {
|
||||
header($field . ': ' . $v, false);
|
||||
}
|
||||
} else {
|
||||
header($field . ': ' . $value);
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->content_length) {
|
||||
// Send content length
|
||||
$length = $this->getContentLength();
|
||||
|
||||
if ($length > 0) {
|
||||
header('Content-Length: ' . $length);
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the content length.
|
||||
*
|
||||
* @return int Content length
|
||||
*/
|
||||
public function getContentLength(): int
|
||||
{
|
||||
return \extension_loaded('mbstring') ?
|
||||
mb_strlen($this->body, 'latin1') :
|
||||
\strlen($this->body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether response was sent.
|
||||
*/
|
||||
public function sent(): bool
|
||||
{
|
||||
return $this->sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a HTTP response.
|
||||
*/
|
||||
public function send(): void
|
||||
{
|
||||
if (ob_get_length() > 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
if (!headers_sent()) {
|
||||
$this->sendHeaders();
|
||||
}
|
||||
|
||||
echo $this->body;
|
||||
|
||||
$this->sent = true;
|
||||
}
|
||||
}
|
156
libs/flight2/net/Route.php
Normal file
156
libs/flight2/net/Route.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
/**
|
||||
* The Route class is responsible for routing an HTTP request to
|
||||
* an assigned callback function. The Router tries to match the
|
||||
* requested URL against a series of URL patterns.
|
||||
*/
|
||||
final class Route
|
||||
{
|
||||
/**
|
||||
* @var string URL pattern
|
||||
*/
|
||||
public string $pattern;
|
||||
|
||||
/**
|
||||
* @var mixed Callback function
|
||||
*/
|
||||
public $callback;
|
||||
|
||||
/**
|
||||
* @var array HTTP methods
|
||||
*/
|
||||
public array $methods = [];
|
||||
|
||||
/**
|
||||
* @var array Route parameters
|
||||
*/
|
||||
public array $params = [];
|
||||
|
||||
/**
|
||||
* @var string|null Matching regular expression
|
||||
*/
|
||||
public ?string $regex = null;
|
||||
|
||||
/**
|
||||
* @var string URL splat content
|
||||
*/
|
||||
public string $splat = '';
|
||||
|
||||
/**
|
||||
* @var bool Pass self in callback parameters
|
||||
*/
|
||||
public bool $pass = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $pattern URL pattern
|
||||
* @param mixed $callback Callback function
|
||||
* @param array $methods HTTP methods
|
||||
* @param bool $pass Pass self in callback parameters
|
||||
*/
|
||||
public function __construct(string $pattern, $callback, array $methods, bool $pass)
|
||||
{
|
||||
$this->pattern = $pattern;
|
||||
$this->callback = $callback;
|
||||
$this->methods = $methods;
|
||||
$this->pass = $pass;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a URL matches the route pattern. Also parses named parameters in the URL.
|
||||
*
|
||||
* @param string $url Requested URL
|
||||
* @param bool $case_sensitive Case sensitive matching
|
||||
*
|
||||
* @return bool Match status
|
||||
*/
|
||||
public function matchUrl(string $url, bool $case_sensitive = false): bool
|
||||
{
|
||||
// Wildcard or exact match
|
||||
if ('*' === $this->pattern || $this->pattern === $url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
$last_char = substr($this->pattern, -1);
|
||||
|
||||
// Get splat
|
||||
if ('*' === $last_char) {
|
||||
$n = 0;
|
||||
$len = \strlen($url);
|
||||
$count = substr_count($this->pattern, '/');
|
||||
|
||||
for ($i = 0; $i < $len; $i++) {
|
||||
if ('/' === $url[$i]) {
|
||||
$n++;
|
||||
}
|
||||
if ($n === $count) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->splat = (string) substr($url, $i + 1);
|
||||
}
|
||||
|
||||
// Build the regex for matching
|
||||
$regex = str_replace([')', '/*'], [')?', '(/?|/.*?)'], $this->pattern);
|
||||
|
||||
$regex = preg_replace_callback(
|
||||
'#@([\w]+)(:([^/\(\)]*))?#',
|
||||
static function ($matches) use (&$ids) {
|
||||
$ids[$matches[1]] = null;
|
||||
if (isset($matches[3])) {
|
||||
return '(?P<' . $matches[1] . '>' . $matches[3] . ')';
|
||||
}
|
||||
|
||||
return '(?P<' . $matches[1] . '>[^/\?]+)';
|
||||
},
|
||||
$regex
|
||||
);
|
||||
|
||||
// Fix trailing slash
|
||||
if ('/' === $last_char) {
|
||||
$regex .= '?';
|
||||
} // Allow trailing slash
|
||||
else {
|
||||
$regex .= '/?';
|
||||
}
|
||||
|
||||
// Attempt to match route and named parameters
|
||||
if (preg_match('#^' . $regex . '(?:\?.*)?$#' . (($case_sensitive) ? '' : 'i'), $url, $matches)) {
|
||||
foreach ($ids as $k => $v) {
|
||||
$this->params[$k] = (\array_key_exists($k, $matches)) ? urldecode($matches[$k]) : null;
|
||||
}
|
||||
|
||||
$this->regex = $regex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an HTTP method matches the route methods.
|
||||
*
|
||||
* @param string $method HTTP method
|
||||
*
|
||||
* @return bool Match status
|
||||
*/
|
||||
public function matchMethod(string $method): bool
|
||||
{
|
||||
return \count(array_intersect([$method, '*'], $this->methods)) > 0;
|
||||
}
|
||||
}
|
118
libs/flight2/net/Router.php
Normal file
118
libs/flight2/net/Router.php
Normal file
@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\net;
|
||||
|
||||
/**
|
||||
* The Router class is responsible for routing an HTTP request to
|
||||
* an assigned callback function. The Router tries to match the
|
||||
* requested URL against a series of URL patterns.
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* Case sensitive matching.
|
||||
*/
|
||||
public bool $case_sensitive = false;
|
||||
/**
|
||||
* Mapped routes.
|
||||
*/
|
||||
protected array $routes = [];
|
||||
|
||||
/**
|
||||
* Pointer to current route.
|
||||
*/
|
||||
protected int $index = 0;
|
||||
|
||||
/**
|
||||
* Gets mapped routes.
|
||||
*
|
||||
* @return array Array of routes
|
||||
*/
|
||||
public function getRoutes(): array
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears all routes in the router.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->routes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a URL pattern to a callback function.
|
||||
*
|
||||
* @param string $pattern URL pattern to match
|
||||
* @param callback $callback Callback function
|
||||
* @param bool $pass_route Pass the matching route object to the callback
|
||||
*/
|
||||
public function map(string $pattern, callable $callback, bool $pass_route = false): void
|
||||
{
|
||||
$url = trim($pattern);
|
||||
$methods = ['*'];
|
||||
|
||||
if (false !== strpos($url, ' ')) {
|
||||
[$method, $url] = explode(' ', $url, 2);
|
||||
$url = trim($url);
|
||||
$methods = explode('|', $method);
|
||||
}
|
||||
|
||||
$this->routes[] = new Route($url, $callback, $methods, $pass_route);
|
||||
}
|
||||
|
||||
/**
|
||||
* Routes the current request.
|
||||
*
|
||||
* @param Request $request Request object
|
||||
*
|
||||
* @return bool|Route Matching route or false if no match
|
||||
*/
|
||||
public function route(Request $request)
|
||||
{
|
||||
$url_decoded = urldecode($request->url);
|
||||
while ($route = $this->current()) {
|
||||
if (false !== $route && $route->matchMethod($request->method) && $route->matchUrl($url_decoded, $this->case_sensitive)) {
|
||||
return $route;
|
||||
}
|
||||
$this->next();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current route.
|
||||
*
|
||||
* @return bool|Route
|
||||
*/
|
||||
public function current()
|
||||
{
|
||||
return $this->routes[$this->index] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next route.
|
||||
*/
|
||||
public function next(): void
|
||||
{
|
||||
$this->index++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to the first route.
|
||||
*/
|
||||
public function reset(): void
|
||||
{
|
||||
$this->index = 0;
|
||||
}
|
||||
}
|
200
libs/flight2/template/View.php
Normal file
200
libs/flight2/template/View.php
Normal file
@ -0,0 +1,200 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\template;
|
||||
|
||||
/**
|
||||
* The View class represents output to be displayed. It provides
|
||||
* methods for managing view data and inserts the data into
|
||||
* view templates upon rendering.
|
||||
*/
|
||||
class View
|
||||
{
|
||||
/**
|
||||
* Location of view templates.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $path;
|
||||
|
||||
/**
|
||||
* File extension.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $extension = '.php';
|
||||
|
||||
/**
|
||||
* View variables.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $vars = [];
|
||||
|
||||
/**
|
||||
* Template file.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $template;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $path Path to templates directory
|
||||
*/
|
||||
public function __construct($path = '.')
|
||||
{
|
||||
$this->path = $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a template variable.
|
||||
*
|
||||
* @param string $key Key
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
public function get($key)
|
||||
{
|
||||
return $this->vars[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a template variable.
|
||||
*
|
||||
* @param mixed $key Key
|
||||
* @param string $value Value
|
||||
*/
|
||||
public function set($key, $value = null)
|
||||
{
|
||||
if (\is_array($key) || \is_object($key)) {
|
||||
foreach ($key as $k => $v) {
|
||||
$this->vars[$k] = $v;
|
||||
}
|
||||
} else {
|
||||
$this->vars[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a template variable is set.
|
||||
*
|
||||
* @param string $key Key
|
||||
*
|
||||
* @return bool If key exists
|
||||
*/
|
||||
public function has($key)
|
||||
{
|
||||
return isset($this->vars[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsets a template variable. If no key is passed in, clear all variables.
|
||||
*
|
||||
* @param string $key Key
|
||||
*/
|
||||
public function clear($key = null)
|
||||
{
|
||||
if (null === $key) {
|
||||
$this->vars = [];
|
||||
} else {
|
||||
unset($this->vars[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a template.
|
||||
*
|
||||
* @param string $file Template file
|
||||
* @param array $data Template data
|
||||
*
|
||||
* @throws \Exception If template not found
|
||||
*/
|
||||
public function render($file, $data = null)
|
||||
{
|
||||
$this->template = $this->getTemplate($file);
|
||||
|
||||
if (!file_exists($this->template)) {
|
||||
throw new \Exception("Template file not found: {$this->template}.");
|
||||
}
|
||||
|
||||
if (\is_array($data)) {
|
||||
$this->vars = array_merge($this->vars, $data);
|
||||
}
|
||||
|
||||
extract($this->vars);
|
||||
|
||||
include $this->template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the output of a template.
|
||||
*
|
||||
* @param string $file Template file
|
||||
* @param array $data Template data
|
||||
*
|
||||
* @return string Output of template
|
||||
*/
|
||||
public function fetch($file, $data = null)
|
||||
{
|
||||
ob_start();
|
||||
|
||||
$this->render($file, $data);
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a template file exists.
|
||||
*
|
||||
* @param string $file Template file
|
||||
*
|
||||
* @return bool Template file exists
|
||||
*/
|
||||
public function exists($file)
|
||||
{
|
||||
return file_exists($this->getTemplate($file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the full path to a template file.
|
||||
*
|
||||
* @param string $file Template file
|
||||
*
|
||||
* @return string Template file location
|
||||
*/
|
||||
public function getTemplate($file)
|
||||
{
|
||||
$ext = $this->extension;
|
||||
|
||||
if (!empty($ext) && (substr($file, -1 * \strlen($ext)) != $ext)) {
|
||||
$file .= $ext;
|
||||
}
|
||||
|
||||
if (('/' == substr($file, 0, 1))) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
return $this->path . '/' . $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays escaped output.
|
||||
*
|
||||
* @param string $str String to escape
|
||||
*
|
||||
* @return string Escaped string
|
||||
*/
|
||||
public function e($str)
|
||||
{
|
||||
echo htmlentities($str);
|
||||
}
|
||||
}
|
250
libs/flight2/util/Collection.php
Normal file
250
libs/flight2/util/Collection.php
Normal file
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
|
||||
namespace flight\util;
|
||||
|
||||
use ArrayAccess;
|
||||
use function count;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use JsonSerializable;
|
||||
|
||||
if (!interface_exists('JsonSerializable')) {
|
||||
require_once __DIR__ . '/LegacyJsonSerializable.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* The Collection class allows you to access a set of data
|
||||
* using both array and object notation.
|
||||
*/
|
||||
final class Collection implements ArrayAccess, Iterator, Countable, JsonSerializable
|
||||
{
|
||||
/**
|
||||
* Collection data.
|
||||
*/
|
||||
private array $data;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $data Initial data
|
||||
*/
|
||||
public function __construct(array $data = [])
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item.
|
||||
*
|
||||
* @param string $key Key
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
public function __get(string $key)
|
||||
{
|
||||
return $this->data[$key] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an item.
|
||||
*
|
||||
* @param string $key Key
|
||||
* @param mixed $value Value
|
||||
*/
|
||||
public function __set(string $key, $value): void
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item exists.
|
||||
*
|
||||
* @param string $key Key
|
||||
*
|
||||
* @return bool Item status
|
||||
*/
|
||||
public function __isset(string $key): bool
|
||||
{
|
||||
return isset($this->data[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item.
|
||||
*
|
||||
* @param string $key Key
|
||||
*/
|
||||
public function __unset(string $key): void
|
||||
{
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an item at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->data[$offset] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an item at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
* @param mixed $value Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
if (null === $offset) {
|
||||
$this->data[] = $value;
|
||||
} else {
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an item exists at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
*
|
||||
* @return bool Item status
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->data[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an item at the offset.
|
||||
*
|
||||
* @param string $offset Offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the collection.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
reset($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current collection item.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current()
|
||||
{
|
||||
return current($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets current collection key.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key()
|
||||
{
|
||||
return key($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the next collection value.
|
||||
*
|
||||
* @return mixed Value
|
||||
*/
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next()
|
||||
{
|
||||
return next($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current collection key is valid.
|
||||
*
|
||||
* @return bool Key status
|
||||
*/
|
||||
public function valid(): bool
|
||||
{
|
||||
$key = key($this->data);
|
||||
|
||||
return null !== $key && false !== $key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the size of the collection.
|
||||
*
|
||||
* @return int Collection size
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return \count($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the item keys.
|
||||
*
|
||||
* @return array Collection keys
|
||||
*/
|
||||
public function keys(): array
|
||||
{
|
||||
return array_keys($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collection data.
|
||||
*
|
||||
* @return array Collection data
|
||||
*/
|
||||
public function getData(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection data.
|
||||
*
|
||||
* @param array $data New collection data
|
||||
*/
|
||||
public function setData(array $data): void
|
||||
{
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the collection data which can be serialized to JSON.
|
||||
*
|
||||
* @return array Collection data which can be serialized by <b>json_encode</b>
|
||||
*/
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items from the collection.
|
||||
*/
|
||||
public function clear(): void
|
||||
{
|
||||
$this->data = [];
|
||||
}
|
||||
}
|
13
libs/flight2/util/LegacyJsonSerializable.php
Normal file
13
libs/flight2/util/LegacyJsonSerializable.php
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/**
|
||||
* Flight: An extensible micro-framework.
|
||||
*
|
||||
* @copyright Copyright (c) 2011, Mike Cao <mike@mikecao.com>
|
||||
* @license MIT, http://flightphp.com/license
|
||||
*/
|
||||
interface LegacyJsonSerializable
|
||||
{
|
||||
public function jsonSerialize();
|
||||
}
|
@ -1451,6 +1451,19 @@ class Parsedown
|
||||
$Element['attributes']['title'] = $Definition['title'];
|
||||
}
|
||||
|
||||
// Prevent XSS Observium Hack (based on same trick in get_vars())
|
||||
// <sCrIpT> < / s c r i p t >
|
||||
// javascript:alert("Hello world");/
|
||||
// <svg onload=alert(document.domain)>
|
||||
$prevent_xss = '!(^\s*(J\s*A\s*V\s*A\s*)?S\s*C\s*R\s*I\s*P\s*T\s*:'.
|
||||
'|<\s*/?\s*S\s*C\s*R\s*I\s*P\s*T\s*>'.
|
||||
'|(<\s*s\s*v\s*g.*(o\s*n\s*l\s*o\s*a\s*d|s\s*c\s*r\s*i\s*p\s*t))'.
|
||||
'|<\s*i\s*m\s*g.*o\s*n\s*e\s*r\s*r\s*o\s*r)!i';
|
||||
if (is_string($Element['attributes']['href']) && preg_match($prevent_xss, $Element['attributes']['href'])) {
|
||||
// Prevent any <script> html tag inside vars, exclude any possible XSS with scripts
|
||||
$Element['attributes']['href'] = 'javascript:void(0)';
|
||||
}
|
||||
|
||||
return array(
|
||||
'extent' => $extent,
|
||||
'element' => $Element,
|
||||
|
Reference in New Issue
Block a user