picea/src/Extension/UrlExtension.php

285 lines
9.9 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
{
if ( ! empty($_SERVER['HTTP_X_FORWARDED_PROTO']) || ! empty($_SERVER['HTTP_X_FORWARDED_SSL']) ) {
$port = "";
}
else {
$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;
}
}