285 lines
		
	
	
		
			9.9 KiB
		
	
	
	
		
			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;
 | |
|     }
 | |
| }
 |