280 lines
9.7 KiB
PHP
280 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace Picea\Extension;
|
|
|
|
use Notes\Route\Attribute\Object\Route;
|
|
use Picea\Compiler\Context;
|
|
|
|
class UrlExtension implements Extension, FunctionExtension {
|
|
|
|
public const URLIZE_PATTERN_URL = <<<PATTERN
|
|
~(?<!href=['"])https?://[\w/._\-&?]*(?!</a>)(?=[^\w/._\-&])~s
|
|
PATTERN;
|
|
|
|
public const URLIZE_PATTERN_EMAIL = <<<PATTERN
|
|
/(?:[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+\\/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])/
|
|
PATTERN;
|
|
|
|
protected string $urlBase;
|
|
|
|
protected string $appUrl;
|
|
|
|
protected string $assetToken;
|
|
|
|
protected array $routes;
|
|
|
|
protected array $routesTarget;
|
|
|
|
protected bool $forceSSL = false;
|
|
|
|
public array $tokens = [ "url" , "route", "asset", "slug" ];
|
|
|
|
#[\Deprecated]
|
|
protected bool $enforceExistingArguments = true;
|
|
|
|
public function __construct(string $urlBase = "", string $assetToken = "", string $appUrl = "", bool $forceSSL = false) {
|
|
$this->urlBase = trim($urlBase, "/");
|
|
$this->assetToken = $assetToken;
|
|
$this->appUrl = $appUrl;
|
|
$this->forceSSL = $forceSSL;
|
|
}
|
|
|
|
public function parse(\Picea\Compiler\Context &$context, ?string $arguments, string $token, array $options = []) : string
|
|
{
|
|
switch($token) {
|
|
case "asset":
|
|
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildAssetUrl($arguments) ?>";
|
|
|
|
case "route":
|
|
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildRouteUrl($arguments) ?>";
|
|
|
|
case "url":
|
|
if ( in_array('parameters', $options) ) {
|
|
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->setUrlParameters($arguments) ?>";
|
|
}
|
|
elseif ( in_array('current', $options) ) {
|
|
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->currentUrl($arguments) ?>";
|
|
}
|
|
|
|
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->buildUrl($arguments) ?>";
|
|
|
|
case "slug":
|
|
return "<?php echo \$picea->compiler->getExtensionFromToken('$token')->slug($arguments) ?>";
|
|
}
|
|
|
|
throw new \ParseExtensionException("Unknown token given $token in UrlExtension.");
|
|
}
|
|
|
|
public function exportFunctions(): array
|
|
{
|
|
return [
|
|
"url" => [ $this, 'buildUrl' ],
|
|
"current_url" => [ $this, 'currentUrl' ],
|
|
"asset" => [ $this, 'buildAssetUrl' ],
|
|
"route" => [ $this, 'buildRouteUrl' ],
|
|
"slug" => [ $this, 'slug' ],
|
|
"urlize" => [ $this, 'urlize' ]
|
|
];
|
|
}
|
|
|
|
public function getRouteList(bool $full = false) : array
|
|
{
|
|
return $this->routes;
|
|
}
|
|
|
|
public function setUrlParameters(string $url = "", array $parameters = []) : string
|
|
{
|
|
return $url . ( $parameters ? "?" . http_build_query($parameters) : "" );
|
|
}
|
|
|
|
public function urlize(string $string) : string
|
|
{
|
|
# Normal URL patterns
|
|
$string = preg_replace(static::URLIZE_PATTERN_URL, '<a href="$0" target="_blank" title="$0">$0</a>', $string);
|
|
|
|
# Email patterns
|
|
$string = preg_replace(static::URLIZE_PATTERN_EMAIL, '<a href="mailto:$0" title="$0">$0</a>', $string);
|
|
|
|
return $string;
|
|
}
|
|
|
|
public function currentUrl(array $parameters = []) : string
|
|
{
|
|
return $this->buildUrl($this->uri(), $parameters);
|
|
}
|
|
|
|
public function buildUrl(string $uri = "", array $parameters = [], bool $appendVersion = false) : string
|
|
{
|
|
return $this->setUrlParameters($this->url() . "/" . ltrim($uri, "/"), $appendVersion ? array_replace([ 'v' => $this->assetToken ], $parameters) : $parameters);
|
|
}
|
|
|
|
public function buildAssetUrl(string $uri, array $parameters = [], bool $appendVersion = true) : string
|
|
{
|
|
return $this->buildUrl($uri, $parameters, $appendVersion);
|
|
}
|
|
|
|
public function buildRouteUrl(string $name, array $parameters = [], bool $appendVersion = false) : string
|
|
{
|
|
if ( false !== ( $route = $this->routes[$name] ?? false ) ) {
|
|
return $this->buildUrl($this->prepareRoute($route, $parameters), $parameters, $appendVersion);
|
|
}
|
|
|
|
$routeList = json_encode($this->routes, \JSON_PRETTY_PRINT);
|
|
|
|
throw new \RuntimeException("
|
|
Given route `$name` could not be found within current route list:
|
|
$routeList
|
|
");
|
|
}
|
|
|
|
public function url() : string
|
|
{
|
|
return $this->scheme() . $this->domain() . $this->base();
|
|
}
|
|
|
|
# src: https://stackoverflow.com/a/14550919
|
|
public static function slug(string $text, string $separator = '-') : string
|
|
{
|
|
$clean = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $text);
|
|
$clean = preg_replace("/[^a-zA-Z0-9\/_| -]/", '', $clean);
|
|
|
|
return preg_replace("/[\/_| -]+/", $separator, strtolower(trim($clean, '-')));
|
|
|
|
}
|
|
|
|
public function registerRoute(string $name, string $route, string $class, string $method, array $routeMethods) : void
|
|
{
|
|
$this->routes[$name] = [
|
|
'route' => $route,
|
|
'routeMethods' => $routeMethods,
|
|
'class' => $class,
|
|
'classMethod' => $method,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Return URI formatted
|
|
*/
|
|
protected function uri(bool $queryString = false) : string
|
|
{
|
|
$uri = ( $_SERVER['REQUEST_URI'] ?? $_SERVER['PHP_SELF'] ?? "" ) . ( $queryString ? $this->queryString() : "" );
|
|
|
|
if ( ! $queryString ) {
|
|
$uri = explode('?', $uri, 2)[0];
|
|
}
|
|
|
|
return '/' . ltrim(substr($uri, strlen($this->base())), '/');
|
|
}
|
|
|
|
/**
|
|
* Return query string
|
|
*/
|
|
protected function queryString() : string
|
|
{
|
|
return isset($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : "";
|
|
}
|
|
|
|
protected function scheme() : string
|
|
{
|
|
return ( $this->forceSSL || $this->isHttps() ? "https" : "http" ) . "://";
|
|
}
|
|
|
|
protected function base() : string
|
|
{
|
|
return $this->urlBase ? "/" . $this->urlBase : "";
|
|
}
|
|
|
|
/**
|
|
* Check if Uri's segments are valid
|
|
* @param array $segments The uri's segments
|
|
*/
|
|
protected function secure($segments = []) : array
|
|
{
|
|
return array_diff($segments, [ '..', '://' ]);
|
|
}
|
|
|
|
protected function domain() : string
|
|
{
|
|
$port = $this->isDefaultPort() ? "" : ":" . $_SERVER['SERVER_PORT'];
|
|
|
|
return strtolower($this->appUrl ? $this->appUrl . $port : $_SERVER['HTTP_HOST']);
|
|
}
|
|
|
|
protected function isDefaultPort() : bool
|
|
{
|
|
return in_array($_SERVER['SERVER_PORT'], [ 80, 443 ]);
|
|
}
|
|
|
|
protected function isHttps() : bool
|
|
{
|
|
$https = in_array('https', array_map("strtolower", [
|
|
$_SERVER['REQUEST_SCHEME'] ?? "",
|
|
$_SERVER['X-Url-Scheme'] ?? "",
|
|
$_SERVER['Front-End-Https'] ?? "",
|
|
$_SERVER['X-Forwarded-Proto'] ?? "",
|
|
$_SERVER['X-Forwarded-Protocol'] ?? "",
|
|
$_SERVER['HTTP_X_FORWARDED_PROTO'] ?? "",
|
|
$_SERVER['HTTP_X_FORWARDED_PROTOCOL'] ?? "",
|
|
])) || isset($_SERVER['HTTP_X_ARR_SSL']);
|
|
|
|
return $https
|
|
|| ( "443" === ( $_SERVER['SERVER_PORT'] ?? "" ) ) || ( "443" === ( $_SERVER['HTTP_X_FORWARDED_PORT'] ?? "" ) )
|
|
|| ( "off" !== ( strtolower($_SERVER['HTTPS'] ?? $_SERVER['HTTP_X_FORWARDED_SSL'] ?? $_SERVER['X-Forwarded-Ssl'] ?? "off")) );
|
|
}
|
|
|
|
protected function prepareRoute(array $routeParam, array &$arguments)
|
|
{
|
|
$route = $routeParam['route'];
|
|
|
|
if ( preg_match_all('~{(.*?)}~si', $route, $matches, PREG_SET_ORDER) ) {
|
|
$search = [];
|
|
|
|
foreach($matches as $item) {
|
|
$default = null;
|
|
|
|
$variable = $item[1];
|
|
|
|
# Handles default
|
|
if (strpos($variable, "=") !== false) {
|
|
list($variable, $default) = explode('=', $item[1]);
|
|
}
|
|
|
|
# Handles type
|
|
if (strpos($variable, ":") !== false) {
|
|
list($variable, ) = explode(':', $item[1]);
|
|
}
|
|
|
|
if ( array_key_exists($variable, $arguments) ) {
|
|
$value = $arguments[ $variable ];
|
|
unset($arguments[ $variable ]);
|
|
}
|
|
else {
|
|
if ($default ?? false) {
|
|
$value = $default;
|
|
}
|
|
elseif ($this->enforceExistingArguments) {dump($routeParam);
|
|
throw new \RuntimeException(sprintf("Error while preparing route %s : could not match variable '%s' into given arguments ( %s ) from %s::%s", $route, $variable, json_encode($arguments), $routeParam['class'], $routeParam['classMethod']));
|
|
}
|
|
}
|
|
|
|
$search[$item[0]] = $value ?? "";
|
|
|
|
}
|
|
|
|
$route = str_replace(array_keys($search), array_values($search), $route);
|
|
}
|
|
|
|
/*
|
|
* @TODO - must also take into account that recursivity is possible here [/test[/another[/still]]],
|
|
* so the regex must be adjusted (or simply using another method could also be a possiblity)
|
|
* while(strpos($route, '[') !== false && strpos($route, ']') !== false ) {
|
|
if ( preg_match_all('~[(.*?)]~si', $route, $matches, PREG_SET_ORDER) ) {
|
|
|
|
}
|
|
}*/
|
|
$route = trim(str_replace([ '[', ']' ], '', $route), '/');
|
|
|
|
return $route;
|
|
}
|
|
}
|