[
  {
    "path": ".gitignore",
    "content": "Thumbs.db\r\nehthumbs.db\r\n.DS_Store\r\n._*\r\n.idea\r\n.idea/vcs.xml\r\nvendor\r\n.phpunit.*\r\n"
  },
  {
    "path": ".php_cs",
    "content": "<?php\n\n$header = <<<'EOF'\nThis file is part of Just.\n\n@license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n@link     https://justframework.com/php/\n@author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n@package  Just\nEOF;\n\nreturn PhpCsFixer\\Config::create()\n    ->setRiskyAllowed(true)\n    ->setRules([\n        '@PSR2' => true,\n        '@Symfony' => true,\n        '@DoctrineAnnotation' => true,\n        '@PhpCsFixer' => true,\n        'header_comment' => [\n            'commentType' => 'PHPDoc',\n            'header' => $header,\n            'separate' => 'none',\n            'location' => 'after_declare_strict',\n        ],\n        'array_syntax' => [\n            'syntax' => 'short'\n        ],\n        'list_syntax' => [\n            'syntax' => 'short'\n        ],\n        'concat_space' => [\n            'spacing' => 'one'\n        ],\n        'blank_line_before_statement' => [\n            'statements' => [\n                'declare',\n            ],\n        ],\n        'ordered_imports' => [\n            'imports_order' => [\n                'class', 'function', 'const',\n            ],\n            'sort_algorithm' => 'alpha',\n        ],\n        'single_line_comment_style' => [\n            'comment_types' => [\n            ],\n        ],\n        'yoda_style' => [\n            'always_move_variable' => false,\n            'equal' => false,\n            'identical' => false,\n        ],\n        'phpdoc_align' => [\n            'align' => 'left',\n        ],\n        'multiline_whitespace_before_semicolons' => [\n            'strategy' => 'no_multi_line',\n        ],\n        'constant_case' => [\n            'case' => 'lower',\n        ],\n        'class_attributes_separation' => true,\n        'combine_consecutive_unsets' => true,\n        'declare_strict_types' => true,\n        'linebreak_after_opening_tag' => true,\n        'lowercase_static_reference' => true,\n        'no_useless_else' => true,\n        'no_unused_imports' => true,\n        'not_operator_with_successor_space' => true,\n        'not_operator_with_space' => false,\n        'ordered_class_elements' => true,\n        'php_unit_strict' => false,\n        'phpdoc_separation' => false,\n        'single_quote' => true,\n        'standardize_not_equals' => true,\n        'multiline_comment_opening_closing' => true,\n        'phpdoc_add_missing_param_annotation' => true,\n        'no_null_property_initialization' => true\n    ])\n    ->setFinder(\n        PhpCsFixer\\Finder::create()\n            ->exclude('public')\n            ->exclude('runtime')\n            ->exclude('vendor')\n            ->in(__DIR__)\n    )\n    ->setUsingCache(false);"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2016 Mahmoud Elnezamy\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "# Route v2.0\nRoute - Fast, flexible routing for PHP, enabling you to quickly and easily build RESTful web applications.\n\n## Installation\n```bash\n$ composer require nezamy/route\n```\nOr if you looking for ready template for using this route Go to https://github.com/nezamy/just\n\n\nRoute requires PHP 7.4.0 or newer.\n\n## Changes list\n- Rewrite route based on php 7.4\n- Support Swoole extensions\n- Support locales to build multi languages website\n- Added Auth, Basic, Digest\n- Availability to customize route parser and handler\n- Smart dependency injection and service container\n\n\n## Usage\nOnly if using composer create index.php in root.\n\nCreate an index.php file with the following contents:\n```php\n<?php declare(strict_types=1);\n\ndefine('DS', DIRECTORY_SEPARATOR);\ndefine('BASE_PATH', __DIR__ . DS);\n//Show errors\n//===================================\nini_set('display_errors', '1');\nini_set('display_startup_errors', '1');\nerror_reporting(E_ALL);\n//===================================\nrequire BASE_PATH.'vendor/autoload.php';\n$request = new Just\\Http\\GlobalRequest;\n$response = new Just\\Http\\Response;\n$route = new Just\\Routing\\Router($request, $response);\n// let store them to container, to use them as a singleton\ncontainer()->set(Just\\Http\\Request::class, $request);\ncontainer()->set(Just\\Http\\Response::class, $response);\ncontainer()->set(Just\\Routing\\Router::class, $route);\n\ntry {\n    include 'app/routes.php';\n    $output = $route->run();\n\n    foreach ($output->headers->all() as $k => $v) {\n        header(\"$k: $v\");\n    }\n    http_response_code($output->statusCode());\n    if ($output->hasRedirect()) {\n        list($url, $code) = $output->getRedirect();\n        header(\"Location: $url\", true, $code);\n    }\n\n} catch (\\Error $e) {\n    pre($e, 'Error', 6);\n} catch (\\Exception $e) {\n    pre($e, 'Exception', 6);\n}\n\necho response()->body();\n```\napp/routes.php\n```php\n<?php\nuse Just\\Route;\n\nRoute::get('/', function (){\n    return 'Welcome to the home page';\n});\n\n// Maybe you want to customize 404 page\nRoute::setNotfound(function (){\n    return 'Page Not found';\n});\n```\n\n### Use with [Swoole](https://www.swoole.co.uk) \n```php\n<?php\ndeclare(strict_types=1);\n\nuse Swoole\\Http\\Server;\nuse Swoole\\Http\\Request;\nuse Swoole\\Http\\Response;\n\nuse Just\\Routing\\Router;\n\nini_set('display_errors', '1');\nini_set('display_startup_errors', '1');\nerror_reporting(E_ALL);\n\nrequire __DIR__ . '/vendor/autoload.php';\n\n$http = new Server(\"0.0.0.0\", 9501);\n$http->set([\n    'document_root' => '/var/www/public',\n    'enable_static_handler' => true,\n]);\n$http->on(\"request\", function (Request $request, Response $response) {\n\n    $request = new Just\\Http\\Request(\n       $request->header ?? [],\n       $request->server ?? [],\n       $request->cookie ?? [],\n       $request->get ?? [],\n       $request->post ?? [],\n       $request->files ?? [],\n       $request->tmpfiles ?? []\n    );\n    $response = new Just\\Http\\Response;\n    $route = new Just\\Routing\\Router($request, $response);\n\tcontainer()->set(Just\\Http\\Request::class, $request);\n\tcontainer()->set(Just\\Http\\Response::class, $response);\n\tcontainer()->set(Router::class, $route);\n    try {\n        include __DIR__ .'/app/routes.php';\n        $output = $route->run();\n        foreach ($output->headers->all() as $k => $v) {\n            $response->header($k, $v);\n        }\n\n        $response->setStatusCode($output->statusCode());\n\n        if ($output->hasRedirect()) {\n            list($url, $code) = $output->getRedirect();\n            $response->redirect($url, $code);\n        }\n    } catch (\\Error $e) {\n        pre($e, 'Error', 6);\n    } catch (\\Exception $e) {\n        pre($e, 'Exception', 6);\n    }\n    $response->end(response()->body(true));\n});\n$http->start();\n```\n\n\n## How it works\nRouting is done by matching a URL pattern with a callback function.\n\n\n### app/routes.php\n```php\nRoute::any('/', function() {\n    return 'Hello World';\n});\n\nRoute::post('/contact-us', function(\\Just\\Http\\Request $req) {\n    pre($req->body, 'Request');\n});\n\n```\n\n### The callback can be any object that is callable. So you can use a regular function:\n```php\nfunction pages() {\n    return 'Page Content';\n}\nRoute::get('/', 'pages');\n```\n\n### Or a class method:\n```php\nclass home\n{\n    public function pages() {\n        return 'Home page Content';\n    }\n}\nRoute::get('/', [home::class, 'pages']);\n// OR\nRoute::get('/', 'home@pages');\n```\n## Method Routing\n```php\nRoute::any('/', function() {});\nRoute::get('/', function() {});\nRoute::post('/', function() {});\nRoute::put('/', function() {});\nRoute::patch('/', function() {});\nRoute::option('/', function() {});\nRoute::delete('/', function() {});\n```\n\n## Parameters\n```php\n// This example will match any page name\nRoute::get('/{page}', function($page) {\n    return \"you are in $page\";\n});\n\nRoute::get('/post/{id}', function($id) {\n    // Will match anything like post/hello or post/5 ...\n    // But not match /post/5/title\n    return \"post id $id\";\n});\n\n// more than parameters\nRoute::get('/post/{id}/{title}', function($id, $title) {\n    return \"post id $id and title $title\";\n});\n\n// you can get parameter in any order\nRoute::get('/post/{id}/{title}', function($title, $id) {\n    return \"post id $id and title $title\";\n});\n```\n\n### For “unlimited” optional parameters, you can do this:\n```php\n// This example will match anything after blog/ - unlimited arguments\nRoute::get('/blog/{any}:*', function($any) {\n    pre($any);\n});\n```\n\n## Regular Expressions\nYou can validate the args by regular expressions.\n```php\n// Validate args by regular expressions uses :(your pattern here)\nRoute::get('/{username}:([0-9a-z_.-]+)/post/{id}:([0-9]+)',\nfunction($username, $id) {\n    return \"author $username post id $id\";\n});\n\n// You can add named regex pattern in routes\nRoute::addPlaceholders([\n    'username' => '([0-9a-z_.-]+)',\n    'id' => '([0-9]+)'\n]);\n\n// Now you can use named regex\nRoute::get('/{username}:username/post/{id}:id', function($username, $id) {\n    return \"author $username post id $id\";\n});\n//if the parameter name match the placeholder name just ignore placeholder and route will deduct that\nRoute::get('/{username}/post/{id}', function($username, $id) {\n    return \"author $username post id $id\";\n});\n```\n\n### Some named regex patterns already registered in routes\n```php\n[\n    'int'               => '/([0-9]+)',\n    'multiInt'          => '/([0-9,]+)',\n    'title'             => '/([a-z_-]+)',\n    'key'               => '/([a-z0-9_]+)',\n    'multiKey'          => '/([a-z0-9_,]+)',\n    'isoCode2'          => '/([a-z]{2})',\n    'isoCode3'          => '/([a-z]{3})',\n    'multiIsoCode2'     => '/([a-z,]{2,})',\n    'multiIsoCode3'     => '/([a-z,]{3,})'\n];\n```\n## Optional parameters\nYou can specify named parameters that are optional for matching by adding (?)\n```php\nRoute::get('/post/{title}?:title/{date}?',\nfunction($title, $date) {\n    $content = '';\n    if ($title) {\n        $content = \"<h1>$title</h1>\";\n    }else{\n        $content =  \"<h1>Posts List</h1>\";\n    }\n\n    if ($date) {\n        $content .= \"<small>Published $date</small>\";\n    }\n    return $content;\n\n});\n```\n## Groups\n```php\nRoute::group('/admin', function()\n{\n    // /admin/\n    Route::get('/', function() {});\n    // /admin/settings\n    Route::get('/settings', function() {});\n    // nested group\n    Route::group('/users', function()\n    {\n        // /admin/users\n        Route::get('/', function() {});\n        // /admin/users/add\n        Route::get('/add', function() {});\n    });\n    // Anything else\n    Route::any('/{any}:*', function($any) {\n        pre(\"Page ( $any ) Not Found\", 6);\n    });\n});\n```\n\n### Groups with parameters\n```php\nRoute::group('/{module}', function($lang)\n{\n    Route::post('/create', function() {});\n    Route::put('/update', function() {});\n});\n```\n\n### Locales \n```php\n// the first language is the default i.e. ar\n// when you hit the site http://localhost on the first time will redirect to  http://localhost/ar\nRoute::locale(['ar','en'], function(){\n    // will be /ar/\n    Route::get('/', function($locale){\n        //get current language\n        pre($locale);\n    });\n    // /ar/contact\n    Route::get('/contact', function() {});\n\n    Route::group('/blog', function() {\n        // /ar/blog/\n        Route::get('/', function() {});\n    });\n});\n// Also you can write locales like that or whatever you want\nRoute::locale(['ar-eg','en-us'], function(){\n    // will be /ar/\n    Route::get('/', function($locale){\n        //get current language\n        list($lang, $country) = explode('-', $locale, 2);\n        pre(\"Lang is $lang, Country is $country\");\n    });\n});\n```\n### Auth\n#### Basic\n```php\n$auth = new \\Just\\Http\\Auth\\Basic(['users' => [\n    'user1' => '123456',\n    'user2' => '987654'\n]]);\nRoute::auth($auth, function (){\n    Route::get('/secret', function(\\Just\\Http\\Request $req){\n        pre(\"Hello {$req->user()->get('username')}, this is a secret page\");\n    });\n});\n```\n#### Digest\n```php\n$auth = new \\Just\\Http\\Auth\\Digest(['users' => [\n    'user1' => '123456',\n    'user2' => '987654'\n]]);\nRoute::auth($auth, function (){\n    Route::get('/secret', function(\\Just\\Http\\Request $req){\n        pre(\"Hello {$req->user()->get('username')}, this is a secret page\");\n    });\n});\n```\n\n### Middleware\n\n#### Global\n```php\nRoute::use(function (\\Just\\Http\\Request $req, $next){\n    //validate something the call next to continue or return whatever if you want break \n    if($req->isMobile()){\n        return 'Please open from a desktop';\n    }\n    \n    return $next();\n}, function ($next){\n    // another middleware\n    $next();\n});\n\n// After \nRoute::use(function ($next){\n    $response =  $next();\n    // make some action\n    return $response;\n});\n```\n#### Middleware on groups\n```php\n// if open from mobile device\nRoute::middleware(fn(\\Just\\Http\\Request $req, $next) => !$req->isMobile() ? '' : $next())\n    ->group('/mobile-only', function (){\n        Route::get('/', function(\\Just\\Http\\Request $req){\n            pre($req->browser());\n        });\n    });\n```\nIf you make the middleware as a class, you can pass the class with namespace.\nthe class should be had a `handle` method.  \n```php\nclass MobileOnly{\n    public function handle(\\Just\\Http\\Request $req, $next){\n        return !$req->isMobile() ? '' : $next();\n    }\n}\nRoute::middleware(MobileOnly::class)\n    ->group('/',function (){\n        Route::get('/', function(\\Just\\Http\\Request $req){\n            pre($req->browser());\n        });\n    });\n```\n\n#### Middleware on route\n```php\nRoute::get('/', function(\\Just\\Http\\Request $req){\n    pre($req->browser());\n})->middleware(MobileOnly::class);\n```\n\n### Dependency injection\nTo learn about Dependency injection and service container please visit this [link](https://github.com/nezamy/di)\n\n### Handle and Parser customization\nExample of CustomRouteHandler\n```php\nclass CustomRouteHandler implements Just\\Routing\\RouteHandlerInterface\n{\n    public function call(callable $handler, array $args = [])\n    {\n        return call_user_func_array($handler, $args);\n    }\n\n    public function parse($handler): callable\n    {\n        if (is_string($handler) && ! function_exists($handler)) {\n            $handler = explode('@', $handler, 2);\n        }\n        return $handler;\n    }\n}\n\\Just\\Route::setHandler(new CustomRouteHandler);\n```\n\n\n```php\nclass CustomRouteParser implements RouteParserInterface\n{\n    public function parse(string $uri): array\n    {\n        $matchedParameter = [];\n        $matchedPattern = [];\n        $result = [];\n        // parse uri here and return array of 3 elements\n        // /{page}\n        // /{page}?\n\n        return ['parameters' => $matchedParameter, 'patterns' => $matchedPattern, 'result' => $result];\n    }\n}\n\\Just\\Route::setParser(new CustomRouteParser);\n```\n"
  },
  {
    "path": "composer.json",
    "content": "{\r\n  \"name\": \"nezamy/route\",\r\n  \"type\": \"library\",\r\n  \"description\": \"Route - Fast, flexible routing for PHP, enabling you to quickly and easily build RESTful web applications.\",\r\n  \"keywords\": [\r\n    \"route\",\r\n    \"restful\",\r\n    \"api\",\r\n    \"justframework\"\r\n  ],\r\n  \"homepage\": \"https://nezamy.com/route\",\r\n  \"license\": \"MIT\",\r\n  \"authors\": [\r\n    {\r\n      \"name\": \"Mahmoud Elnezamy\",\r\n      \"email\": \"mahmoud@nezamy.com\",\r\n      \"homepage\": \"http://nezamy.com\",\r\n      \"role\": \"Full Stack Web Developer\"\r\n    }\r\n  ],\r\n\r\n  \"autoload\": {\r\n    \"psr-4\": {\r\n      \"Just\\\\\": \"src/\",\r\n      \"\": \"\"\r\n    },\r\n    \"files\": [\r\n      \"src/functions.php\"\r\n    ]\r\n  },\r\n  \"autoload-dev\": {\r\n    \"psr-4\": {\r\n      \"Just\\\\Test\\\\\": \"tests/\"\r\n    }\r\n  },\r\n  \"require\": {\r\n    \"php\": \">=7.4\",\r\n    \"ext-json\": \"*\",\r\n    \"ext-mbstring\": \"*\",\r\n    \"nezamy/di\": \"^2.0\"\r\n  },\r\n  \"require-dev\": {\r\n    \"phpunit/phpunit\": \"^7.5 || ^8.5\",\r\n    \"friendsofphp/php-cs-fixer\": \"^2.16\"\r\n  },\r\n  \"scripts\": {\r\n    \"fix\": \"php-cs-fixer fix ./src\",\r\n    \"test\": \"phpunit\"\r\n  }\r\n}\r\n"
  },
  {
    "path": "phpunit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<phpunit backupGlobals=\"true\"\n         backupStaticAttributes=\"false\"\n         bootstrap=\"tests/bootstrap.php\"\n         colors=\"true\"\n         convertErrorsToExceptions=\"true\"\n         convertNoticesToExceptions=\"true\"\n         convertWarningsToExceptions=\"true\"\n         processIsolation=\"false\"\n         stopOnFailure=\"false\"\n         testdox=\"true\">\n    <testsuites>\n        <testsuite name=\"Application Test Suite\">\n            <directory>./tests/</directory>\n        </testsuite>\n    </testsuites>\n    <filter>\n        <whitelist>\n            <directory suffix=\".php\">app/</directory>\n        </whitelist>\n    </filter>\n    <php>\n    </php>\n</phpunit>"
  },
  {
    "path": "src/DataType/Uri.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\DataType;\n\nuse Just\\Prototype\\StringPrototype;\n\nclass Uri extends StringPrototype\n{\n    public function getChunk(?string $uri = null)\n    {\n        $uri = $uri ?? $this->data;\n        $chunk = $uri ? '/' . explode('/', $uri)[1] : '/';\n        return $this->isStatic($chunk) ? $chunk : '/*';\n    }\n\n    public function isStatic(?string $uri = null)\n    {\n        $uri = $uri ?? $this->data;\n        return ! $uri || $uri && strpbrk('*{}?:()', $uri) === false;\n    }\n\n    public function rtrim($char)\n    {\n        $this->data = rtrim($this->data, $char);\n    }\n\n    public function ltrim($char)\n    {\n        $this->data = ltrim($this->data, $char);\n    }\n}\n"
  },
  {
    "path": "src/Http/Auth/AuthInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http\\Auth;\n\ninterface AuthInterface\n{\n    public function check(): bool;\n\n    public function validate(array $credentials): bool;\n\n    public function redirectToLogin();\n\n    public function login(): bool;\n\n    public function logout(): void;\n\n    public function user();\n}\n"
  },
  {
    "path": "src/Http/Auth/Basic.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http\\Auth;\n\nclass Basic implements AuthInterface\n{\n    public $error = '';\n\n    protected $options = [];\n\n    private $user = [];\n\n    public function __construct($options)\n    {\n        $this->options = array_merge([\n            'users' => [],\n            'realm' => 'Restricted area',\n        ], $options);\n    }\n\n    public function check(): bool\n    {\n        return $this->login();\n    }\n\n    public function redirectToLogin()\n    {\n        $this->logout();\n        if (! $this->login()) {\n            $this->logout();\n            response()->end($this->error);\n        }\n    }\n\n    public function login(array $credentials = []): bool\n    {\n        $username = $password = null;\n\n        if (request()->server->has('PHP_AUTH_USER') && request()->server->has('PHP_AUTH_PW')) {\n            $username = request()->server->get('PHP_AUTH_USER');\n            $password = request()->server->get('PHP_AUTH_PW');\n        } elseif (request()->headers->has('authorization')) {\n            $auth = request()->headers->get('authorization');\n            if (strpos(strtolower($auth), 'basic') === 0) {\n                [$username, $password] = explode(':', base64_decode(substr($auth, 6)));\n            }\n        }\n\n        return $this->validate(['username' => $username, 'password' => $password]);\n    }\n\n    public function logout(): void\n    {\n        response()->headers->set('WWW-Authenticate', 'Basic realm=\"' . $this->options['realm'] . '\"');\n        response()->setStatusCode(401);\n    }\n\n    public function validate(array $credentials): bool\n    {\n        if ($credentials['username'] && $credentials['password']) {\n            $username = filter_var($credentials['username'], FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH | FILTER_FLAG_ENCODE_LOW);\n            $password = filter_var($credentials['password'], FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH | FILTER_FLAG_ENCODE_LOW);\n            $users = $this->options['users'];\n            if (isset($users[$username]) && $users[$username] == $password) {\n                unset($credentials['password']);\n                $this->user = $credentials;\n                return true;\n            }\n        }\n        $this->error = 'Authorization Required';\n        return false;\n    }\n\n    public function user(): array\n    {\n        return $this->user;\n    }\n}\n"
  },
  {
    "path": "src/Http/Auth/Digest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http\\Auth;\n\nclass Digest implements AuthInterface\n{\n    public $error = '';\n\n    protected $options = [];\n\n    private $user = [];\n\n    public function __construct($options)\n    {\n        $this->options = array_merge([\n            'users' => [],\n            'realm' => 'Restricted area',\n            'qop' => 'auth',\n            'nonce' => uniqid(),\n            'error' => 'Wrong Credentials!',\n            'cancel' => 'Authorization Required',\n        ], $options);\n    }\n\n    public function check(): bool\n    {\n        return $this->login();\n    }\n\n    public function redirectToLogin()\n    {\n        $this->logout();\n        if (! $this->login()) {\n            $this->logout();\n            response()->end($this->error);\n        }\n    }\n\n    public function login(array $credentials = []): bool\n    {\n        $data = [];\n        if (request()->server->has('PHP_AUTH_DIGEST')) {\n            $data = $this->digest_parse(request()->server->get('PHP_AUTH_DIGEST'));\n        } elseif (request()->headers->has('authorization')) {\n            $data = $this->digest_parse(request()->headers->get('authorization'));\n        }\n\n        return $this->validate($data);\n    }\n\n    public function logout(): void\n    {\n        response()->headers->set('WWW-Authenticate', 'Digest realm=\"' . $this->options['realm'] . '\",qop=\"' . $this->options['qop'] . '\",nonce=\"' . $this->options['nonce'] . '\",opaque=\"' . md5($this->options['realm']) . '\"');\n        response()->setStatusCode(401);\n    }\n\n    public function validate(array $credentials): bool\n    {\n        if (! $credentials || ! isset($this->options['users'][$credentials['username']])) {\n            $this->error = $this->options['error'];\n            return false;\n        }\n        $A1 = md5($credentials['username'] . ':' . $this->options['realm'] . ':' . $this->options['users'][$credentials['username']]);\n        $A2 = md5(request()->method() . ':' . $credentials['uri']);\n        $valid_response = md5($A1 . ':' . $credentials['nonce'] . ':' . $credentials['nc'] . ':' . $credentials['cnonce'] . ':' . $credentials['qop'] . ':' . $A2);\n        if ($credentials['response'] != $valid_response) {\n            $this->error = $this->options['error'];\n            return false;\n        }\n\n        return true;\n    }\n\n    public function user(): array\n    {\n        return $this->user;\n    }\n\n    private function digest_parse($txt)\n    {\n        // protect against missing data\n        $needed_parts = ['nonce' => 1, 'nc' => 1, 'cnonce' => 1, 'qop' => 1, 'username' => 1, 'uri' => 1, 'response' => 1];\n        $data = [];\n        $keys = implode('|', array_keys($needed_parts));\n\n        preg_match_all('@(' . $keys . ')=(?:([\\'\"])([^\\2]+?)\\2|([^\\s,]+))@', $txt, $matches, PREG_SET_ORDER);\n\n        foreach ($matches as $m) {\n            $data[$m[1]] = $m[3] ? $m[3] : $m[4];\n            unset($needed_parts[$m[1]]);\n        }\n\n        return $needed_parts ? [] : $data;\n    }\n}\n"
  },
  {
    "path": "src/Http/Auth.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http;\n\nuse Just\\Http\\Auth\\AuthInterface;\n\n/**\n * Class Auth.\n * @method bool check()\n * @method bool validate(array $credentials)\n * @method void redirectToLogin()\n * @method bool login()\n * @method void logout()\n * @method array user()\n */\nclass Auth\n{\n    private AuthInterface $auth;\n\n    public function __construct(AuthInterface $auth)\n    {\n        $this->auth = $auth;\n        if (! $auth->check()) {\n            $auth->redirectToLogin();\n        }\n    }\n\n    public function __call($method, $args)\n    {\n        if (method_exists($this->auth, $method)) {\n            return call_user_func_array([$this->auth, $method], $args);\n        }\n        throw new \\LogicException(\"{$method} Not Exists on Just\\\\Http\\\\Auth\");\n    }\n}\n"
  },
  {
    "path": "src/Http/GlobalRequest.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http;\n\nclass GlobalRequest extends Request\n{\n    public function __construct()\n    {\n        $headers = [];\n        $server = [];\n\n        foreach ($_SERVER as $key => $value) {\n            if (substr($key, 0, 5) === 'HTTP_') {\n                $key = substr($key, 5);\n//                $key = $this->parseHeaderKey($key);\n                $headers[$key] = $value;\n                continue;\n            }\n\n            $key = strtolower($key);\n            $server[$key] = $value;\n        }\n\n        if (! isset($headers['authorization'])) {\n            if (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {\n                $headers['authorization'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];\n            } elseif (isset($_SERVER['PHP_AUTH_USER'])) {\n                $basic_pass = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : '';\n                $headers['authorization'] = 'Basic ' . base64_encode($_SERVER['PHP_AUTH_USER'] . ':' . $basic_pass);\n            } elseif (isset($_SERVER['PHP_AUTH_DIGEST'])) {\n                $headers['authorization'] = $_SERVER['PHP_AUTH_DIGEST'];\n            }\n        }\n\n        if (isset($server['content_type']) && $server['content_type'] == 'application/x-www-form-urlencoded') {\n            parse_str(file_get_contents('php://input'), $input);\n        } else {\n            $input = json_decode(file_get_contents('php://input'), true);\n        }\n        $post = array_merge($input ?? [], $_POST);\n\n        parent::__construct($headers, $server, $_COOKIE, $_GET, $post, $_FILES);\n    }\n}\n"
  },
  {
    "path": "src/Http/Middleware.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http;\n\nuse Just\\Routing\\RouteHandlerInterface;\n\nclass Middleware\n{\n    private array $layers = [];\n    private RouteHandlerInterface $handler;\n\n    public function __construct(RouteHandlerInterface $handler)\n    {\n        $this->handler = $handler;\n    }\n\n    public function add(array $layers): void\n    {\n        $this->layers = array_merge($this->layers, $layers);\n    }\n\n    public function handle(callable $core)\n    {\n        $layers = array_reverse($this->layers);\n        $final =  array_reduce($layers, function ($nextLayer, $layer) {\n            return $this->createLayer($nextLayer, $layer);\n        }, function () use ($core) {\n            return $this->handler->call($core);\n        });\n\n        return $this->handler->call($final);\n    }\n\n    private function createLayer($nextLayer, $layer): callable\n    {\n        return function () use ($nextLayer, $layer) {\n            container()->setVar('next', $nextLayer);\n            if(is_string($layer) && class_exists($layer)){\n                $layer = [$layer, 'handle'];\n            }\n            $layer = $this->handler->parse($layer);\n            return $this->handler->call($layer);\n        };\n    }\n}\n"
  },
  {
    "path": "src/Http/Request.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http;\n\nuse Just\\Prototype\\GetterObject;\n\n/**\n * Class Request.\n * @method array arguments()\n * @method string uri()\n * @method string method()\n * @method string currentUrl()\n * @method string protocol()\n * @method string scheme()\n * @method array user()\n */\nclass Request\n{\n    public GetterObject $headers;\n\n    public GetterObject $server;\n\n    public GetterObject $cookies;\n\n    public GetterObject $query;\n\n    public GetterObject $body;\n\n    public GetterObject $files;\n\n    public GetterObject $tmp_files;\n\n    protected $_arguments = [];\n\n    protected $_uri;\n\n    protected $_method;\n\n    protected $_url;\n\n    protected $_currentUrl;\n\n    protected $_protocol;\n\n    protected $_scheme;\n\n    protected GetterObject $_user;\n\n    public function __construct(array $headers, array $server, array $cookies, array $query, array $body, array $files, $tmp_files = [])\n    {\n        $this->headers = new class($headers) extends GetterObject {\n            protected function getterTransformKey($key): string\n            {\n                return  str_replace([' ', '_'], '-', strtolower($key));\n            }\n        };\n        $this->server = new class($server) extends GetterObject {\n            protected function getterTransformKey($key): string\n            {\n                return strtolower($key);\n            }\n        };\n\n        $this->cookies = new GetterObject($cookies);\n        $this->query = new GetterObject($query);\n        $this->body = new GetterObject($body);\n        $this->files = new GetterObject($files);\n        $this->tmp_files = new GetterObject($tmp_files);\n        $this->_protocol = isset($server['server_protocol']) ? str_replace('HTTP/', '', $server['server_protocol']) : '1.1';\n        $this->_scheme = isset($server['https']) && $server['https'] === 'on' || isset($headers['x-forwarded-proto']) && $headers['x-forwarded-proto'] == 'https' ? 'https' : 'http';\n        $this->_uri = urldecode(parse_url($server['request_uri'] ?? '', PHP_URL_PATH) ?? '') ?? '/';\n        $this->_url = $this->_scheme . '://' . $this->serverName();\n        $this->_currentUrl = $this->url(rtrim($this->_uri, '/') ?? '');\n        $this->_method = $server['request_method'] ?? 'GET';\n    }\n\n    public function __call($method, $args)\n    {\n        if (isset($this->{'_' . $method})) {\n            return $this->{'_' . $method};\n        }\n        throw new \\LogicException(\"{$method} Not Exists on Just\\\\Http\\\\Request\");\n    }\n\n    public function url($uri = ''): string\n    {\n        return $this->_url . $uri;\n    }\n\n    public function serverName(): string\n    {\n        return $this->server->get('server_name', $this->headers->get('host', 'localhost'));\n    }\n\n    public function setArguments(array $args): void\n    {\n        $this->_arguments = $args;\n    }\n\n    public function setUser(array $user): void\n    {\n        $this->_user = new GetterObject($user);\n    }\n\n    public function isJson(): bool\n    {\n        if ($this->headers->get('X_REQUESTED_WITH') == 'XMLHttpRequest' || strpos($this->headers->get('ACCEPT'), '/json') !== false) {\n            return true;\n        }\n        return false;\n    }\n\n    public function ip(): string\n    {\n        if ($this->headers->has('client_ip')) {\n            $ip = $this->headers->get('client_ip');\n        } elseif ($this->headers->has('x_forwarded_for')) {\n            $ip = $this->headers->get('x_forwarded_for');\n        } elseif ($this->headers->has('x_forwarded')) {\n            $ip = $this->headers->get('x_forwarded');\n        } elseif ($this->headers->has('forwarded_for')) {\n            $ip = $this->headers->get('forwarded_for');\n        } elseif ($this->headers->has('forwarded')) {\n            $ip = $this->headers->get('forwarded');\n        } elseif ($this->server->has('remote_addr')) {\n            $ip = $this->server->get('remote_addr');\n        } else {\n            $ip = getenv('REMOTE_ADDR');\n        }\n\n        if (! filter_var($ip, FILTER_VALIDATE_IP)) {\n            return 'unknown';\n        }\n\n        return $ip;\n    }\n\n    public function browser(): string\n    {\n        $user_agent = $this->headers->get('user-agent');\n        if (strpos($user_agent, 'Opera') || strpos($user_agent, 'OPR/')) {\n            return 'Opera';\n        }\n        if (strpos($user_agent, 'Edge')) {\n            return 'Edge';\n        }\n        if (strpos($user_agent, 'Chrome')) {\n            return 'Chrome';\n        }\n        if (strpos($user_agent, 'Safari')) {\n            return 'Safari';\n        }\n        if (strpos($user_agent, 'Firefox')) {\n            return 'Firefox';\n        }\n        if (strpos($user_agent, 'MSIE') || strpos($user_agent, 'Trident/7')) {\n            return 'Internet Explorer';\n        }\n        return 'unknown';\n    }\n\n    public function platform(): string\n    {\n        $user_agent = $this->headers->get('user-agent');\n        if (preg_match('/linux/i', $user_agent)) {\n            return 'linux';\n        }\n        if (preg_match('/macintosh|mac os x/i', $user_agent)) {\n            return 'mac';\n        }\n        if (preg_match('/windows|win32/i', $user_agent)) {\n            return 'windows';\n        }\n        return 'unknown';\n    }\n\n    public function isMobile(): bool\n    {\n        $aMobileUA = [\n            '/iphone/i' => 'iPhone',\n            '/ipod/i' => 'iPod',\n            '/ipad/i' => 'iPad',\n            '/android/i' => 'Android',\n            '/blackberry/i' => 'BlackBerry',\n            '/webos/i' => 'Mobile',\n        ];\n\n        // Return true if mobile User Agent is detected.\n        foreach ($aMobileUA as $sMobileKey => $sMobileOS) {\n            if (preg_match($sMobileKey, $this->headers->get('user-agent'))) {\n                return true;\n            }\n        }\n        // Otherwise, return false.\n        return false;\n    }\n}"
  },
  {
    "path": "src/Http/Response.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http;\n\nuse Just\\Prototype\\ObjectStore;\n\nclass Response\n{\n    public $headers;\n\n    protected $_body = '';\n\n    protected $_statusCode = 200;\n\n    protected $_redirect = [];\n\n    private $_end = false;\n\n    public function __construct()\n    {\n        $this->headers = new ObjectStore();\n    }\n\n    public function body($clean = false): string\n    {\n        $body = $this->_body;\n        if ($clean) {\n            $this->_body = '';\n        }\n        return $body;\n    }\n\n    public function write($body): void\n    {\n        if (! $this->_end) {\n            $this->_body .= $body;\n        }\n    }\n\n    public function end(string $body = ''): void\n    {\n        if (! $this->_end) {\n            $this->_body = $body;\n            $this->_end = true;\n        }\n    }\n\n    public function isEnded(): bool\n    {\n        return $this->_end;\n    }\n\n    public function setStatusCode(int $statusCode): void\n    {\n        $this->_statusCode = $statusCode;\n    }\n\n    public function statusCode(): int\n    {\n        return $this->_statusCode;\n    }\n\n    public function redirectTo(string $location, int $status_code = 302)\n    {\n        $this->_redirect = [$location, $status_code];\n    }\n\n    public function hasRedirect(): bool\n    {\n        return count($this->_redirect) > 0;\n    }\n\n    public function getRedirect()\n    {\n        return $this->_redirect;\n    }\n}\n"
  },
  {
    "path": "src/Http/Session.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Http;\n\nclass Session\n{\n    private $options = [];\n\n    public function __construct(array $options)\n    {\n        $this->options = array_merge([\n            'storage',\n        ], $options);\n    }\n}\n"
  },
  {
    "path": "src/Prototype/ArrayPrototype.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Prototype;\n\nclass ArrayPrototype\n{\n    use Getter;\n    use ConvertObject;\n    use Setter;\n\n    public function __construct(array $data = [])\n    {\n        $this->replace($data);\n        return $this;\n    }\n\n//    public function set(string $key, $value): void {\n//        if(!$value instanceof StringPrototype){\n//            $type = gettype($value);\n//            switch ($type) {\n//                case 'string':\n//                    $value = new StringPrototype($value);\n//                    break;\n//                case 'array':\n//                case 'object':\n//                    $value = new ArrayPrototype((array)$value);\n//            }\n//        }\n//        $this->_set($key, $value);\n//    }\n}\n"
  },
  {
    "path": "src/Prototype/ConvertObject.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Prototype;\n\nuse Just\\Storage\\KeyValue\\KeyValueStoreInterface;\n\ntrait ConvertObject\n{\n    public function __toString(): string\n    {\n        return $this->toJson();\n    }\n\n    public function toArray($data = null): array\n    {\n        $array = [];\n        $data = $data ?? $this->all();\n        foreach ((array) $data as $k => $v) {\n            if (is_object($v)) {\n                if ($v instanceof StringPrototype) {\n                    $array[$k] = (string) $v;\n                } else {\n                    $array[$k] = $v instanceof KeyValueStoreInterface ? $this->toArray($v->all()) : $v;\n                }\n            } else {\n                $array[$k] = $v;\n            }\n        }\n\n        return $array;\n    }\n\n    public function toJson(): string\n    {\n        return json_encode($this->toArray(), JSON_PRETTY_PRINT);\n    }\n}\n"
  },
  {
    "path": "src/Prototype/Getter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Prototype;\n\nuse ArrayIterator;\n\ntrait Getter\n{\n    private $data;\n\n    public function get(string $key, $default = '')\n    {\n        return $this->data[$this->getterTransformKey($key)] ?? $default;\n    }\n\n    public function has(string $key): bool\n    {\n        return isset($this->data[$this->getterTransformKey($key)]);\n    }\n\n    public function all(): array\n    {\n        return $this->data;\n    }\n\n    public function only(...$keys): array\n    {\n        $results = [];\n        $keys = $this->variadic(...$keys);\n        foreach ($keys as $key) {\n            $results[$key] = $this->get($key);\n        }\n        return $results;\n    }\n\n    public function except(...$keys): array\n    {\n        $results = $this->data;\n        $keys = $this->variadic(...$keys);\n        foreach ($keys as $key) {\n            unset($results[$key]);\n        }\n        return $results;\n    }\n\n    public function keys(): array\n    {\n        return array_keys($this->data);\n    }\n\n    public function count(): int\n    {\n        return count($this->data);\n    }\n\n    public function last()\n    {\n        return array_key_last($this->data);\n    }\n\n    public function getIterator(): ArrayIterator\n    {\n        return new ArrayIterator($this->data);\n    }\n\n    public function variadic(...$keys): array\n    {\n        if (is_array($keys[0])) {\n            $shift = array_shift($keys);\n            $keys = array_merge($shift, $keys);\n        }\n        return $keys;\n    }\n\n    protected function getterTransformKey($key): string\n    {\n        return $key;\n    }\n}\n"
  },
  {
    "path": "src/Prototype/GetterObject.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Prototype;\n\nclass GetterObject\n{\n    use Getter;\n\n    public function __construct(array $data = [])\n    {\n        foreach ($data as $k => $v) {\n            $this->set($k, $v);\n        }\n    }\n\n    private function set(string $key, $value): void\n    {\n        $this->data[$this->getterTransformKey($key)] = $value;\n    }\n}\n"
  },
  {
    "path": "src/Prototype/ObjectStore.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Prototype;\n\nclass ObjectStore\n{\n    use Setter;\n    use Getter;\n    use ConvertObject;\n\n    public function __construct(array $data = [])\n    {\n        $this->replace($data);\n    }\n}\n"
  },
  {
    "path": "src/Prototype/Setter.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Prototype;\n\ntrait Setter\n{\n    private $data;\n\n    public function set(string $key, $value): void\n    {\n        $this->data[$this->setterTransformKey($key)] = $this->setterTransformValue($value);\n    }\n\n    public function push(string $key, $value): void\n    {\n        $key = $this->setterTransformKey($key);\n        if (! isset($this->data[$key])) {\n            $this->data[$key] = [];\n        } elseif (! is_array($this->data[$key])) {\n            $this->data[$key] = (array) $this->data[$key];\n        }\n        $this->data[$key][] = $this->setterTransformValue($value);\n    }\n\n    public function pop(string $key)\n    {\n        $key = $this->setterTransformKey($key);\n        if (! isset($this->data[$key])) {\n            throw new \\LogicException(__CLASS__ . \" | The value of [key] {$this->data[$key]} is not defined\");\n        }\n        if (! is_array($this->data[$key])) {\n            throw new \\LogicException(__CLASS__ . \" | The value of [key] {$this->data[$key]} is not array\");\n        }\n\n        return array_pop($this->data[$key]);\n    }\n\n    public function increment(string $key, int $by = 1): int\n    {\n        $key = $this->setterTransformKey($key);\n        if (isset($this->data[$key]) && (string) $this->data[$key] !== ((string) (int) $this->data[$key])) {\n            throw new \\LogicException(__CLASS__ . \" | The value of [key] {$this->data[$key]} is not integer\");\n        }\n        return $this->data[$key] = (int) ($this->data[$key] ?? 0) + $by;\n    }\n\n    public function decrement(string $key, int $by = 1): int\n    {\n        $key = $this->setterTransformKey($key);\n        if (isset($this->data[$key]) && (string) $this->data[$key] !== ((string) (int) $this->data[$key])) {\n            throw new \\LogicException(__CLASS__ . \" | the value of [key] {$this->data[$key]} is not integer\");\n        }\n        return $this->data[$key] = (int) ($this->data[$key] ?? 0) - $by;\n    }\n\n    public function add(array $data): void\n    {\n        foreach ($data as $k => $v) {\n            $this->set($k, $v);\n        }\n    }\n\n    public function replace(array $data): void\n    {\n        $this->clear();\n        foreach ($data as $k => $v) {\n            $this->set((string) $k, $v);\n        }\n    }\n\n    public function remove(string $key): void\n    {\n        unset($this->data[$this->setterTransformKey($key)]);\n    }\n\n    public function clear(): void\n    {\n        $this->data = [];\n    }\n\n    protected function setterTransformKey($key): string\n    {\n        return $key;\n    }\n\n    protected function setterTransformValue($value)\n    {\n        return $value;\n    }\n}\n"
  },
  {
    "path": "src/Prototype/StringPrototype.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Prototype;\n\nclass StringPrototype\n{\n    protected string $data;\n\n    public function __construct(string $value)\n    {\n        $this->data = $value;\n    }\n\n    public function __toString(): string\n    {\n        return $this->data;\n    }\n\n    public function set($newValue)\n    {\n        $this->data = $newValue;\n    }\n\n    public function eq(string $string)\n    {\n        return $this->data === $string;\n    }\n\n    public function match(string $string, &$matches)\n    {\n        return preg_match($string, $this->data, $matches);\n    }\n\n    public function ends(string $string)\n    {\n        return substr($this->data, -strlen($string)) === $string;\n    }\n\n    public function startsWith(string $string)\n    {\n        return substr($this->data, 0, strlen($string)) === $string;\n    }\n\n    public function limit(int $limit, $trimMarker = '')\n    {\n        return mb_strimwidth($this->data, 0, $limit, $trimMarker);\n    }\n\n    /**\n     * @return bool|int\n     */\n    public function contain(string $string)\n    {\n        return strpos($this->data, $string);\n    }\n\n    /**\n     * @return $this\n     */\n    public function trim(string $character_mask = \" \\t\\n\\r\\0\\x0B\")\n    {\n        $this->data = trim($this->data, $character_mask);\n        return $this;\n    }\n\n    public function append($value): void\n    {\n        $this->data .= $value;\n    }\n\n    public function prepend($value): void\n    {\n        $this->data = $value . $this->data;\n    }\n}\n"
  },
  {
    "path": "src/Route.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just;\n\nuse Just\\Http\\Auth\\AuthInterface;\nuse Just\\Routing\\RouteHandlerInterface;\nuse Just\\Routing\\RouteParserInterface;\n\n/**\n * Class App.\n *\n * @method static \\Just\\Routing\\Route any(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Route get(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Route head(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Route post(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Route put(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Route patch(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Route options(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Route delete(string $uri, mixed $handler)\n * @method static \\Just\\Routing\\Router middleware(...$middleware)\n * @method static \\Just\\Routing\\Route auth(AuthInterface $auth, callable $callback)\n * @method static \\Just\\Routing\\Route locale(array $locales, callable $callback)\n * @method static void addPlaceholders(array $patterns)\n * @method static void setNotfound($handler)\n * @method static void group($prefix, callable $callback)\n * @method static void use(...$middleware)\n * @method static void setHandler(RouteHandlerInterface $handler)\n * @method static void setParser(RouteParserInterface $parser)\n *\n */\nclass Route\n{\n    public static function __callStatic($method, $args)\n    {\n        $instance = container()->get(\\Just\\Routing\\Router::class);\n        return $instance->{$method}(...$args);\n    }\n}\n"
  },
  {
    "path": "src/Routing/Route.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Routing;\n\nuse Just\\DataType\\Uri;\nuse Just\\Http\\Auth\\AuthInterface;\nuse Just\\Prototype\\ArrayPrototype;\n\nclass Route\n{\n    public ArrayPrototype $options;\n\n    private string $method;\n\n    private Uri $uri;\n\n    /**\n     * @var callable\n     */\n    private $handler;\n\n    private array $args = [];\n\n    private array $middleware = [];\n\n    public function __construct(string $method, Uri $uri, $handler, array $options = [])\n    {\n        $this->method = $method;\n        $this->uri = $uri;\n        $this->handler = $handler;\n        $this->options = new ArrayPrototype($options);\n    }\n\n    public function getMethod(): string\n    {\n        return $this->method;\n    }\n\n    public function getUri(): Uri\n    {\n        return $this->uri;\n    }\n\n//    parse before get\n\n    public function getHandler(RouteHandlerInterface $handler): callable\n    {\n        return $handler->parse($this->handler);\n    }\n\n    public function addNamespaceToHandler()\n    {\n        if (is_string($this->handler)) {\n            $this->handler = $this->options->get('namespace') . '\\\\' . $this->handler;\n        } elseif (is_array($this->handler)) {\n            $this->handler[0] = $this->options->get('namespace') . '\\\\' . $this->handler[0];\n        }\n    }\n\n    public function getArgs(): array\n    {\n        return $this->args;\n    }\n\n    public function setArgs(array $args): void\n    {\n        $this->args = $args;\n    }\n\n    public function middleware(...$middleware)\n    {\n        $this->middleware = array_merge($this->middleware, $middleware);\n        return $this;\n    }\n\n    public function getMiddleware()\n    {\n        return $this->middleware;\n    }\n\n    public function hasMiddleware()\n    {\n        return count($this->middleware) > 0;\n    }\n\n    public function withoutMiddleware(...$middleware)\n    {\n        $this->middleware = array_filter($this->middleware, fn ($var) => ! in_array($var, $middleware));\n        return $this;\n    }\n}\n"
  },
  {
    "path": "src/Routing/RouteHandler.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Routing;\n\nuse Just\\DI\\Resolver;\nuse LogicException;\n\nclass RouteHandler implements RouteHandlerInterface\n{\n    private Resolver $resolver;\n\n    public function __construct()\n    {\n        $this->resolver = new Resolver();\n    }\n\n    public function call(callable $handler, array $args = [])\n    {\n        return $this->resolver->resolve($handler);\n    }\n\n    public function parse($handler): callable\n    {\n        if (is_string($handler) && ! function_exists($handler)) {\n            $handler = str_replace('::', '@', $handler);\n            $handler = explode('@', $handler, 2);\n        }\n\n        if (is_callable($handler)) {\n            if (is_array($handler)) {\n                $handler = $this->resolver->prepare($handler);\n            }\n            return $handler;\n        }\n\n        if (! is_callable($handler)) {\n            $method = isset($handler[1]) ? '::' . $handler[1] : '';\n            throw new LogicException(\"[{$handler[0]}{$method}] is not callable\");\n        }\n        return $handler;\n    }\n}\n"
  },
  {
    "path": "src/Routing/RouteHandlerInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Routing;\n\ninterface RouteHandlerInterface\n{\n    public function call(callable $handler, array $args = []);\n\n    public function parse($handler): callable;\n}\n"
  },
  {
    "path": "src/Routing/RouteParser.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Routing;\n\nuse Just\\Support\\Regex;\n\nclass RouteParser implements RouteParserInterface\n{\n    public function parse(string $uri): array\n    {\n        $matchedParameter = [];\n        $matchedPattern = [];\n        $result = preg_replace_callback('/\\/\\{([a-z-0-9@]+)\\}\\??((:\\(?[^\\/]+\\)?)?)/i', function ($match) use (&$matchedParameter, &$matchedPattern) {\n            [$full, $parameter, $namedPattern] = $match;\n            $pattern = '/' . Regex::get('?');\n            if (! empty($namedPattern)) {\n                $replace = substr($namedPattern, 1);\n\n                if (Regex::has($replace)) {\n                    $pattern = '/' . Regex::get($replace);\n                } elseif (substr($replace, 0, 1) == '(' && substr($replace, -1, 1) == ')') {\n                    $pattern = '/' . $replace;\n                }\n            } elseif (Regex::has($parameter)) {\n                $pattern = '/' . Regex::get($parameter);\n            }\n            // Check whether parameter is optional.\n            if (strpos($full, '?') !== false) {\n                $pattern = str_replace(['/(', '|'], ['(/', '|/'], $pattern) . '?';\n            }\n            $matchedParameter[] = $parameter;\n            $matchedPattern[] = $pattern;\n\n            return $pattern;\n        }, trim($uri));\n\n        return ['parameters' => $matchedParameter, 'patterns' => $matchedPattern, 'result' => $result];\n    }\n}\n"
  },
  {
    "path": "src/Routing/RouteParserInterface.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Routing;\n\ninterface RouteParserInterface\n{\n    /**\n     * @param string $uri i.e '/{username}:([0-9a-z_.-]+)/post/{id}:([0-9]+)'\n     * @return array [\n     *               'parameters' => ['username', 'id'],\n     *               'patterns' => ['/([0-9a-z_.-]+)', '/([0-9]+)'],\n     *               'result' => '/([0-9a-z_.-]+)/post/([0-9]+)'\n     *               ]\n     */\n    public function parse(string $uri): array;\n}\n"
  },
  {
    "path": "src/Routing/Router.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Routing;\n\nuse Just\\DataType\\Uri;\nuse Just\\Http\\Auth;\nuse Just\\Http\\Auth\\AuthInterface;\nuse Just\\Http\\Middleware;\nuse Just\\Http\\Request;\nuse Just\\Http\\Response;\nuse Just\\Prototype\\ArrayPrototype;\nuse Just\\Support\\Regex;\n\n/**\n * Class Route.\n * @method Route any(string $uri, mixed $handler, array $options = [])\n * @method Route get(string $uri, mixed $handler, array $options = [])\n * @method Route head(string $uri, mixed $handler, array $options = [])\n * @method Route post(string $uri, mixed $handler, array $options = [])\n * @method Route put(string $uri, mixed $handler, array $options = [])\n * @method Route patch(string $uri, mixed $handler, array $options = [])\n * @method Route options(string $uri, mixed $handler, array $options = [])\n * @method Route delete(string $uri, mixed $handler, array $options = [])\n */\nclass Router\n{\n    protected array $allowedMethods = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'DELETE', 'ANY'];\n\n    protected array $allowedContentType = ['html' => 'text/html', 'json' => 'application/json', 'jsonp' => 'application/javascript'];\n\n    protected Route $matched;\n\n    private Request $request;\n\n    private Response $response;\n\n    private RouteHandlerInterface $handler;\n\n    private RouteParserInterface $parser;\n\n    private array $middleware_list = [];\n\n    private array $globalMiddleware = [];\n\n    private array $routes = [];\n\n    private string $currentGroupPrefix = '';\n\n    private array $currentGroupOptions = [];\n\n    private array $nextGroupOptions = [];\n\n    private array $groupOptions = [];\n\n    private ?string $localeRedirectHandle = null;\n\n    private array $auth_list = [];\n\n    private ?string $currentAuthId = null;\n\n    private \\Closure $notfound;\n\n    public function __construct(Request $request, Response $response)\n    {\n        $this->request = $request;\n        $this->response = $response;\n        $this->parser = new RouteParser();\n        $this->handler = new RouteHandler();\n\n        $this->notfound = function () {\n            pre('Page is not found', '404 Not Found', 6);\n        };\n        container()->set(Request::class, $this->request);\n        container()->set(Response::class, $this->response);\n    }\n\n    public function __call($method, $args)\n    {\n        return call_user_func_array([$this, 'add'], array_merge([$method], $args));\n    }\n\n    public function setHandler(RouteHandlerInterface $handler)\n    {\n        $this->handler = $handler;\n    }\n\n    public function setParser(RouteParserInterface $parser)\n    {\n        $this->parser = $parser;\n    }\n\n    public function setNotfound($handler)\n    {\n        $this->notfound = $handler;\n    }\n\n    public function run(): Response\n    {\n        $this->match($this->request->method(), $this->request->uri());\n        return $this->response;\n    }\n\n    public function add($method, $uri, $handler, array $options = []): Route\n    {\n        $uri = new Uri($this->currentGroupPrefix . $uri);\n        $chunk = $uri->getChunk();\n\n        if (! isset($this->routes[$chunk])) {\n            $this->routes[$chunk] = [];\n        }\n\n        if (! in_array($method = strtoupper($method), $this->allowedMethods)) {\n            throw new \\LogicException(\"[{$method}] Method is not allowed\");\n        }\n        if($method == 'ANY'){\n            $method = '';\n        }\n        $opt = [];\n        if ($this->currentAuthId) {\n            $opt['auth'] = $this->currentAuthId;\n        }\n        if ($options) {\n            $opt = array_merge($opt, $options);\n        }\n        if ($this->currentGroupOptions) {\n            $opt['group'] = $this->currentGroupOptions;\n        }\n\n        $this->routes[$chunk][] = $route = new Route($method, $uri, $handler, $opt);\n\n        return $route;\n    }\n\n    public function locale(array $locales, callable $callback): void\n    {\n        if (session_status() == PHP_SESSION_NONE) {\n            @session_start();\n        }\n        if (! isset($_SESSION['locale'])) {\n            $_SESSION['locale'] = $locales[0];\n        }\n        $this->group('/{@locale}?:(' . implode('|', $locales) . ')', $callback);\n    }\n\n    public function auth(AuthInterface $auth, callable $callback)\n    {\n        $id = uniqid((string) rand());\n        $this->auth_list[$id] = $auth;\n        $this->currentAuthId = $id;\n        $callback($this);\n        $this->currentAuthId = '';\n    }\n\n    public function group($prefix, callable $callback): void\n    {\n        $previousGroupOptions = $this->currentGroupOptions;\n        if ($this->nextGroupOptions) {\n            $id = uniqid((string) rand());\n            $this->groupOptions[$id] = $this->nextGroupOptions;\n            $this->currentGroupOptions[] = $id;\n            $this->nextGroupOptions = [];\n        }\n\n        $previousGroupPrefix = $this->currentGroupPrefix;\n        $this->currentGroupPrefix = $previousGroupPrefix . $prefix;\n        $callback($this);\n        $this->currentGroupPrefix = $previousGroupPrefix;\n        $this->currentGroupOptions = $previousGroupOptions;\n    }\n\n    public function middleware(...$middleware): Router\n    {\n        $id = uniqid((string) rand());\n        $this->middleware_list[$id] = $middleware;\n        if (! isset($this->nextGroupOptions['middleware'])) {\n            $this->nextGroupOptions['middleware'] = [];\n        }\n        $this->nextGroupOptions['middleware'][] = $id;\n        return $this;\n    }\n\n    public function use(...$middleware): Router\n    {\n        $this->globalMiddleware = array_merge($this->globalMiddleware, $middleware);\n        return $this;\n    }\n\n    public function addPlaceholders(array $patterns): void\n    {\n        foreach ($patterns as $name => $pattern) {\n            Regex::set($name, $pattern);\n        }\n    }\n\n    public function match(string $method, string $uri): bool\n    {\n        $uri = new Uri($uri);\n        $uri->rtrim('/');\n        $chunk = $uri->getChunk();\n        $matched = null;\n\n        if (isset($this->routes[$chunk])) {\n            $matched = $this->find($method, $uri, $this->routes[$chunk]);\n        }\n        if (! $matched && isset($this->routes['/*'])) {\n            $matched = $this->find($method, $uri, $this->routes['/*']);\n        }\n\n        if ($matched) {\n            $this->handleOptions($matched);\n\n            if ($this->response->isEnded()) {\n                return false;\n            }\n\n            $args = $matched->getArgs();\n            if (count($args)) {\n                container()->importVars($args);\n                $this->request->setArguments($args);\n            }\n\n            $middleware = new Middleware($this->handler);\n            $middleware->add($matched->getMiddleware());\n\n            $output = $middleware->handle(\n                $matched->getHandler($this->handler)\n            );\n\n            $this->response->write($output ?? '');\n            return true;\n        }\n\n        $this->handler->call($this->notfound);\n        return false;\n    }\n\n    public function handleMiddleware(Route $matched)\n    {\n        if ($this->globalMiddleware) {\n            foreach ($this->globalMiddleware as $item) {\n                $matched->middleware($item);\n            }\n        }\n\n        if ($matched->options->has('middleware')) {\n            $middleware = (array) $matched->options->get('middleware');\n            foreach (array_reverse($middleware) as $item) {\n                call_user_func_array([$matched, 'middleware'], $this->middleware_list[$item]);\n            }\n        }\n    }\n\n    public function getMatched()\n    {\n        return $this->matched;\n    }\n\n    public function export(): array\n    {\n        return $this->routes;\n    }\n\n    private function handleOptions(Route $matched)\n    {\n        if ($matched->options->has('auth')) {\n            $this->handleAuth($matched->options->get('auth'));\n        }\n\n        if ($matched->options->has('group')) {\n            $this->handleGroupOptions($matched->options);\n        }\n\n        $this->handleMiddleware($matched);\n\n        if ($matched->options->has('namespace')) {\n            $matched->addNamespaceToHandler();\n        }\n        //Handle Json\n        if ($matched->options->has('content_type') && isset($this->allowedContentType[$matched->options->get('content_type')])) {\n            $this->response->headers->set('Content-Type', $this->allowedContentType[$matched->options->get('content_type')]);\n        }\n    }\n\n    private function handleAuth(string $id)\n    {\n        if (! isset($this->auth_list[$id])) {\n            throw new \\LogicException('Auth is not Registered');\n        }\n        $auth = new Auth($this->auth_list[$id]);\n        $this->request->setUser($auth->user());\n    }\n\n    private function handleGroupOptions(ArrayPrototype $options)\n    {\n        $final = [];\n        foreach ($options->get('group') as $id) {\n            foreach ($this->groupOptions[$id] as $key => $value) {\n                if ($key == 'namespace') {\n                    $final['namespace'] = (isset($final['namespace']) ? $final['namespace'] . '\\\\' : '') . $value;\n                    continue;\n                }\n                if ($key == 'middleware') {\n                    $final['middleware'] = array_merge($final['middleware'] ?? [], $value);\n                    continue;\n                }\n                $final[$key] = $value;\n            }\n        }\n        $options->remove('group');\n        $options->add($final);\n    }\n\n    private function find(string $requestMethod, Uri $requestUri, $routes): Route\n    {\n        $result_args = [];\n        $matched = false;\n        foreach ($routes as $route) {\n            $uri = $route->getUri();\n            $method = $route->getMethod();\n            $uri->rtrim('/');\n            if ($method && $method !== $requestMethod) {\n                continue;\n            }\n\n            if ($uri->isStatic() && $uri->eq((string) $requestUri)) {\n                $matched = true;\n            } else {\n                $pattern = $this->parser->parse((string) $uri);\n                if (preg_match('~^' . $pattern['result'] . '$~i', (string) $requestUri, $args)) {\n                    array_shift($args);\n                    if ($args) {\n                        $args = array_map(function ($s) {\n                            return ltrim($s, '/');\n                        }, $args);\n                    }\n                    $result_args = $this->bindArgs($pattern['parameters'], $args);\n                    $matched = true;\n                }\n            }\n\n            if ($matched) {\n                $route->setArgs($result_args);\n                $this->matched = $route;\n                return $route;\n            }\n        }\n        return new Route('', new Uri('/404'), $this->notfound);\n    }\n\n    private function bindArgs(array $pram, array $args): array\n    {\n        $newArgs = [];\n        if (count($pram) == count($args)) {\n            $pram = array_map(function ($s) {\n                return ltrim($s, '@');\n            }, $pram);\n            $newArgs = array_combine($pram, $args);\n        } else {\n            foreach ($pram as $p) {\n                $value = array_shift($args);\n                if ($p == '@locale') {\n                    $p = 'locale';\n                    if (! $value) {\n                        $this->localeRedirect();\n                        break;\n                    }\n                }\n                $newArgs[$p] = $value;\n            }\n        }\n        if (isset($newArgs['locale'])) {\n            $_SESSION['locale'] = $newArgs['locale'];\n        }\n        return $newArgs;\n    }\n\n    private function localeRedirect()\n    {\n        $value = $_SESSION['locale'];\n        $this->response->redirectTo('/' . $value . $this->request->uri());\n        if ($this->localeRedirectHandle && is_callable($this->localeRedirectHandle)) {\n            $redirect = call_user_func_array($this->localeRedirectHandle, [$this->response->getRedirect(), $this->request->uri()]);\n            if ($redirect) {\n                $this->response->redirectTo('/' . $value . $this->request->uri());\n            }\n        }\n        $this->response->end();\n    }\n}\n"
  },
  {
    "path": "src/Support/ArrayTrait.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Support;\n\n/**\n * ArrTrait.\n *\n * @since       1.0.0\n */\ntrait ArrayTrait\n{\n    /**\n     * Get value from nested array.\n     *\n     * @param string $k\n     * @param string $default\n     *\n     * @return mixed\n     */\n    public static function get(array $arr, $k, $default = null)\n    {\n        if (isset($arr[$k])) {\n            return $arr[$k];\n        }\n\n        $nested = explode('.', $k);\n        foreach ($nested as $part) {\n            if (isset($arr[$part])) {\n                $arr = $arr[$part];\n                continue;\n            }\n            $arr = $default;\n            break;\n        }\n        return $arr;\n    }\n\n    /**\n     * set value to nested array.\n     *\n     * @param string $k\n     * @param mixed $v\n     *\n     * @return array\n     */\n    public static function set(array $arr, $k, $v)\n    {\n        $nested = ! is_array($k) ? explode('.', $k) : $k;\n        $count = count($nested);\n        if ($count == 1) {\n            return $arr[$k] = $v;\n        }\n        if ($count > 1) {\n            $prev = '';\n            $loop = 1;\n            $unshift = $nested;\n\n            foreach ($nested as $part) {\n                if (isset($arr[$part]) && $count > $loop) {\n                    $prev = $part;\n                    array_shift($unshift);\n                    ++$loop;\n                    continue;\n                }\n                if ($loop > 1 && $loop < $count) {\n                    if (! isset($arr[$prev][$part])) {\n                        $arr[$prev][$part] = [];\n                    }\n\n                    $arr[$prev] = static::set($arr[$prev], $unshift, $v);\n                    ++$loop;\n                    break;\n                }\n                if ($loop >= 1 && $loop == $count) {\n                    if (! is_array($arr[$prev])) {\n                        $arr[$prev] = [];\n                    }\n\n                    if ($part == '') {\n                        $arr[$prev][] = $v;\n                    } else {\n                        $arr[$prev][$part] = $v;\n                    }\n                } else {\n                    $arr[$part] = [];\n                    $prev = $part;\n                    array_shift($unshift);\n                    ++$loop;\n                }\n            }\n        }\n        return $arr;\n    }\n\n    /**\n     * Get value if key exists or default value.\n     *\n     * @param string $k\n     * @param string $default\n     *\n     * @return mixed\n     */\n    public static function value(array $arr, $k, $default = null)\n    {\n        return isset($arr[$k]) ? $arr[$k] : $default;\n    }\n\n    /**\n     * Get value from string json.\n     *\n     * @param string $jsonStr\n     * @param string $k\n     * @param string $default\n     *\n     * @return mixed\n     */\n    public static function json($jsonStr, $k = null, $default = null)\n    {\n        $json = json_decode($jsonStr, true);\n        if ($k && $json) {\n            return self::get($json, $k, $default);\n        }\n        return $json;\n    }\n\n//    public static function replace_values($arr, $from = null, $to = \"\")\n//    {\n//        $results = [];\n//        foreach ($arr as $k => $v) {\n//            $toArr = (array) $v;\n//            if(count($toArr) > 1 || is_array($v)){\n//                $results[$k] = nullToString($toArr);\n//            } else {\n//                $results[$k] = $v;\n//                if ($v == $from) {\n//                    $results[$k] = $to;\n//                }\n//            }\n//        }\n//        return $results;\n//    }\n}\n"
  },
  {
    "path": "src/Support/Regex.php",
    "content": "<?php\n\ndeclare(strict_types=1);\n/**\n * This file is part of Just.\n *\n * @license  https://github.com/just-framework/php/blob/master/LICENSE MIT License\n * @link     https://justframework.com/php/\n * @author   Mahmoud Elnezamy <mahmoud@nezamy.com>\n * @package  Just\n */\nnamespace Just\\Support;\n\n/**\n * Class Regex.\n * @method static string get(string $name)\n * @method static boolean has(string $name)\n * @method static void set(string $name, string $pattern)\n * @method static void update(string $name, string $pattern)\n * @method static array list()\n */\nclass Regex\n{\n    private static ?Regex $instance = null;\n\n    private array $patterns = [\n        '*' => '(.*)',\n        '?' => '([^\\/]+)',\n        'int' => '([0-9]+)',\n        'multiInt' => '([0-9,]+)',\n        'title' => '([a-z_-]+)',\n        'key' => '([a-z0-9_]+)',\n        'multiKey' => '([a-z0-9_,]+)',\n        'isoCode2' => '([a-z]{2})',\n        'isoCode3' => '([a-z]{3})',\n    ];\n\n    public function __call(string $name, array $arguments)\n    {\n        return call_user_func_array([$this, '_' . $name], $arguments);\n    }\n\n    public static function __callStatic(string $name, array $arguments)\n    {\n        return call_user_func_array([self::instance(), '_' . $name], $arguments);\n    }\n\n    public static function instance(): self\n    {\n        if (static::$instance === null) {\n            static::$instance = new static();\n        }\n        return static::$instance;\n    }\n\n    public function _set(string $name, string $pattern): void\n    {\n        if ($this->has($name)) {\n            throw new \\LogicException(\"{$name} already registered in route patterns\");\n        }\n        $this->patterns[$name] = $pattern;\n    }\n\n    public function _get(string $name): string\n    {\n        return $this->patterns[$name];\n    }\n\n    public function _has(string $name): bool\n    {\n        return array_key_exists($name, $this->patterns);\n    }\n\n    public function _update(string $name, string $pattern): void\n    {\n        $this->patterns[$name] = $pattern;\n    }\n\n    public function _list(): array\n    {\n        return $this->patterns;\n    }\n}\n"
  },
  {
    "path": "src/functions.php",
    "content": "<?php declare(strict_types=1);\n! defined('DS') && define('DS', DIRECTORY_SEPARATOR);\n\n//=============[ pre() is print array ]==============\nfunction pre($arr, $title = null, $theme = 1)\n{\n    ob_start();\n    switch ($theme) {\n        case 2: $color = '#e4e7e7'; $background = '#2295bc'; break;\n        case 3: $color = '#064439'; $background = '#51bba8'; break;\n        case 4: $color = '#efc75e'; $background = '#324d5b'; break;\n        case 5: $color = '#000000'; $background = '#b1eea1'; break;\n        case 6: $color = '#fff'; $background = '#e2574c'; break;\n        default:    $color = '#2295bc'; $background = '#e4e7e7';\n    }\n    if ($title) { ?>\n        <div style=\"border:1px solid rgba(0,0,0,0.1);border-bottom:0;color:#2e3436;position: relative;padding: 6px 40px;font-weight:500;font-family: Monaco,Consolas, 'Lucida Console',monospace;letter-spacing:1px;font-size:14px;display: inline-block;width: auto;left: 40px;bottom: -30px;\"><?php echo $title; ?></div>\n    <?php } ?>\n    <pre style=\"direction: ltr;background:<?php echo $background; ?>;color:<?php echo $color; ?>;\n        width: calc(100% - 122px);margin: 30px auto;overflow:auto;\n        font-family: Monaco,Consolas, 'Lucida Console',monospace;font-size: 14px;padding: 20px;border: 1px solid rgba(0,0,0,0.1)\"><?php print_r($arr); ?></pre>\n    <?php\n    response()->write(ob_get_clean());\n}\n\nfunction dpre()\n{\n    call_user_func_array('pre', func_get_args());\n    response()->end(response()->body());\n}\n\nfunction container()\n{\n    return \\Just\\DI\\Container::instance();\n}\n\nfunction response(): Just\\Http\\Response\n{\n    return container()->get(\\Just\\Http\\Response::class);\n}\n\nfunction request(): Just\\Http\\Request\n{\n    return container()->get(\\Just\\Http\\Request::class);\n}\n\nfunction auth(): Just\\Http\\Auth\n{\n    return container()->get(\\Just\\Http\\Auth::class);\n}\n"
  },
  {
    "path": "tests/AuthTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Just\\Test;\n\nuse Just\\DI\\Container;\nuse Just\\Http\\Auth;\nuse Just\\Http\\GlobalRequest;\nuse Just\\Http\\Request;\nuse Just\\Http\\Response;\nuse PHPUnit\\Framework\\TestCase;\n\nclass AuthTest extends TestCase\n{\n    public function testBasicAuth() : void\n    {\n        Container::instance()->set(Request::class, new GlobalRequest);\n        Container::instance()->set(Response::class, new Response);\n        $credentials = ['users'=> ['Mahmoud'=> '123258']];\n        $auth = new Auth(new Auth\\Basic($credentials));\n        \n        $this->assertTrue($auth->validate(['username'=> 'Mahmoud', 'password' => '123258']));\n        // $auth->\n        $this->assertTrue(response()->headers->has('WWW-Authenticate'));\n    }\n    /**\n     * Undocumented function\n     *\n     * @return void\n     */\n    public function testDigest(): void\n    {\n        Container::instance()->set(Request::class, new GlobalRequest);\n        Container::instance()->set(Response::class, new Response);\n        new Auth(new Auth\\Digest(['users'=> ['Mahmoud'=> '123258']]));\n        $this->assertTrue(response()->headers->has('WWW-Authenticate'));\n    }\n}\n"
  },
  {
    "path": "tests/DummyRequest.php",
    "content": "<?php declare(strict_types=1);\n\nuse Just\\Http\\GlobalRequest;\n\nclass DummyRequest extends GlobalRequest\n{\n    protected $_uri;\n    protected $_method;\n    public function setUri(string $uri)\n    {\n        $this->_uri = $uri;\n    }\n    public function setMethod(string $method)\n    {\n        $this->_method = $method;\n    }\n}\n"
  },
  {
    "path": "tests/RouteParserTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Just\\Test;\n\nuse Just\\Routing\\RouteParser;\nuse PHPUnit\\Framework\\TestCase;\n\nclass RouteParserTest extends TestCase\n{\n    public function testParameters() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/{username}/post/{id}');\n        $this->assertSame([\n            'parameters' => [\n                'username', 'id'\n            ], 'patterns' => [\n                '/([^\\/]+)',\n                '/([^\\/]+)'\n            ], 'result' => '/([^\\/]+)/post/([^\\/]+)'\n        ], $results);\n    }\n\n    public function testParametersWithRegex() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/{username}:([0-9a-z_.-]+)/post/{id}:([0-9]+)');\n        $this->assertSame([\n            'parameters' => [\n                'username', 'id'\n            ], 'patterns' => [\n                '/([0-9a-z_.-]+)',\n                '/([0-9]+)'\n            ], 'result' => '/([0-9a-z_.-]+)/post/([0-9]+)'\n        ], $results);\n    }\n\n    public function testParametersWithPlaceholder() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/{username}:title/post/{id}:int');\n        $this->assertSame([\n            'parameters' => [\n                'username', 'id'\n            ], 'patterns' => [\n                '/([a-z_-]+)',\n                '/([0-9]+)'\n            ], 'result' => '/([a-z_-]+)/post/([0-9]+)'\n        ], $results);\n    }\n\n    public function testParametersWithPlaceholderAll() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/{all}:*');\n        $this->assertSame([\n            'parameters' => [\n                'all'\n            ], 'patterns' => [\n                '/(.*)',\n            ], 'result' => '/(.*)'\n        ], $results);\n    }\n\n    public function testParametersHasSamePlaceholderName() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/post/{title}');\n        $this->assertSame([\n            'parameters' => [\n                'title'\n            ], 'patterns' => [\n                '/([a-z_-]+)',\n            ], 'result' => '/post/([a-z_-]+)'\n        ], $results);\n    }\n\n    public function testOptionalParametersWithPlaceholder() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/{username}:title/posts/{id}?:int');\n        $this->assertSame([\n            'parameters' => [\n                'username', 'id'\n            ], 'patterns' => [\n                '/([a-z_-]+)',\n                '(/[0-9]+)?'\n            ], 'result' => '/([a-z_-]+)/posts(/[0-9]+)?'\n        ], $results);\n    }\n\n    public function testLocaleParameter() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/{@locale}:(ar|en)/posts/{id}?:int');\n        $this->assertSame([\n            'parameters' => [\n                '@locale', 'id'\n            ], 'patterns' => [\n                '/(ar|en)',\n                '(/[0-9]+)?'\n            ], 'result' => '/(ar|en)/posts(/[0-9]+)?'\n        ], $results);\n    }\n\n    public function testLocaleOptionalParameter() : void\n    {\n        $parser = new RouteParser();\n        $results = $parser->parse('/{@locale}?:(ar|en)/posts/{id}?:int');\n        $this->assertSame([\n            'parameters' => [\n                '@locale', 'id'\n            ], 'patterns' => [\n                '(/ar|/en)?',\n                '(/[0-9]+)?'\n            ], 'result' => '(/ar|/en)?/posts(/[0-9]+)?'\n        ], $results);\n    }\n}\n"
  },
  {
    "path": "tests/RouteTest.php",
    "content": "<?php\ndeclare(strict_types=1);\n\nnamespace Just\\Test;\n\nuse DummyRequest;\nuse Just\\DataType\\Uri;\nuse Just\\Http\\GlobalRequest;\nuse Just\\Http\\Response;\nuse Just\\Routing\\Route;\nuse Just\\Support\\Regex;\nuse PHPUnit\\Framework\\TestCase;\nuse Just\\Routing\\Router;\nclass controller{\n    public static function method(){\n        pre('Methods', 'Controller');\n    }\n}\n\nclass RouteTest extends TestCase\n{\n    protected function app(): Router{\n        return new Router(new GlobalRequest(), new Response());\n    }\n\n\n    public function testShortcuts() : void\n    {\n        $r = $this->app();\n        $r->add('GET', '/get/users', 'Just\\Test\\controller::method');\n        $r->delete('/delete', 'Just\\Test\\controller::method');\n        $r->get('/get', 'Just\\Test\\controller::method');\n        $r->head('/head', 'Just\\Test\\controller::method');\n        $r->patch('/patch', 'Just\\Test\\controller::method');\n        $r->post('/post', 'Just\\Test\\controller::method');\n        $r->put('/put', 'Just\\Test\\controller::method');\n        $r->options('/options', 'Just\\Test\\controller::method');\n\n        $expected = [\n            '/get' => [\n                new Route('GET', new Uri('/get/users'), 'Just\\Test\\controller::method'),\n                new Route('GET', new Uri('/get'), 'Just\\Test\\controller::method'),\n            ],\n            '/delete' => [\n                new Route('DELETE', new Uri('/delete'), 'Just\\Test\\controller::method')\n            ],\n            '/head' => [\n                new Route('HEAD', new Uri('/head'), 'Just\\Test\\controller::method')\n            ],\n            '/patch' => [\n                new Route('PATCH', new Uri('/patch'), 'Just\\Test\\controller::method')\n            ],\n            '/post' => [\n                new Route('POST', new Uri('/post'), 'Just\\Test\\controller::method')\n            ],\n            '/put' => [\n                new Route('PUT', new Uri('/put'), 'Just\\Test\\controller::method')\n            ],\n            '/options' => [\n                new Route('OPTIONS', new Uri('/options'), 'Just\\Test\\controller::method')\n            ]\n        ];\n        self::assertEquals($expected, $r->export());\n    }\n\n    public function testGroups() : void\n    {\n        $r = $this->app();\n        $r->get('/get', 'Just\\Test\\controller::method');\n        $r->group('/api/v1', function (Router $r) {\n            $r->get('/posts', 'Just\\Test\\controller::method');\n            $r->post('/posts/create', 'Just\\Test\\controller::method');\n\n            $r->group('/nested-group', function (Router $r) {\n                $r->get('/posts', 'Just\\Test\\controller::method');\n                $r->post('/posts/create', 'Just\\Test\\controller::method');\n            });\n        });\n\n        $r->group('/admin', static function (Router $r): void {\n            $r->get('-some-info', 'Just\\Test\\controller::method');\n        });\n        $r->group('/admin-', static function (Router $r): void {\n            $r->get('more-info', 'Just\\Test\\controller::method');\n        });\n\n        $expected = [\n            '/get' => [\n                new Route('GET', new Uri('/get'), 'Just\\Test\\controller::method'),\n            ],\n            '/api' => [\n                new Route('GET', new Uri('/api/v1/posts'), 'Just\\Test\\controller::method'),\n                new Route('POST', new Uri('/api/v1/posts/create'), 'Just\\Test\\controller::method'),\n                new Route('GET', new Uri('/api/v1/nested-group/posts'), 'Just\\Test\\controller::method'),\n                new Route('POST', new Uri('/api/v1/nested-group/posts/create'), 'Just\\Test\\controller::method'),\n            ],\n            '/admin-some-info' => [\n                new Route('GET', new Uri('/admin-some-info'), 'Just\\Test\\controller::method'),\n            ],\n            '/admin-more-info' => [\n                new Route('GET', new Uri('/admin-more-info'), 'Just\\Test\\controller::method'),\n            ]\n        ];\n\n        self::assertEquals($expected, $r->export());\n    }\n\n    public function testRouteDynamic() : void\n    {\n        $r = $this->app();\n        $r->get('/{user}/{id}?', 'Just\\Test\\controller::method');\n        $r->get('/user/{user}/{id}?', 'Just\\Test\\controller::method');\n        self::assertEquals([\n            '/*' => [\n                new Route('GET', new Uri('/{user}/{id}?'), 'Just\\Test\\controller::method')\n            ],\n            '/user' => [\n                new Route('GET', new Uri('/user/{user}/{id}?'), 'Just\\Test\\controller::method')\n            ],\n        ], $r->export());\n        try {\n            $r->match('GET', '/username');\n        } catch (\\LogicException $e) {\n        }\n        $expect = new Route('GET', new Uri('/{user}/{id}?'), 'Just\\Test\\controller::method');\n        $expect->setArgs([\n            'user' => 'username',\n            'id' => null\n        ]);\n        self::assertEquals($expect, $r->getMatched());\n\n        try {\n            $r->match('GET', '/username/10/');\n        } catch (\\LogicException $e) {\n        }\n\n        $expect = new Route('GET', new Uri('/{user}/{id}?'), 'Just\\Test\\controller::method');\n        $expect->setArgs([\n            'user' => 'username',\n            'id' => 10\n        ]);\n        self::assertEquals($expect, $r->getMatched());\n    }\n\n    public function testGroupDynamic() : void\n    {\n        $r = $this->app();\n        $r->group('/{lang}:isoCode2', function (Router $r) {\n            $r->get('/{page}', 'Just\\Test\\controller::method');\n            $r->get('/post/{post}', 'Just\\Test\\controller::method');\n        });\n\n        self::assertEquals([\n            '/*' => [\n                new Route('GET', new Uri('/{lang}:isoCode2/{page}'), 'Just\\Test\\controller::method'),\n                new Route('GET', new Uri('/{lang}:isoCode2/post/{post}'), 'Just\\Test\\controller::method')\n            ]\n        ], $r->export());\n        try {\n            $r->match('GET', '/ar/page');\n        } catch (\\LogicException $e) {\n        }\n\n        $expect = new Route('GET', new Uri('/{lang}:isoCode2/{page}'), 'Just\\Test\\controller::method');\n        $expect->setArgs([\n            'lang' => 'ar',\n            'page' => 'page'\n        ]);\n        self::assertEquals($expect, $r->getMatched());\n    }\n\n    public function testGroupDynamicOptionalParameter() : void\n    {\n        $r = $this->app();\n        $r->group('/{lang}?:isoCode2', function (Router $r) {\n            $r->get('/{page}', 'Just\\Test\\controller::method');\n            $r->get('/post/{post}', 'Just\\Test\\controller::method');\n        });\n\n        self::assertEquals([\n            '/*' => [\n                new Route('GET', new Uri('/{lang}?:isoCode2/{page}'), 'Just\\Test\\controller::method'),\n                new Route('GET', new Uri('/{lang}?:isoCode2/post/{post}'), 'Just\\Test\\controller::method')\n            ]\n        ], $r->export());\n\n        try {\n            $r->match('GET', '/page');\n        } catch (\\LogicException $e) {\n        }\n\n        $expect = new Route('GET', new Uri('/{lang}?:isoCode2/{page}'), 'Just\\Test\\controller::method');\n        $expect->setArgs([\n            'lang' => '',\n            'page' => 'page'\n        ]);\n        self::assertEquals($expect, $r->getMatched());\n\n        try {\n            $r->match('GET', '/ar/page');\n        } catch (\\LogicException $e) {\n        }\n\n        $expect = new Route('GET', new Uri('/{lang}?:isoCode2/{page}'), 'Just\\Test\\controller::method');\n        $expect->setArgs([\n            'lang' => 'ar',\n            'page' => 'page'\n        ]);\n        self::assertEquals($expect, $r->getMatched());\n    }\n\n    public function testAddPlaceholder() : void\n    {\n        $r = $this->app();\n        $r->addPlaceholders([\n           'test' => '(test_[a-z_-]+)'\n        ]);\n        $this->assertSame(Regex::get('test'), '(test_[a-z_-]+)');\n    }\n\n    public function testLocale(): void\n    {\n        $r = $this->app();\n        $r->locale(['ar', 'en'], function (Router $r) {\n            $r->get('/', 'Just\\Test\\controller::method');\n            $r->get('/page', 'Just\\Test\\controller::method');\n            $r->get('/page/{page}', 'Just\\Test\\controller::method');\n        });\n        self::assertEquals([\n            '/*' => [\n                new Route('GET', new Uri('/{@locale}?:(ar|en)/'), 'Just\\Test\\controller::method'),\n                new Route('GET', new Uri('/{@locale}?:(ar|en)/page'), 'Just\\Test\\controller::method'),\n                new Route('GET', new Uri('/{@locale}?:(ar|en)/page/{page}'), 'Just\\Test\\controller::method'),\n            ]\n        ], $r->export());\n        try {\n            $r->match('GET', '/en/page');\n        } catch (\\LogicException $e) {\n        }\n        $expect = new Route('GET', new Uri('/{@locale}?:(ar|en)/page'), 'Just\\Test\\controller::method');\n        $expect->setArgs([\n            'locale' => 'en',\n        ]);\n        self::assertEquals($expect, $r->getMatched());\n    }\n\n    public function testLocaleRedirect(): void\n    {\n        $req = new DummyRequest();\n        $req->setUri('/page');\n        $r = new Router($req, new Response());\n        $r->locale(['ar', 'en'], function (Router $r) {\n            $r->get('/', function () {\n            });\n            $r->get('/page', function () {\n            });\n        });\n\n        $match = $r->run();\n        $this->assertTrue($match->hasRedirect());\n        $this->assertTrue($match->isEnded());\n        $this->assertEquals(['/ar/page', 302], $match->getRedirect());\n    }\n}\n"
  },
  {
    "path": "tests/bootstrap.php",
    "content": "<?php\nerror_reporting(E_ALL);\n\n// ini_set('error_reporting', E_ALL);\n\n$loader = require __DIR__ . '/../vendor/autoload.php';\n$loader = require __DIR__ . '/DummyRequest.php';\n"
  }
]