diff --git a/Auth/Middleware/SessionStart.php b/Auth/Middleware/SessionStart.php index 99e8a9a..3b2a15e 100755 --- a/Auth/Middleware/SessionStart.php +++ b/Auth/Middleware/SessionStart.php @@ -10,10 +10,10 @@ class SessionStart implements MiddlewareInterface { - const NAME = NULL; // Set a custom seesion name - const TIME = 360; - const SSL = true; - const SAMESITE = true; + public const NAME = null; // Set a custom seesion name + public const TIME = 360; + public const SSL = true; + public const SAMESITE = true; private $container; diff --git a/Cli/Connectors/AbstractCli.php b/Cli/Connectors/AbstractCli.php new file mode 100755 index 0000000..887b126 --- /dev/null +++ b/Cli/Connectors/AbstractCli.php @@ -0,0 +1,176 @@ +args = $request->getCliArgs(); + $this->prompt = new Prompt(); + $this->command = new Command(); + $this->protocol = static::PROMPT; + $this->provider = $provider; + } + + /** + * Extend the prompt + * @param string $promptName + * @param array $arr + * @return void + */ + protected function addPrompt(string $promptName, array $arr): void + { + $this->protocol[$promptName] = $arr; + } + + /** + * Check if prompt exists + * @param string $promptName + * @return bool + */ + protected function hasPrompt(string $promptName): bool + { + return isset($this->protocol[$promptName]); + } + + /** + * Filter out cli command for hidden and passed argv + * @param array $arr + * @return array + */ + protected function filterSetArgs(array $arr): array + { + $new = []; + foreach ($arr as $key => $val) { + if(!isset($this->args[$key])) { + $type = ($val['type'] ?? ""); + if($type === "hidden") { + $this->input[$key] = ($val['default'] ?? ""); + } else { + $new[$key] = $val; + } + } else { + $this->input[$key] = $this->args[$key]; + } + } + return $new; + } + + /** + * Get filtered values + * @param array $arr + * @return array + */ + protected function getFilterVal(array $arr): array + { + return array_merge($arr, $this->input); + } + + /** + * Extract help text with doc block comment + * @param object|string $className + * @return Comment + * @throws ReflectionException + */ + protected function getCommentBlock(object|string $className): Comment + { + $block = new Comment($className); + $this->command->title($block->getClass(true)); + $methods = $block->getAllMethods(); + foreach ($block->getAllMethods() as $method) { + $comment = $block->getDocComment($method); + if(isset($comment['description'])) { + $class = strtolower($block->getClass(true)); + if(($comment['methodExtends'][0] ?? "") === "prompt") { + foreach($this->protocol as $meth => $_arr) { + if(!in_array($meth, $methods)) { + $this->generateHelpText($comment, $class, $meth); + } + } + + } else { + $this->generateHelpText($comment, $class, $method); + } + } + + } + return $block; + } + + /** + * Will help generate help texts in PHP Maple Cli + * Example: Extending Prompt Array items + * --help: + * description: Add parameter types usage example to prompt help text + * default: Add Default value to parameter usage example to prompt help text + * help: Force remove parameter type from usage example in prompt help text + * @param array $comment + * @param string $class + * @param string $method + * @return void + */ + private function generateHelpText(array $comment, string $class, string $method) + { + $classA = "$class:$method"; + $length = strlen($classA); + $padA = str_pad("", 40 - $length, " ", STR_PAD_RIGHT); + $padB = str_pad("", 40, " ", STR_PAD_RIGHT); + + if(isset($this->protocol[$method])) { + $valid = reset($this->protocol[$method]); + //isValidPrompt + if(isset($valid['type']) && isset($valid['message'])) { + + $this->command->statusMsg($classA, false); + $this->command->message("{$padA}{$comment['description']}"); + + $fill = "{$padB} "; + $this->command->title("{$fill}Arguments"); + foreach ($this->protocol[$method] as $arg => $data) { + if((bool)($data['help'] ?? true) !== false) { + $message = (!empty($data['description'])) ? $data['description'] : ($data['message'] ?? ""); + $addEg = (!empty($data['default'])) ? " (example: {$data['default']})" : ""; + $this->command->approve("$fill--$arg: ", false); + $this->command->message($message . $addEg); + } + } + $this->command->message("\n"); + } + } + } + + private function isValidPrompt() + { + } + + /** + * Access help (or with added flag --help) + * @return void + * @throws ReflectionException + */ + public function help(): void + { + $this->command->title("\nMaplePHP CLI Commands"); + $this->command->message("Below are all the available inputs for the MaplePHP CLI commands.\n"); + $block = $this->getCommentBlock($this); + $class = strtolower($block->getClass(true)); + $this->command->title("\nExample usage: ", false); + $this->command->message("php cli $class:[action] --arg=1 --arg=2"); + } + +} diff --git a/Cli/Connectors/Config.php b/Cli/Connectors/Config.php index 34c95f7..f39d127 100755 --- a/Cli/Connectors/Config.php +++ b/Cli/Connectors/Config.php @@ -29,7 +29,7 @@ public function __construct(ContainerInterface $container, RequestInterface $req public function install() { - + $type = ($this->args['type'] ?? null); $envConfig = $this->cli->getConfig(); $allowedConfigs = array_keys($envConfig); diff --git a/Cli/Connectors/Database.php b/Cli/Connectors/Database.php index e3ebb23..f5d5ced 100755 --- a/Cli/Connectors/Database.php +++ b/Cli/Connectors/Database.php @@ -55,7 +55,7 @@ function (StreamInterface $_stream) use ($delete) { public function insertUser() { - $orgs = $this->user->getAllOrgs("org_id,org_name", function($row) { + $orgs = $this->user->getAllOrgs("org_id,org_name", function ($row) { return [$row->org_id => $row->org_name]; }); diff --git a/Cli/Connectors/Image.php b/Cli/Connectors/Image.php index c1dca36..8d1632c 100755 --- a/Cli/Connectors/Image.php +++ b/Cli/Connectors/Image.php @@ -4,6 +4,7 @@ * This is NOT COMPLETED! * THERE is MUCH to more to be done! */ + namespace MaplePHP\Foundation\Cli\Connectors; use MaplePHP\Foundation\Cli\Connectors\CliInterface; @@ -58,16 +59,22 @@ public function resize() $this->img = new Resize($original); $valid = new Inp($hex); if($valid->equal("auto") || $valid->hex()) { - if($hex === "auto") $hex = $this->img->getAverageColour(); - if($opacity > 1) $opacity = 0; - if($opacity > 0) $hex = $this->img->getLuminance($hex, $opacity); + if($hex === "auto") { + $hex = $this->img->getAverageColour(); + } + if($opacity > 1) { + $opacity = 0; + } + if($opacity > 0) { + $hex = $this->img->getLuminance($hex, $opacity); + } $hex = str_replace("&", "#", $hex); if($this->img->isMime("image/png")) { $this->img->setBackgroundColor($hex, $opacity); } } - $set = array(); + $set = []; $path = "{$savePath}{$basename}-0"; $this->resizeImg($type, $path, 2600, 2600); $this->img->execute(); @@ -82,7 +89,7 @@ public function resize() $this->resizeImg($type, $path, $width, $heigth); $this->img->convertTo("webp"); $this->img->execute(); - $set[$width+$heigth] = $this->img->getTruePath(); + $set[$width + $heigth] = $this->img->getTruePath(); } $this->cli->write("Image has been resized and optimized."); @@ -120,16 +127,18 @@ protected function resizeImg(string $type, string $file, int $width, int $height switch($type) { case "resize": $this->img->resize($width, $height); - break; + break; case "crop": $this->img->crop($width, $height); - break; + break; case "trim": $this->img->trim(); $this->img->resize($width, $height); - break; + break; + } + if(!$this->img->isMime("image/gif")) { + $this->img->convertTo("jpg"); } - if(!$this->img->isMime("image/gif")) $this->img->convertTo("jpg"); //$fallbackImg = $this->img->getTrueBasename(); } diff --git a/Cli/Connectors/Install.php b/Cli/Connectors/Install.php new file mode 100755 index 0000000..a7210cd --- /dev/null +++ b/Cli/Connectors/Install.php @@ -0,0 +1,291 @@ + [ + "name" => [ + "type" => "text", + "message" => "App name", + "default" => "My app", + "validate" => [ + "length" => [1,60] + ], + "error" => "Required", + "description" => "Set your app name", + ], + "lang" => [ + "type" => "text", + "message" => "Language", + "default" => "en", + "validate" => [ + "length" => [2,2] + ], + "error" => "Required and must be 2 characters", + "description" => "Set your default language", + ], + "charset" => [ + "type" => "hidden", + "message" => "Set your charset", + "default" => "UTF-8", + "validate" => [ + "length" => [1,60] + ], + "error" => "Required" + ], + "debug_display" => [ + "type" => "hidden", + "message" => "Debug mode", + "default" => "1", + "validate" => [ + "length" => [1,1], + "int" => [] + ], + "error" => "Required", + "description" => "Set debug mode", + ], + "debug_log" => [ + "type" => "hidden", + "message" => "Debug mode", + "default" => "0", + "validate" => [ + "length" => [1,1], + "int" => [] + ], + "error" => "Required", + "description" => "Set debug mode", + ], + "database_type" => [ + "type" => "select", + "message" => "Select Your Preferred Database?", + "items" => [ + false => "None", + "mysql" => "MySQL", + "mariadb" => "MariaDB", + "postgresql" => "PostgreSQL", + "sqlite" => "SQLite (Migration not supported)", + ] + ], + "database" => [ + "type" => "continue", + ], + "tailwind" => [ + "type" => "toggle", + "message" => "Do you want to install Tailwind?" + ], + "transpiler" => [ + "type" => "select", + "message" => "Do you want to install a Transpiler?", + "items" => [ + false => "None", + "esbuild" => "Esbuild", + "vite" => "Vite" + ] + ] + ] + ]; + + public function __construct(RequestInterface $request, Provider $provider) + { + parent::__construct($request, $provider); + foreach($_ENV['config'] as $key => $value) { + if(is_array($value)) { + $arr = explode("|", $key); + $arr = array_filter($arr); + foreach($arr as $newKey) { + $this->addPrompt($newKey, $value); + } + } + } + } + + /** + * Install the MaplePHP framework + * @param Dir $dir + * @return void + * @throws PromptException + */ + public function install(Dir $dir): void + { + $promptData = $this->filterSetArgs(static::PROMPT[__FUNCTION__]); + + $promptData['database'] = [ + "type" => "continue", + 'items' => function ($prevVal) { + if($prevVal === "mysql") { + return [ + "host" => [ + "type" => "text", + "message" => "Database host", + "default" => "localhost", + "validate" => [ + "length" => [1,120], + "domain" => [] + ], + "error" => "Not a valid database host", + "description" => "Set database host name", + ], + "name" => [ + "type" => "text", + "message" => "Database name", + "validate" => [ + "length" => [1,100], + ], + "error" => "Required", + "description" => "Set database name", + ], + "username" => [ + "type" => "text", + "message" => "Database username", + "validate" => [ + "length" => [0,100], + ], + "error" => "Max length 100 characters", + "description" => "Set database username", + ], + "password" => [ + "type" => "text", + "message" => "Database password", + "validate" => [ + "length" => [0,100], + ], + "error" => "Max length 100 characters", + "description" => "Set database password", + ], + "port" => [ + "type" => "text", + "default" => "3306", + "message" => "Database port", + "validate" => [ + "length" => [0,5], + "max" => [65535] + ], + "error" => "Not a valid database port", + "description" => "Set database port number", + ], + "prefix" => [ + "type" => "text", + "message" => "Database table prefix", + "validate" => function ($val) { + if(strlen($val) > 0) { + return !(((int)preg_match("/^[a-zA-Z_]+$/", $val) > 0) && str_ends_with($val, "_")); + } + return false; + }, + "error" => "No special characters allowed and has to end with a underscore (\"_\") ", + "description" => "Set database table prefix" + ], + "charset" => [ + "type" => "text", + "message" => "Database charset", + "default" => "UTF-8", + "validate" => [ + "length" => [0,100], + ], + "error" => "Max length 100 characters", + "description" => "Set database charset", + ], + ]; + } + return false; + } + ]; + + $file = $dir->getDir() . ".env"; + $this->prompt->setTitle("Installing the framework"); + $this->prompt->set($promptData); + + $prompt = $this->getFilterVal($this->prompt->prompt()); + + $this->updateEnvFile("app", $file, $prompt); + $this->command->approve("The framework has been successfully installed."); + } + + /** + * Install to the framework + * @methodExtends prompt + * @param Dir $dir + * @param UrlInterface $url + * @return void + * @throws Exception + */ + public function config(Dir $dir, UrlInterface $url): void + { + $action = $url->select("action")->get(); + if(empty($action) || $action === "install") { + $this->install($dir); + + } else { + + if($this->hasPrompt($action)) { + $promptData = $this->protocol[$action]; + $promptData['confirm'] = [ + 'type' => 'confirm', + 'message' => 'Are you sure you want to proceed?' + ]; + $promptData = $this->filterSetArgs($promptData); + $this->prompt->setTitle("Installing $action"); + $this->prompt->set($promptData); + try { + $prompt = $this->prompt->prompt(); + if($prompt) { + unset($prompt['confirm']); + $file = $dir->getDir() . ".env"; + $this->updateEnvFile($action, $file, $prompt); + $this->command->approve("The package has been successfully installed."); + } + + } catch (PromptException $e) { + $this->command->error("The package \"$action\" is not configured correctly!"); + } catch (Exception $e) { + throw $e; + } + + } else { + $this->command->error("The package \"$action\" do not exists!"); + } + } + } + + /** + * Will update the EnvFile (Will be moved to service file) + * @param string $prefix + * @param string $file + * @param array $prompt + * @return void + */ + private function updateEnvFile(string $prefix, string $file, array $prompt): void + { + $env = new Env($file); + foreach ($prompt as $key => $value) { + $nKey = strtoupper("{$prefix}_$key"); + if(is_array($value)) { + foreach($value as $keyB => $valueB) { + $nKey = strtoupper("{$key}_$keyB"); + $env->set($nKey, $valueB); + } + } else { + $env->set($nKey, $value); + } + } + $envStream = new Stream(Stream::TEMP); + $envStream->write($env->generateOutput()); + $upload = new UploadedFile($envStream); + $upload->moveTo($file); + } +} diff --git a/Cli/Connectors/Make.php b/Cli/Connectors/Make.php new file mode 100755 index 0000000..047c9ac --- /dev/null +++ b/Cli/Connectors/Make.php @@ -0,0 +1,201 @@ +cliDir = realpath(__DIR__ . "/.."); + $this->buildPrompt($this->cliDir); + } + + /** + * Make file options + * @methodExtends prompt + * @param Dir $dir + * @param UrlInterface $url + * @return void + * @throws PromptException + */ + public function make(Dir $dir, UrlInterface $url): void + { + $action = $url->select("action")->get(); + if($this->hasPrompt($action)) { + + $promptData = $this->protocol[$action]; + $promptData = $this->filterSetArgs($promptData); + $this->prompt->setTitle("Installing $action"); + $this->prompt->set($promptData); + + try { + $prompt = $this->getFilterVal($this->prompt->prompt()); + if($prompt) { + + $addedFiles = []; + $make = $this->items[$action][$prompt['type']]; + $data = $this->prepareMake($prompt['name'], $make); + + foreach($make as $key => $file) { + $overwriteFile = true; + $templateFile = $this->cliDir . "/make/" . $file['file'] . ".php"; + $createFileName = ($data['fileName'][$key] ?? ""); + $path = $this->mkdir($dir, $file['file']); // Utilize the same dir structure + + if(is_file($path . "/" . $createFileName . ".php")) { + $overwriteFile = $this->command->confirm("File exists: " . $path . "/" . $createFileName . ".php\nDo you want to overwrite?"); + } + + if($overwriteFile) { + $stream = new Stream($templateFile); + $makeContent = $stream->getContents(); + $makeContent = str_replace($data['find'], $data['replace'], $makeContent); + + $stream = new Stream(Stream::TEMP); + $stream->write($makeContent); + $upload = new UploadedFile($stream); + $upload->moveTo($path . "/" . $createFileName . ".php"); + $addedFiles[] = $path . "/" . $createFileName . ".php"; + } + } + + $this->command->message(""); + if(count($addedFiles) > 0) { + foreach($addedFiles as $file) { + $this->command->approve("File created:" . $file); + } + } else { + $this->command->message("No files where added"); + } + } + + } catch (PromptException $e) { + $this->command->error("The package \"$action\" is not configured correctly!"); + + } catch (Exception $e) { + throw $e; + } + } + } + + /** + * Will build the make prompt + * @param $cliDir + * @return void + */ + private function buildPrompt($cliDir): void + { + $this->items = $this->makeData($cliDir); + foreach($this->items as $type => $value) { + + $items = []; + foreach ($value as $key => $_prompt) { + $items[$key] = $key; + } + + $this->addPrompt($type, [ + "name" => [ + "type" => "text", + "message" => "Choose a $type name", + "validate" => [ + "length" => [1, 60], + "pregMatch" => ["a-zA-Z_"], + ], + "error" => function ($error) { + if($error === "length") { + return "Required (1-60 characters)"; + } + return "No special characters (\"a-z\", \"A-Z\" and \"_\")"; + }, + ], + "type" => [ + "type" => "select", + "message" => "Choose a $type type", + "items" => $items, + "help" => false + ] + ]); + } + } + + /** + * Get make path and create missing directories + * @param Dir $dir + * @param string $file + * @return string + */ + private function mkdir(Dir $dir, string $file): string + { + $exp = explode("/", $dir->getDir($file)); + array_pop($exp); + $path = implode("/", $exp); + if(!is_dir($path)) { + mkdir($path, 0755, true); + } + return $path; + } + + /** + * Get make data stream + * @param $cliDir + * @return false|mixed + */ + private function makeData($cliDir) + { + try { + $stream = new Stream("$cliDir/make/make.json"); + $result = $stream->getContents(); + return json_decode($result, true); + + + } catch (RuntimeException $e) { + $this->command->error($e->getMessage()); + } + return false; + } + + /** + * This will propagate and prepare some data to be used to make files + * @param string $name + * @param array $make + * @return array + */ + private function prepareMake(string $name, array $make): array + { + // Pre propagate some variables + $fileName = $replace = $find = []; + foreach($make as $key => $file) { + $prefix = ucfirst($name); + $suffix = ucfirst(trim(str_replace("%s", "", $file['name']))); + $fileName[$key] = $prefix.$suffix; + $find[] = "___{$suffix}___"; + $replace[] = $fileName[$key]; + } + + return [ + "find" => $find, + "replace" => $replace, + "fileName" => $fileName + ]; + } +} diff --git a/Cli/Connectors/Migrate-safe.php b/Cli/Connectors/Migrate-safe.php new file mode 100755 index 0000000..b900bfc --- /dev/null +++ b/Cli/Connectors/Migrate-safe.php @@ -0,0 +1,93 @@ +container = $container; + $this->args = $request->getCliArgs(); + $this->cli = $cli; + $this->migrate = $mig; + } + + public function create() + { + if (!$this->migrate->hasMigration()) { + return $this->missingMigrate(); + } + + $this->cli->confirm("Are you sure you want to migrate the {$this->table} table?", function ($stream) { + + try { + $msg = $this->migrate->getBuild()->create(); + $stream->write($this->migrate->getBuild()->getMessage($msg)); + } catch (QueryCreateException $e) { + $this->cli->write($e->getMessage()); + } + }); + + return $this->cli->getResponse(); + } + + public function read() + { + if (!$this->migrate->hasMigration()) { + return $this->missingMigrate(); + } + + try { + $this->cli->write($this->migrate->getBuild()->read()); + } catch (QueryCreateException $e) { + $this->cli->write($e->getMessage()); + } + + + // $this->cli->write($this->migrate->getBuild()->read()); + return $this->cli->getResponse(); + } + + public function drop() + { + if (!$this->migrate->hasMigration()) { + return $this->missingMigrate(); + } + $this->cli->confirm("Are you sure you want to drop the the {$this->table} table?", function ($stream) { + $msg = $this->migrate->getBuild()->drop(); + $stream->write($this->migrate->getBuild()->getMessage($msg)); + }); + return $this->cli->getResponse(); + } + + public function help() + { + $this->cli->write('$ migrate [type] [--values, --values, ...]'); + $this->cli->write('Type: read, create, drop or help'); + $this->cli->write('Values: --table=%s, --help'); + return $this->cli->getResponse(); + } + + public function missingMigrate() + { + $this->cli->write('The migrate "' . $this->migrate->getName() . '" is missing! Read help form more info. ' . + '($ migrate help)'); + return $this->cli->getResponse(); + } +} diff --git a/Cli/Connectors/Migrate.php b/Cli/Connectors/Migrate.php index 825cc62..bffb74f 100755 --- a/Cli/Connectors/Migrate.php +++ b/Cli/Connectors/Migrate.php @@ -2,92 +2,137 @@ namespace MaplePHP\Foundation\Cli\Connectors; -use MaplePHP\Foundation\Cli\Connectors\CliInterface; -use MaplePHP\Http\Interfaces\ResponseInterface; -use MaplePHP\Http\Interfaces\RequestInterface; use MaplePHP\Container\Interfaces\ContainerInterface; -use MaplePHP\Query\Exceptions\QueryCreateException; -use MaplePHP\Foundation\Cli\StandardInput; +use MaplePHP\Foundation\Http\Dir; +use MaplePHP\Foundation\Http\Provider; use MaplePHP\Foundation\Migrate\Migration; +use MaplePHP\Http\Interfaces\RequestInterface; +use MaplePHP\Http\Interfaces\ServerRequestInterface; +use MaplePHP\Prompts\PromptException; +//use MaplePHP\Query\Exceptions\QueryCreateException; +use RecursiveDirectoryIterator; +use RecursiveIteratorIterator; +use Exception; -class Migrate implements CliInterface +class Migrate extends AbstractCli { - protected $container; - protected $args; - protected $table; - protected $cli; - protected $migrate; - protected $mig; - protected $migName; - - public function __construct(ContainerInterface $container, RequestInterface $request, StandardInput $cli, Migration $mig) - { - $this->container = $container; - $this->args = $request->getCliArgs(); - $this->cli = $cli; - $this->migrate = $mig; - } + public const PROMPT = []; + private array $classes; - public function create() + public function __construct(RequestInterface $request, Provider $provider, Dir $dir) { - if (!$this->migrate->hasMigration()) { - return $this->missingMigrate(); - } + parent::__construct($request, $provider); - $this->cli->confirm("Are you sure you want to migrate the {$this->table} table?", function ($stream) { + $classesA = $this->getAllClassesInDir($dir->getPublic(), 'database/migrations'); + // Class B: borde inte migreras den borde flyttas istället. + $classesB = $this->getAllClassesInDir($dir->getPublic(), 'app/Libraries/Foundation/Migrate/Tables', 'MaplePHP\Foundation\Migrate\Tables'); + $this->classes = $classesA + $classesB; - try { - $msg = $this->migrate->getBuild()->create(); - $stream->write($this->migrate->getBuild()->getMessage($msg)); - } catch (QueryCreateException $e) { - $this->cli->write($e->getMessage()); - } - }); - - return $this->cli->getResponse(); + $this->addPrompt('migrate', [ + "migration" => [ + "type" => "select", + "message" => "Choose migration", + "items" => ([0 => 'All'] + $this->classes), + "description" => "Choose a migration class", + ], + "read" => [ + "type" => "hidden", + "message" => "Read sql output without migration", + ], + ]); } - public function read() + /** + * Create migration + * + * @param Migration $mig + * @return void + * @throws PromptException + */ + public function migrate(Migration $mig): void { - if (!$this->migrate->hasMigration()) { - return $this->missingMigrate(); - } + $action = 'migrate'; + if($this->hasPrompt($action)) { + $promptData = $this->filterSetArgs($this->protocol[$action]); + $this->prompt->setTitle("Install the database"); + $this->prompt->set($promptData); - try { - $this->cli->write($this->migrate->getBuild()->read()); - } catch (QueryCreateException $e) { - $this->cli->write($e->getMessage()); - } + try { + $prompt = $this->prompt->prompt(); + if($prompt) { + if($prompt['migration'] === 0) { + foreach($this->classes as $class) { + $this->migrateClass($mig, $class); + } + } else { + $this->migrateClass($mig, $prompt['migration']); + } + } + } catch (PromptException $_e) { + $this->command->error("The package \"$action\" is not configured correctly!"); - // $this->cli->write($this->migrate->getBuild()->read()); - return $this->cli->getResponse(); - } + } catch (Exception $e) { + throw $e; + } - public function drop() - { - if (!$this->migrate->hasMigration()) { - return $this->missingMigrate(); + } else { + $this->command->error("The package \"$action\" do not exists!"); } - $this->cli->confirm("Are you sure you want to drop the the {$this->table} table?", function ($stream) { - $msg = $this->migrate->getBuild()->drop(); - $stream->write($this->migrate->getBuild()->getMessage($msg)); - }); - return $this->cli->getResponse(); } - public function help() + /** + * Might add to service if it gets more complex + * + * @param Migration $mig + * @param string $class + * @return void + * @throws \Exception + */ + private function migrateClass(Migration $mig, string $class): void { - $this->cli->write('$ migrate [type] [--values, --values, ...]'); - $this->cli->write('Type: read, create, drop or help'); - $this->cli->write('Values: --table=%s, --help'); - return $this->cli->getResponse(); + if(class_exists($class)) { + $mig->setMigration($class); + if(isset($this->args['read'])) { + $this->command->message($mig->getBuild()->read()); + } else { + $msg = $mig->getBuild()->create(); + $this->command->message($mig->getBuild()->getMessage($msg, "$class has successfully migrated!")); + } + + } else { + $this->command->error("The migration does not exists!"); + } } - public function missingMigrate() + /** + * Will + * + * @param string $baseDir + * @param string $dir + * @param string|null $namespace + * @return array + */ + private function getAllClassesInDir(string $baseDir, string $dir, ?string $namespace = null): array { - $this->cli->write('The migrate "' . $this->migrate->getName() . '" is missing! Read help form more info. ' . - '($ migrate help)'); - return $this->cli->getResponse(); + $classes = []; + if(is_null($namespace)) { + $namespace = str_replace('/', '\\', $dir); + } + $dir = $baseDir.$dir; + $rii = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir)); + foreach ($rii as $file) { + if (!$file->isDir()) { + $filePath = basename($file->getRealPath()); + $end = explode(".", $filePath); + $end = end($end); + if($end === "php") { + $className = str_replace([DIRECTORY_SEPARATOR, '.php'], ['\\', ''], $filePath); + $class = $namespace . '\\' . $className; + $classes[$class] = $class; + } + } + } + return $classes; } } diff --git a/Cli/Connectors/Package.php b/Cli/Connectors/Package.php index 682b9a6..f6ce44c 100755 --- a/Cli/Connectors/Package.php +++ b/Cli/Connectors/Package.php @@ -14,7 +14,7 @@ /** * Is used to install dkim packages to extend MaplePHP functionallity - * This is just a fun little test that I am playing around with. + * This is just a fun little test that I am playing around with. * @psalm-suppress ForbiddenCode */ class Package implements CliInterface @@ -287,7 +287,7 @@ public function uninstall(): ResponseInterface */ public function delete(): ResponseInterface { - $files = array(); + $files = []; $config = $this->cli->setJsonFileStream($this->configFile); $name = ($this->args['name'] ?? ""); $lastestVersion = $version = ($this->args['version'] ?? null); @@ -338,7 +338,7 @@ public function help() return $this->cli->getResponse(); } - private function deletePackageFile(string $name, string $version, array &$files = array()): void + private function deletePackageFile(string $name, string $version, array &$files = []): void { $file = "{$name}-{$version}"; $packageFile = "{$this->packageDir}{$file}"; diff --git a/Cli/Connectors/Server.php b/Cli/Connectors/Server.php index 6f74cfc..7970cbd 100755 --- a/Cli/Connectors/Server.php +++ b/Cli/Connectors/Server.php @@ -2,67 +2,112 @@ namespace MaplePHP\Foundation\Cli\Connectors; -use MaplePHP\Foundation\Cli\Connectors\CliInterface; -use MaplePHP\Http\Interfaces\ResponseInterface; -use MaplePHP\Http\Interfaces\RequestInterface; -use MaplePHP\Container\Interfaces\ContainerInterface; -use MaplePHP\Http\Env; -use MaplePHP\Container\Reflection; -use MaplePHP\Foundation\Cli\StandardInput; use MaplePHP\Foundation\Http\Dir; +use MaplePHP\Prompts\PromptException; -class Server implements CliInterface +class Server extends AbstractCli { - protected $container; - protected $args; - protected $dir; - protected $cli; - protected $port = 8000; + protected Dir $dir; - public function __construct(ContainerInterface $container, RequestInterface $request, Dir $dir, StandardInput $cli, Env $env) + public const PROMPT = [ + 'start' => [ + "host" => [ + "type" => "text", + "message" => "Host", + "default" => "localhost", + "validate" => [ + "length" => [1,200], + "domain" => [] + ], + "error" => "Not a valid host", + "description" => "Set hostname", + ], + "port" => [ + "type" => "text", + "message" => "Port", + "default" => "8282", + "validate" => [ + "length" => [4,4], + "isInt" => [] + ], + "error" => "Not a valid port expecting 4 digits e.g. (0000)", + "description" => "Set a port number", + ], + "path" => [ + "type" => "hidden", + "message" => "Change the public directory", + "default" => "public", + "validate" => [ + "length" => [1,120] + ], + "error" => "Required", + ] + ] + ]; + + /** + * Will start a MaplePHP server + * @param Dir $dir + * @return void + * @throws PromptException + */ + public function start(Dir $dir): void { - $this->container = $container; - $this->args = $request->getCliArgs(); - $this->dir = $dir; - $this->cli = $cli; - } + $this->prompt->setTitle("Preparing Server"); + $this->prompt->set($this->filterSetArgs(static::PROMPT[__FUNCTION__])); + $prompt = $this->getFilterVal($this->prompt->prompt()); + + $host = $prompt['host']; + $port = (int)$prompt['port']; + $path = ($this->args['path'] ?? "public"); + $pathPublicDir = realpath($dir->getPublic($path)); + + if (!$pathPublicDir) { + $this->command->error("Invalid directory path to public directory"); + return; + } + + + $this->command->title("\nMaplePHP Server has started, visit bellow to display you app."); + $this->command->statusMsg("Visit: http://$host:$port\n"); + $cmd = sprintf('php -S %s -t %s', escapeshellarg("$host:$port"), escapeshellarg($pathPublicDir)); + + // Start PHP built-in server + $phpProcess = proc_open($cmd, [], $pipes); + if (!is_resource($phpProcess)) { + echo "Failed to start PHP built-in server.\n"; + return; + } + + // Start Vite dev server + $viteProcess = proc_open("npm run dev", [], $pipes); + if (!is_resource($viteProcess)) { + proc_terminate($phpProcess); + return; + } + + $phpStatus = proc_get_status($phpProcess); + $viteStatus = proc_get_status($viteProcess); + + while ($phpStatus['running'] || $viteStatus['running']) { + usleep(100000); // Sleep for 100ms + $phpStatus = proc_get_status($phpProcess); + $viteStatus = proc_get_status($viteProcess); + } + + // Cleanup + proc_close($phpProcess); + proc_close($viteProcess); + + + $this->command->message("Servers has been stopped"); - public function start() - { - die("COMING SOON"); - - //putenv("") - $localIPs = $this->cli->getLocalIPAddress(); - $this->cli->write("MaplePHP Server has started, vist bellow to display you app."); - $this->cli->write("Vist: http://{$localIPs}:8080"); - $cmd = (sprintf('php -S %s:%d -t %s', $localIPs, 8080, escapeshellarg($this->dir->getPublic()))); - //$command = "nohup $cmd > /dev/null 2>&1 & echo $! > server.pid"; - //echo $cmd; - //die; - $out = shell_exec('php -S 10.0.1.220:8080 -t /var/www/html/systems/_phpfuse/'); - - return $this->cli->getResponse(); - } - public function stop() - { /* - $pid = file_get_contents('server.pid'); - shell_exec("kill $pid"); - $this->cli->write("The server has been stopped!"); + if($out) { + $this->command->error("Could not connect to host!"); + } */ - return $this->cli->getResponse(); - } - - public function help() - { - $this->cli->write('$ config [type] [--values, --values, ...]'); - $this->cli->write('Type: install, read, create, drop or help'); - $this->cli->write('Values: --key=%s, --value=%s --strict'); - $this->cli->write('--key: The env config key (type: create, drop)'); - $this->cli->write('--value: The env config value (type: create)'); - $this->cli->write('--strict: Will also show hidden configs, (type: read)'); - return $this->cli->getResponse(); } } diff --git a/Cli/Connectors/Test.php b/Cli/Connectors/Test.php new file mode 100755 index 0000000..10af86a --- /dev/null +++ b/Cli/Connectors/Test.php @@ -0,0 +1,31 @@ +args = $request->getCliArgs(); + } + + /** + * Run all test suites + * @param Dir $dir + * @return void + * @throws Exception + */ + public function test(Dir $dir): void + { + $unit = new FileIterator($this->args); + $path = ($this->args['path'] ?? $dir->getPublic()); + $unit->executeAll($path); + } +} diff --git a/Cli/Make/app/Http/Controllers/Auth/Login.php b/Cli/Make/app/Http/Controllers/Auth/Login.php new file mode 100755 index 0000000..e4d7ca2 --- /dev/null +++ b/Cli/Make/app/Http/Controllers/Auth/Login.php @@ -0,0 +1,237 @@ +form = $form; + $this->mail = $mail; + $this->auth = $auth; + } + + public function form(ResponseInterface $response, RequestInterface $request): ResponseInterface + { + $this->form->build(); + $url = $this->url()->select(["page"])->add(["model"])->getUrl(); + + + $this->view()->setPartial("form", [ + "name" => $this->local("auth")->get("signIn", "Sign in"), + "content" => "You can use regular form like bellow or place form in a modal: " . + "Click here", + "form" => [ + "method" => "post", + "action" => $this->url()->reset()->add(["login"])->getUrl(), + "form" => $this->form, + "submit" => "Send" + ] + ]); + + return $response->clearCache(); + } + + public function formModel(): object + { + $this->form->build(); + + $item = $this->responder()->setView("modal", [ + "type" => "opener", + "headline" => $this->local("auth")->get("signIn", "Sign in"), + "content" => "Lorem ipsum form responder" + ]); + + $item->field($item->item("form"), [ + "data" => [ + "method" => "post", + "action" => $this->url()->select(["page"])->getUrl(), + "token" => $this->form->getToken(), + "submit" => $this->local("auth")->get("signIn", "Sign in"), + ], + "fields" => $this->form->getFields() + ]); + + return $this->responder()->build(); + } + + /** + * Validate the login + * @param ResponseInterface $response + * @param RequestInterface $request + * @return object (json) + */ + public function login(ResponseInterface $response, RequestInterface $request): object + { + $user = $this->auth->validate($request); + if ($user) { + $this->responder()->redirect($this->url()->getRoot(static::LOGIN_PATH)); + } else { + $this->responder()->error($this->local("auth")->get("wrongCredentials", "Wrong credentials")); + } + return $this->responder()->build(); + } + + /** + * Forgot password + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface + */ + public function forgotPasswordForm(ResponseInterface $response, RequestInterface $request): ResponseInterface + { + $this->view()->setPartial("form", [ + "name" => $this->local("auth")->get("resetPassword", "Reset password"), + "content" => $this->local("auth")->get("resetPasswordInfo"), + "form" => [ + "method" => "post", + "action" => $this->url()->getUrl(), + "form" => $this->form->inst()->text()->name("email")->attr([ + "placeholder" => $this->local("auth")->get(["fillIn", "email"]) + ])->label($this->local("auth")->get("email", "Email"))->get(), + "token" => $this->form->getToken(), + "submit" => "Send" + ] + ]); + + return $response; + } + + /** + * Send forgotten password if received correct email address + * @param ResponseInterface $response + * @param RequestInterface $request + * @return object + */ + public function forgotPasswordPost(ResponseInterface $response, RequestInterface $request): object + { + $param = $request->getParsedBody(); + if (!is_array($param)) { + throw new \Exception("Parsed body is empty", 1); + } + $user = $this->auth->generateForgetToken($param['email']); + if (is_object($user)) { + $changeURL = $this->url()->select("page")->add(["reset", $user->token])->getUrl(); + $view = $this->view()->withView("mail/text", [ + "section" => [ + [ + "content" => [ + $this->view()->createTag("td", $this->local("auth") + ->get("resetPassword", "Reset password"), ["class" => "h4 title"]), + $this->view()->createTag("td", $this->local("auth") + ->get("hello", "Hello"), ["class" => "h1 title"]), + $this->view()->createTag("td", $this->local("auth") + ->get("resetPasswordInfo"), ["class" => "para-2"]), + $this->view()->createTag("td", $this->local("auth") + ->get("clickHere"), ["class" => "para-2"]), + ], + "button" => [ + "url" => $changeURL, + "title" => $this->local("auth")->get("clickHere", "Click here"), + "legend" => $changeURL + ] + ], + [ + "content" => "" . $this->local("auth")->get("linkWillExpire", "Please note that this link will expire", [ + $this->auth::FORGET_TOKEN_EXPIRE . " hours" + ]) . "", + ] + ], + "footer" => [ + "headline" => $this->local("auth") + ->get("anyQuestions", "Any questions?"), + "content" => $this->local("auth") + ->get("contactUsAt", "Contact us at") . ": " . (string)getenv("MAIL_FROMEMAIL") + ] + ]); + + // From adress is set in ENV but is overwritable! + // $this->mail()->setFrom('from@example.com', 'Mailer'); + $this->mail->addAddress($user->email, "{$user->firstname} {$user->lastname}"); + $this->mail->Subject = $this->local("auth")->get("resetPassword", "Reset password"); + $this->mail->Body = $view->get(); + $this->mail->AltBody = "We have received a request to reset the password associated with your account. " . + "Click on the following link to proceed with the password change.\n{$changeURL}"; + $this->mail->send(); + } + + // Will redirect back to login page event if email do not exists. We do this out of security reasons. + $message = $this->local("auth")->get("resetPasswordInfo"); + $this->responder()->okRedirect($message, $this->url()->select("page")->getUrl()); + + return $this->responder()->build(); + } + + + public function resetPasswordForm(ResponseInterface $response, RequestInterface $request): ResponseInterface + { + + $this->auth->token()->setToken($this->url()->select(["token"])->get(), false); + $userID = $this->auth->token()->validate(); + if ($userID) { + $this->view()->setPartial("form", [ + "name" => $this->local("auth")->get(["fillIn", "password"]), + //"content" => "Fill in your email bellow and if you get an email...", + "form" => [ + "method" => "post", + "action" => $this->url()->getUrl(), + "form" => $this->form->inst()->text()->name("password")->attr([ + "type" => "password", "placeholder" => $this->local("auth")->get(["fillIn", "password"]) + ])->label($this->local("auth")->get("password", "Password"))->get(), + "token" => $this->form->getToken(), + "submit" => $this->local("auth")->get(["send", "Send"]) + ] + ]); + } else { + $response = $response->withStatus(403, $this->local("auth")->get("tokenExpired", "Token expired")) + ->setDescription($this->local("auth")->get("newPasswordRequest", "You need to request a new password.")); + } + + return $response; + } + + public function resetPasswordPost(ResponseInterface $response, RequestInterface $request): object + { + $param = $request->getParsedBody(); + if (!is_array($param)) { + throw new \Exception("Parsed body is empty", 1); + } + if (!$this->auth->validatePassword($param['password'])) { + $this->responder()->error($this->local("auth")->get("invalidPassword", "invalid password")); + } else { + $this->auth->token()->setToken($this->url()->select(["token"])->get(), false); + $userID = $this->auth->token()->validate(); + if ($userID !== false && is_int($userID)) { + $this->auth->updatePassword($userID, $param['password']); + $this->auth->token()->disable($userID); + + $message = $this->local("auth")->get("youPasswordChanged", "Your password has changed"); + $this->responder()->okRedirect($message, $this->url()->select("page")->getUrl()); + } else { + $this->responder()->error($this->local("auth")->get("tokenExpired", "Token expired") . ". " . + $this->local("auth")->get("newPasswordRequest", "You need to request a new password.")); + } + } + + return $this->responder()->build(); + } +} diff --git a/Cli/Make/app/Http/Controllers/Auth/Pages.php b/Cli/Make/app/Http/Controllers/Auth/Pages.php new file mode 100755 index 0000000..7fb83f0 --- /dev/null +++ b/Cli/Make/app/Http/Controllers/Auth/Pages.php @@ -0,0 +1,48 @@ +session()->loggedIn()) { + unset($_SESSION); + session_destroy(); + $this->cookies()->delete("maple_token_1"); + } + $response->location($this->url()->getRoot(static::LOGOUT_PATH)); + } + + /** + * Profile + * @param ResponseInterface $response + * @param RequestInterface $request + * @return void + */ + public function profile(ResponseInterface $response, RequestInterface $request) + { + $this->view()->setPartial("main.ingress", [ + "tagline" => getenv("APP_NAME"), + "name" => "Welcome " . $this->user()->firstname, + "content" => "Get ready to build you first application." + ]); + } +} diff --git a/Cli/Make/app/Http/Controllers/Cli/YourCliController.php b/Cli/Make/app/Http/Controllers/Cli/YourCliController.php new file mode 100755 index 0000000..c6e2738 --- /dev/null +++ b/Cli/Make/app/Http/Controllers/Cli/YourCliController.php @@ -0,0 +1,38 @@ +args = $request->getCliArgs(); + $this->cli = $cli; + } + + public function read(): ResponseInterface + { + $this->cli->write("Hello World"); + return $this->cli->getResponse(); + } + + public function install(): ResponseInterface + { + $this->cli->write("Hello World"); + return $this->cli->getResponse(); + } + + public function help(): ResponseInterface + { + $this->cli->write("Help me text"); + return $this->cli->getResponse(); + } +} diff --git a/Cli/Make/app/Http/Controllers/Default.php b/Cli/Make/app/Http/Controllers/Default.php new file mode 100755 index 0000000..c0461b1 --- /dev/null +++ b/Cli/Make/app/Http/Controllers/Default.php @@ -0,0 +1,32 @@ +provider = $provider; + } + + /** + * Display a start/listing of the resource. + */ + public function index() + { + } + + /** + * Display the specified resource. + */ + public function show(Response $response) + { + } + +} diff --git a/Cli/Make/app/Http/Controllers/Request.php b/Cli/Make/app/Http/Controllers/Request.php new file mode 100755 index 0000000..9ad62b9 --- /dev/null +++ b/Cli/Make/app/Http/Controllers/Request.php @@ -0,0 +1,69 @@ +provider = $provider; + $this->form = $form; + } + + /** + * Display a start/listing of the resource. + */ + public function index() + { + } + + /** + * Show the form for creating a new resource. + */ + public function create() + { + } + + /** + * Store a newly created resource in storage. + */ + public function store(Request $request, Validate $validate) + { + } + + /** + * Display the specified resource. + */ + public function show(Response $response) + { + } + + /** + * Show the form for editing the specified resource. + */ + public function edit(Response $response) + { + } + + /** + * Update the specified resource in storage. + */ + public function update(Request $request, Response $response, Validate $validate) + { + } + + /** + * Remove the specified resource from storage. + */ + public function destroy(Request $request, Response $response) + { + } +} diff --git a/Cli/Make/app/Http/Controllers/Resource.php b/Cli/Make/app/Http/Controllers/Resource.php new file mode 100755 index 0000000..b4a4839 --- /dev/null +++ b/Cli/Make/app/Http/Controllers/Resource.php @@ -0,0 +1,120 @@ +provider = $provider; + } + + /** + * The start page see router + * @return ResponseInterface + */ + public function start() + { + $this->provider->view()->setPartial("main.ingress", [ + "tagline" => "Ingress view partial", + "name" => "Welcome to MaplePHP", + "content" => "Get ready to build you first application." + ]); + + $this->provider->view()->setPartial("main.text", [ + "tagline" => "Text view partial A", + "name" => "Lorem ipsum dolor", + "content" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam id sapien dui. Nullam gravida bibendum finibus. Pellentesque a elementum augue. Aliquam malesuada et neque ac varius. Nam id eros eros. Ut ut mattis ex. Aliquam molestie tortor quis ultrices euismod. Quisque blandit pellentesque purus, in posuere ex mollis ac." + ]); + + $this->provider->view()->setPartial("main.text.textB", [ + "tagline" => "Text view partial B", + "name" => "Lorem ipsum dolor", + "content" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam id sapien dui. Nullam gravida bibendum finibus. Pellentesque a elementum augue. Aliquam malesuada et neque ac varius. Nam id eros eros. Ut ut mattis ex. Aliquam molestie tortor quis ultrices euismod. Quisque blandit pellentesque purus, in posuere ex mollis ac." + ]); + + retur + } + + /** + * The about page (see router) + * @param ResponseInterface $response PSR-7 Response + * @param RequestInterface $request PSR-7 Request + * @return ResponseInterface + */ + public function about(ResponseInterface $response, RequestInterface $request): ResponseInterface + { + + // Overwrite the default meta value + //$this->head()->getElement("title")->setValue("Welcome to my awesome app"); + //$this->head()->getElement("description")->attr("content", "Some text about my awesome app"); + + // $this->view() is the same as $this->provider when extending to the BaseController!; + $this->view()->setPartial("main.ingress", [ + "tagline" => "Layered structure MVC framework", + "name" => "MaplePHP" + ]); + + $this->view()->setPartial("main.text", [ + "tagline" => "Layered structure MVC framework", + "name" => "MaplePHP", + "content" => "MaplePHP is a layered structure PHP framework that has been meticulously crafted to " . + "provide developers with an intuitive, user-friendly experience that doesn't compromise on performance " . + "or scalability. By leveraging a modular architecture with full PSR interface support, the framework " . + "allows for easy customization and flexibility, enabling developers to pick and choose the specific " . + "components they need to build their applications." + ]); + + // Browser cache content up to an hour + // This will work even with a session open so be careful + // return $response->setCache($this->date()->getTimestamp(), 3600); + return $response; + } + + + /** + * The about page (see router) + * @param ResponseInterface $response PSR-7 Response + * @param RequestInterface $request PSR-7 Request + * @return ResponseInterface + */ + public function policy(ResponseInterface $response, RequestInterface $request): ResponseInterface + { + $this->view()->setPartial("main.text.integrity", [ + "name" => "Integrity policy", + "content" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam id sapien dui. Nullam gravida bibendum finibus. Pellentesque a elementum augue. Aliquam malesuada et neque ac varius. Nam id eros eros. Ut ut mattis ex. Aliquam molestie tortor quis ultrices euismod. Quisque blandit pellentesque purus, in posuere ex mollis ac." + ]); + + $this->view()->setPartial("main.text.cookie", [ + "name" => "Cookies", + "content" => "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam id sapien dui. Nullam gravida bibendum finibus. Pellentesque a elementum augue. Aliquam malesuada et neque ac varius. Nam id eros eros. Ut ut mattis ex. Aliquam molestie tortor quis ultrices euismod. Quisque blandit pellentesque purus, in posuere ex mollis ac." + ]); + + return $response; + } + + /** + * Will be invoked if method in router is missing + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface + */ + public function __invoke(ResponseInterface $response, RequestInterface $request): object + { + $_response = $response->withHeader("Content-type", "application/json; charset=UTF-8"); + + // Repaint the whole HTML document with: + // @response->getBody()->write("New content...") + // @responder->build(), will do as above but read current responder json data + return $this->responder()->build(); + } +} diff --git a/Cli/Make/app/Http/Middlewares/Defualt.php b/Cli/Make/app/Http/Middlewares/Defualt.php new file mode 100755 index 0000000..0d8dcdd --- /dev/null +++ b/Cli/Make/app/Http/Middlewares/Defualt.php @@ -0,0 +1,50 @@ +provider = $provider; + } + + /** + * Will load before the controllers + * @param ResponseInterface $response + * @param RequestInterface $request + * @return void + */ + public function before(ResponseInterface $response, RequestInterface $request) + { + // Bind array data to the provider/container. + } + + /** + * Custom Before middleware (Will load before the controllers) + * @param ResponseInterface $response + * @param RequestInterface $request + * @return void + */ + public function yourCustomMethod(ResponseInterface $response, RequestInterface $request) + { + } + + /** + * Will load after the controllers + * @param ResponseInterface $response + * @param RequestInterface $request + * @return void + */ + public function after(ResponseInterface $response, RequestInterface $request) + { + // This will take over the ingress view in at partial main location. + } +} diff --git a/Cli/Make/app/Http/Middlewares/Document.php b/Cli/Make/app/Http/Middlewares/Document.php new file mode 100755 index 0000000..8518f32 --- /dev/null +++ b/Cli/Make/app/Http/Middlewares/Document.php @@ -0,0 +1,87 @@ +provider = $provider; + $this->nav = $nav; + } + + /** + * Will load before the controllers + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface|void + */ + public function before(ResponseInterface $response, RequestInterface $request) + { + } + + /** + * Add head to the document + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface|void + */ + public function head(ResponseInterface $response, RequestInterface $request) + { + // Partial in document director + // The exclamation character will disable thrown error, if you remove the partial template file. + $this->provider->view()->setPartial("head.!document/head"); + + } + + /** + * Add head to the document + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface|void + */ + public function navigation(ResponseInterface $response, RequestInterface $request) + { + // Partial in document director + // The exclamation character will disable thrown error, if you remove the partial template file. + $this->provider->view()->setPartial("navigation.!document/navigation", [ + "nav" => $this->nav->get() + ]); + } + + /** + * Add footer to the document + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface|void + */ + public function footer(ResponseInterface $response, RequestInterface $request) + { + // Partial in document director + // The exclamation character will disable thrown error, if you remove the partial template file. + $this->provider->view()->setPartial("footer.!document/footer", [ + "nav" => $this->nav->get() + ]); + + } + + /** + * Will load after the controllers + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface|void + */ + public function after(ResponseInterface $response, RequestInterface $request) + { + + } +} diff --git a/Cli/Make/app/Http/Middlewares/Navigation.php b/Cli/Make/app/Http/Middlewares/Navigation.php new file mode 100755 index 0000000..c6a4ad7 --- /dev/null +++ b/Cli/Make/app/Http/Middlewares/Navigation.php @@ -0,0 +1,75 @@ +provider = $provider; + $this->nav = $nav; + } + + /** + * Before controllers + * @param ResponseInterface $response + * @param RequestInterface $request + * @return ResponseInterface|void + */ + public function before(ResponseInterface $response, RequestInterface $request) + { + + // You can use this middelware to create an dynamic navigation + // The id is not required, but will create it´s own id with increment, starting from 1 if not filled in. + // The id is used to select parent! + $this->nav->add("main", [ + "id" => 1, + "name" => "Start", + "slug" => "", + "parent" => 0, + "title" => "Meta title start", + "description" => "Meta description start" + + ])->add("main", [ + "id" => 2, + "name" => "Contact", + "slug" => "contact", + "parent" => 0, + "title" => "Meta title contact", + "description" => "Meta description contact" + ]); + + // Will build the navigation + return parent::before($response, $request); + } + + /** + * After controllers + * @param ResponseInterface $response + * @param RequestInterface $request + * @return void + */ + public function after(ResponseInterface $response, RequestInterface $request) + { + + } + + +} diff --git a/Cli/Make/app/Models/Request.php b/Cli/Make/app/Models/Request.php new file mode 100755 index 0000000..97b40be --- /dev/null +++ b/Cli/Make/app/Models/Request.php @@ -0,0 +1,57 @@ +form->add([ + "firstname" => [ + "type" => "text", + "label" => $this->provider->local("auth")->get("firstname", "First name"), + "validate" => [ + "length" => [1, 60] + ] + ], + "lastname" => [ + "type" => "text", + "label" => $this->provider->local("auth")->get("lastname", "Last name"), + "validate" => [ + "length" => [1, 80] + ] + ], + "email" => [ + "type" => "text", + "label" => $this->provider->local("auth")->get("email", "Email"), + "attr" => [ + "type" => "email" + ], + "validate" => [ + "length" => [1, 160] + ] + ], + "message" => [ + "type" => "textarea", + "label" => $this->provider->local("auth")->get("message", "Message"), + "validate" => [ + "length" => [1, 2000] + ] + ] + ]); + + /* + // Set form values + $this->form->setValues([ + "firstname" => "John Doe", + ]); + */ + } +} diff --git a/Cli/Make/app/Services/Request.php b/Cli/Make/app/Services/Request.php new file mode 100755 index 0000000..756506f --- /dev/null +++ b/Cli/Make/app/Services/Request.php @@ -0,0 +1,57 @@ +form->add([ + "firstname" => [ + "type" => "text", + "label" => $this->provider->local("auth")->get("firstname", "First name"), + "validate" => [ + "length" => [1, 60] + ] + ], + "lastname" => [ + "type" => "text", + "label" => $this->provider->local("auth")->get("lastname", "Last name"), + "validate" => [ + "length" => [1, 80] + ] + ], + "email" => [ + "type" => "text", + "label" => $this->provider->local("auth")->get("email", "Email"), + "attr" => [ + "type" => "email" + ], + "validate" => [ + "length" => [1, 160] + ] + ], + "message" => [ + "type" => "textarea", + "label" => $this->provider->local("auth")->get("message", "Message"), + "validate" => [ + "length" => [1, 2000] + ] + ] + ]); + + /* + // Set form values + $this->form->setValues([ + "firstname" => "John Doe", + ]); + */ + } +} diff --git a/Cli/Make/database/Migrations/Default.php b/Cli/Make/database/Migrations/Default.php new file mode 100755 index 0000000..845e82f --- /dev/null +++ b/Cli/Make/database/Migrations/Default.php @@ -0,0 +1,56 @@ +mig->drop(); + + $this->mig->column("id", [ + "type" => "int", + "length" => 11, + "attr" => "unsigned", + "index" => "primary", + "ai" => true + + ])->column("name", [ + "type" => "varchar", + "length" => 160, + "collate" => true + + ])->column("content", [ + "type" => "text", + "collate" => true + + ])->column("status", [ + "type" => "int", + "length" => 11, + "index" => "index", + "default" => 0 + + ])->column("parent", [ + "type" => "int", + "length" => 11, + "index" => "index", + "default" => 0 + + ])->column("create_date", [ + "type" => "datetime", + "default" => "2000-01-01 00:00:00" + ]); + } +} diff --git a/Cli/Make/make.json b/Cli/Make/make.json new file mode 100755 index 0000000..279d113 --- /dev/null +++ b/Cli/Make/make.json @@ -0,0 +1,36 @@ +{ + "controller": { + "default": [ + { + "file": "app/Http/Controllers/Default", + "name": "%sController" + } + ], + "request": [ + { + "file": "app/Http/Controllers/Request", + "name": "%sController" + }, + { + "file": "app/Models/Request", + "name": "%sRequestModel" + } + ] + }, + "model": { + "request": [ + { + "file": "app/Models/Request", + "name": "%sRequestModel" + } + ] + }, + "middleware": { + "default": ["app/Http/Middlewares/Default"], + "document": ["app/Http/Middlewares/Document"], + "navigation": ["app/Http/Middlewares/Navigation"] + }, + "migration": { + "default": ["database/migrations/Default"] + } +} diff --git a/Cli/Packaging.php b/Cli/Packaging.php index 3114b4a..54ef58b 100755 --- a/Cli/Packaging.php +++ b/Cli/Packaging.php @@ -12,7 +12,7 @@ class Packaging public const REQUIRED = ["package", "description", "version", "architecture", "maintainer", "files"]; public const CONTROL_COLUMNS = ["package", "description", "version", "architecture", "maintainer"]; - private $data = array(); + private $data = []; public function setPackage(string $package): void { @@ -57,7 +57,7 @@ public function getData(): array public function formatFileName(string $fileName): string { $str = new Str($fileName); - return $str->clearBreaks()->trim()->replaceSpecialChar()->replaceSpaces("")->get(); + return $str->clearBreaks()->trim()->normalizeAccents()->replaceSpaces("")->get(); } public function getControlFile(): string @@ -135,7 +135,7 @@ public function validate() private function fileCommand(string $rootDir, string $packageFile): string { - $fileError = $fileCommand = array(); + $fileError = $fileCommand = []; $files = explode(",", $this->data['files']); foreach ($files as $file) { $file = str_replace("\\", "/", $file); diff --git a/Cli/Routers/default.php b/Cli/Routers/default.php index 82fee9e..0e21af5 100755 --- a/Cli/Routers/default.php +++ b/Cli/Routers/default.php @@ -6,13 +6,13 @@ * * Can be used as bellow, this will first search for "sibling param" if not just use "/": * - * {page:(?:.+/|/)vara-bilar} + * {page:(?:.+/|/)our-cars} * - * OK: "/PageParam1/PageParam2/vara-bilar" - * OK: "/vara-bilar" + * OK: "/PageParam1/PageParam2/our-cars" + * OK: "/our-cars" * * Add a dynamic route from pages - * /{page:.+}/{id:\d+}/{permalink:bil-[^/]+} + * /{page:.+}/{id:\d+}/{permalink:car-[^/]+} * * @var object $routes */ @@ -21,40 +21,76 @@ // Group handle is not required, but is a great way to organizing CLI packages->type calls +// +//$routes->cli("/install/help", ['MaplePHP\Foundation\Cli\Connectors\Install', "help"]); +//$routes->cli("/install/{action:[^/]+}", ['MaplePHP\Foundation\Cli\Connectors\Install', "config"]); +$routes->cli("/test", ['MaplePHP\Foundation\Cli\Connectors\Test', "test"]); + +$routes->group("/install", function ($routes) { + // It is recommended to add this handle at the beginning of every grouped call + $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); + $routes->cli("/help", ['MaplePHP\Foundation\Cli\Connectors\Install', "help"]); + $routes->cli("[/{action:[^/]+}]", ['MaplePHP\Foundation\Cli\Connectors\Install', "config"]); +}); + +$routes->group("/server", function ($routes) { + // It is recommended to add this handle at the beginning of every grouped call + $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); + $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Server', "help"]); + $routes->cli("/start", ['MaplePHP\Foundation\Cli\Connectors\Server', "start"]); + //$routes->cli("/test", ['MaplePHP\Foundation\Cli\Connectors\Server', "test"]); +}); + + // Database creation/migration $routes->group("/migrate", function ($routes) { - // It is recommended to add this handle at the begining of every grouped call + // It is recommended to add this handle at the beginning of every grouped call $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); - $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Migrate', "help"]); + //$routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Migrate', "migrate"]); + $routes->cli("[/migrate]", ['MaplePHP\Foundation\Cli\Connectors\Migrate', "migrate"]); + - $routes->cli("/create", ['MaplePHP\Foundation\Cli\Connectors\Migrate', "create"]); $routes->cli("/read", ['MaplePHP\Foundation\Cli\Connectors\Migrate', "read"]); $routes->cli("/drop", ['MaplePHP\Foundation\Cli\Connectors\Migrate', "drop"]); + $routes->cli("/help", ['MaplePHP\Foundation\Cli\Connectors\Migrate', "help"]); }); +$routes->group("/make", function ($routes) { + // It is recommended to add this 2 handles at the beginning of every grouped call + $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); + $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Make', "help"]); + + $routes->cli("/{action:[^/]+}", ['MaplePHP\Foundation\Cli\Connectors\Make', "make"]); + //$routes->cli("/package", ['MaplePHP\Foundation\Cli\Connectors\Config', "package"]); + //$routes->cli("/create", ['MaplePHP\Foundation\Cli\Connectors\Config', "create"]); + //$routes->cli("/read", ['MaplePHP\Foundation\Cli\Connectors\Config', "read"]); + //$routes->cli("/drop", ['MaplePHP\Foundation\Cli\Connectors\Config', "drop"]); +}); + + +/* + $routes->group("/config", function ($routes) { - // It is recommended to add this 2 handles at the begining of every grouped call + // It is recommended to add this 2 handles at the beginning of every grouped call $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Config', "help"]); $routes->cli("/install", ['MaplePHP\Foundation\Cli\Connectors\Config', "install"]); - $routes->cli("/package", ['MaplePHP\Foundation\Cli\Connectors\Config', "package"]); - $routes->cli("/create", ['MaplePHP\Foundation\Cli\Connectors\Config', "create"]); - $routes->cli("/read", ['MaplePHP\Foundation\Cli\Connectors\Config', "read"]); - $routes->cli("/drop", ['MaplePHP\Foundation\Cli\Connectors\Config', "drop"]); + //$routes->cli("/package", ['MaplePHP\Foundation\Cli\Connectors\Config', "package"]); + //$routes->cli("/create", ['MaplePHP\Foundation\Cli\Connectors\Config', "create"]); + //$routes->cli("/read", ['MaplePHP\Foundation\Cli\Connectors\Config', "read"]); + //$routes->cli("/drop", ['MaplePHP\Foundation\Cli\Connectors\Config', "drop"]); }); - -$routes->group("/image", function ($routes) { - // It is recommended to add this 2 handles at the begining of every grouped call + $routes->group("/image", function ($routes) { + // It is recommended to add this 2 handles at the beginning of every grouped call $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Image', "help"]); $routes->cli("/resize", ['MaplePHP\Foundation\Cli\Connectors\Image', "resize"]); }); - $routes->group("/package", function ($routes) { - // It is recommended to add this 2 handles at the begining of every grouped call + // It is recommended to add this 2 handles at the beginning of every grouped call $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Package', "help"]); @@ -67,9 +103,11 @@ $routes->cli("/updateBuild", ['MaplePHP\Foundation\Cli\Connectors\Package', "updateBuild"]); $routes->cli("/delete", ['MaplePHP\Foundation\Cli\Connectors\Package', "delete"]); }); +*/ + $routes->group("/database", function ($routes) { - // It is recommended to add this 2 handles at the begining of every grouped call + // It is recommended to add this 2 handles at the beginning of every grouped call $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Database', "help"]); @@ -79,7 +117,7 @@ }); $routes->group("/mail", function ($routes) { - // It is recommended to add this 2 handles at the begining of every grouped call + // It is recommended to add this 2 handles at the beginning of every grouped call $routes->map("*", '[/{any:.*}]', ['MaplePHP\Foundation\Cli\Connectors\Cli', "handleMissingType"]); $routes->cli("[/help]", ['MaplePHP\Foundation\Cli\Connectors\Mail', "help"]); $routes->cli("/send", ['MaplePHP\Foundation\Cli\Connectors\Mail', "send"]); diff --git a/Cli/StandardInput.php b/Cli/StandardInput.php index 5ebbfef..961db15 100755 --- a/Cli/StandardInput.php +++ b/Cli/StandardInput.php @@ -31,7 +31,7 @@ public function __construct(ResponseInterface $response) /** * CLi confirmation * @param string $message - * @param callable $call + * @param callable $call * @return void */ public function confirm(string $message, callable $call) @@ -71,7 +71,7 @@ public function step(?string $message, ?string $default = null, ?string $respons * @param string|null $prompt * @return string */ - function maskedInput(?string $prompt = null, ?string $valid = "required", array $args = []): string + public function maskedInput(?string $prompt = null, ?string $valid = "required", array $args = []): string { $this->stream = new Stream(Stream::STDIN, "r"); $prompt = $this->prompt($prompt." (masked input)"); @@ -102,7 +102,7 @@ function maskedInput(?string $prompt = null, ?string $valid = "required", array } /** - * Will give you multiple option to choose between + * Will give you multiple option to choose between * @param array $choises * @return string */ @@ -237,7 +237,7 @@ public function readFile(string $file): string } /** - * Get json from stream + * Get json from stream * @param string $file */ public function setJsonFileStream(string $file) @@ -261,7 +261,7 @@ public function setJsonFileStream(string $file) * WARNING: Built to wokr with CLI and not server * @return string|false */ - function lossyGetPublicIP(): string|false + public function lossyGetPublicIP(): string|false { if (extension_loaded('curl')) { $client = new Client([ @@ -285,14 +285,14 @@ function lossyGetPublicIP(): string|false * @param string $fullDirPath E.g. '/var/www/html/dir1/dir2' * @return string|false */ - function lossyGetRelativePath($fullDirPath): string|false + public function lossyGetRelativePath($fullDirPath): string|false { $checkDirs = [ - '/var/www/html', - '/Library/WebServer/Documents', - 'C:\xampp\htdocs', - '/opt/lampp/htdocs', - '/usr/share/nginx/html', + '/var/www/html', + '/Library/WebServer/Documents', + 'C:\xampp\htdocs', + '/opt/lampp/htdocs', + '/usr/share/nginx/html', 'C:\nginx\html' ]; foreach($checkDirs as $basePath) { @@ -309,7 +309,7 @@ function lossyGetRelativePath($fullDirPath): string|false * WARNING: Built to wokr with CLI and not server * @return string */ - function lossyGetLocalIP(): string + public function lossyGetLocalIP(): string { $serverIP = ""; $host = gethostname(); @@ -322,7 +322,7 @@ function lossyGetLocalIP(): string return $serverIP; } - function getLocalIPAddress(): string|false + public function getLocalIPAddress(): string|false { $result = false; $os = strtolower(PHP_OS); diff --git a/DocComment/Comment.php b/DocComment/Comment.php new file mode 100755 index 0000000..be901f2 --- /dev/null +++ b/DocComment/Comment.php @@ -0,0 +1,88 @@ +class = (is_object($class)) ? $class::class : $class; + } + + /** + * Get Class name + * @return string + */ + public function getClass(bool $base = false): string + { + if($base) { + $expClass = explode('\\', $this->class); + return end($expClass); + } + return $this->class; + } + + /** + * Get all methods + * @return array + */ + public function getAllMethods(): array + { + return get_class_methods($this->class); + } + + /** + * Get parsed doc comment + * @param string $method + * @return string|false + * @throws \ReflectionException + */ + public function getDocComment(string $method): array|false + { + $reflection = new \ReflectionClass($this->class); + $inst = $reflection->getMethod($method); + $docComment = $inst->getDocComment(); + if ($docComment !== false) { + $arr = $this->parseDocComment($docComment); + $arr['method'] = $method; + return $arr; + } + return false; + } + + /** + * Parse the Doc comment to array + * @param string $docComment + * @return array + */ + protected function parseDocComment(string $docComment): array + { + $arr = []; + $lines = preg_split('/\r\n|\r|\n/', $docComment); + foreach ($lines as $line) { + $line = trim($line, "/* \t\n\r\0\x0B"); + if (preg_match('/^@(\w+)\s+(.*)$/', $line, $matches)) { + $tag = $matches[1]; + $desc = $matches[2]; + $arr[$tag] = []; + if(isset($arr[$tag])) { + $arr[$tag][] = $desc; + } + + } else { + if (!isset($arr['description'])) { + $arr['description'] = []; + } + $arr['description'][] = $line; + } + } + + // Flatten description array to a string + if (isset($arr['description'])) { + $arr['description'] = implode(' ', $arr['description']); + } + return $arr; + } +} diff --git a/Dom/Middleware/Meta.php b/Dom/Middleware/Meta.php index deb1bab..f558a9d 100755 --- a/Dom/Middleware/Meta.php +++ b/Dom/Middleware/Meta.php @@ -22,11 +22,9 @@ public function __construct(Provider $provider, Json $json) /** * Before controllers - * @param ResponseInterface $response - * @param RequestInterface $request * @return void */ - public function before(ResponseInterface $response, RequestInterface $request) + public function before() { $activeResult = false; $myAppName = $this->provider->env("APP_NAME", "My App"); @@ -51,12 +49,10 @@ public function before(ResponseInterface $response, RequestInterface $request) /** * After controllers - * @param ResponseInterface $response - * @param RequestInterface $request * @return void */ - public function after(ResponseInterface $response, RequestInterface $request) - { + public function after() + { // Set the config here in before just to make sure it is already set in the services and controllers $this->provider->foot()->getElement("config") ->setValue("const CONFIG = " . $this->json->encode()); diff --git a/Form/Builder.php b/Form/Builder.php index 79e7305..f34b48a 100755 --- a/Form/Builder.php +++ b/Form/Builder.php @@ -4,23 +4,30 @@ namespace MaplePHP\Foundation\Form; +use MaplePHP\Container\Interfaces\ContainerExceptionInterface; use MaplePHP\Container\Interfaces\ContainerInterface; +use MaplePHP\Container\Interfaces\NotFoundExceptionInterface; use MaplePHP\Form\Fields; use MaplePHP\Foundation\Security\Csrf; use MaplePHP\Foundation\Form\FormFields; -use BadMethodCallException; - +/** + * @method Fields add(array[] $array) + */ class Builder { public const FORM_NAME = null; - protected $form; - protected $csrf; - + protected Fields $form; + protected Csrf $csrf; + /** * Form modal will combine all essentials libraries * @param ContainerInterface $container + * @param FormFields $FormFields + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws \Exception */ public function __construct(ContainerInterface $container, FormFields $FormFields) { @@ -73,7 +80,7 @@ public function createToken(): string { return $this->csrf->createToken(); } - + /** * Shortcut to all the class Fields methods * @param string $fieldName diff --git a/Form/FormFields.php b/Form/FormFields.php index 2b4aeee..057fc8f 100755 --- a/Form/FormFields.php +++ b/Form/FormFields.php @@ -12,7 +12,6 @@ class FormFields extends AbstractFormFields { - protected $provider; public function __construct(Provider $provider) diff --git a/Form/Forms/AbstractForm.php b/Form/Forms/AbstractForm.php index 787c849..2e24d98 100755 --- a/Form/Forms/AbstractForm.php +++ b/Form/Forms/AbstractForm.php @@ -7,8 +7,8 @@ abstract class AbstractForm { - protected $form; - protected $provider; + protected Builder $form; + protected Provider $provider; public function __construct(Provider $provider, Builder $form) { @@ -18,16 +18,16 @@ public function __construct(Provider $provider, Builder $form) } /** - * Create form - Setup the form inside of this method + * Create form - Set up the form inside of this method * @return void */ abstract protected function createForm(): void; /** * Direct access form instance - * @return form + * @return Builder */ - public function form() + public function form(): Builder { return $this->form; } @@ -35,12 +35,12 @@ public function form() /** * Shortcut to all the class Models\Form\Form AND MaplePHP\Form\Fields methods! * If thrown Error, then it will be triggered from Models\Form\Form - * @param string $a Method name - * @param array $b argumnets + * @param string $method Method name + * @param array $args arguments * @return mixed */ - public function __call($a, $b) + public function __call(string $method, array $args) { - return call_user_func_array([$this->form, $a], $b); + return call_user_func_array([$this->form, $method], $args); } } diff --git a/Form/Validate.php b/Form/Validate.php index 67194f8..dab016c 100755 --- a/Form/Validate.php +++ b/Form/Validate.php @@ -2,14 +2,17 @@ namespace MaplePHP\Foundation\Form; +use MaplePHP\Container\Interfaces\ContainerExceptionInterface; use MaplePHP\Container\Interfaces\ContainerInterface; -use MaplePHP\Http\Interfaces\ServerRequestInterface; +use MaplePHP\Container\Interfaces\NotFoundExceptionInterface; use MaplePHP\Foundation\Security\Csrf; use MaplePHP\Foundation\Http\Json; use MaplePHP\Form\Validate as valid; -use MaplePHP\Foundation\Form\Builder; use MaplePHP\Foundation\Form\Forms\AbstractForm; +//use MaplePHP\Http\Interfaces\ServerRequestInterface; +//use MaplePHP\Foundation\Form\Builder; + class Validate { protected $container; @@ -18,6 +21,11 @@ class Validate protected $json; + /** + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface + * @throws \Exception + */ public function __construct(ContainerInterface $container, Json $json) { $this->container = $container; @@ -27,8 +35,8 @@ public function __construct(ContainerInterface $container, Json $json) /** * This will validate the form - * @param Form|AbstractForm $form Instance of Form or AbstractForm - * @param array|object|null $data Request data + * @param Builder|AbstractForm $form Instance of Form or AbstractForm + * @param array|object|null $data Request data * @return bool|array False or XSS protected data */ public function validate(Builder|AbstractForm $form, array|object|null $data) diff --git a/Http/Cookie.php b/Http/Cookie.php index 013e87f..1a4ea4c 100755 --- a/Http/Cookie.php +++ b/Http/Cookie.php @@ -8,9 +8,8 @@ class Cookie { - - const SAMESITE = "Strict"; - const HTTPONLY = true; + public const SAMESITE = "Strict"; + public const HTTPONLY = true; private $url; private $cookies; diff --git a/Http/Dir.php b/Http/Dir.php index 257313a..e56f512 100755 --- a/Http/Dir.php +++ b/Http/Dir.php @@ -7,7 +7,6 @@ class Dir implements DirHandlerInterface { - private $dir; //private $publicDirPath; @@ -24,6 +23,16 @@ public function __construct(DirInterface $dir) */ } + /** + * Get root dir + * @param string $path + * @return string + */ + public function getDir(string $path = ""): string + { + return $this->dir->getDir($path); + } + /** * Get resource dir * @param string $path @@ -31,7 +40,7 @@ public function __construct(DirInterface $dir) */ public function getResources(string $path = ""): string { - return $this->dir->getDir("resources/{$path}"); + return $this->dir->getRoot("resources/{$path}"); } /** @@ -51,7 +60,17 @@ public function getPublic(string $path = ""): string */ public function getStorage(string $path = ""): string { - return $this->dir->getDir("storage/{$path}"); + return $this->dir->getRoot("storage/{$path}"); + } + + /** + * Get storage Foundation + * @param string $path + * @return string + */ + public function getDatabase(string $path = ""): string + { + return $this->dir->getRoot("database/{$path}"); } /** diff --git a/Http/Json.php b/Http/Json.php index f6c309f..0c20dd2 100755 --- a/Http/Json.php +++ b/Http/Json.php @@ -22,8 +22,8 @@ class Json implements JsonInterface ]; - public $data = array("status" => 0, "error" => 0); - public $fields = array(); + public $data = ["status" => 0, "error" => 0]; + public $fields = []; public function __construct() { @@ -162,7 +162,7 @@ public function form($fields): self public function reset(?array $new = null): void { if (is_null($new)) { - $new = array("status" => 0, "error" => 0); + $new = ["status" => 0, "error" => 0]; } $this->data = $new; diff --git a/Http/Provider.php b/Http/Provider.php index 065ca69..6c8ea11 100755 --- a/Http/Provider.php +++ b/Http/Provider.php @@ -2,23 +2,44 @@ namespace MaplePHP\Foundation\Http; +use Exception; +use MaplePHP\Container\Interfaces\ContainerExceptionInterface; +use MaplePHP\Container\Interfaces\NotFoundExceptionInterface; +use MaplePHP\DTO\Traverse; use MaplePHP\Http\Interfaces\ResponseInterface; use MaplePHP\Http\Interfaces\RequestInterface; use MaplePHP\Container\Interfaces\ContainerInterface; use MaplePHP\Http\Interfaces\UrlInterface; use MaplePHP\Http\Interfaces\DirInterface; use MaplePHP\Container\EventHandler; -use MaplePHP\DTO\Format\DateTime; +use MaplePHP\DTO\Format\Clock; use MaplePHP\DTO\Format\Str; use MaplePHP\DTO\Format\Local; use MaplePHP\DTO\Format\Encode; -use MaplePHP\Query\DB; +use MaplePHP\Query\Connect; use BadMethodCallException; +/** + * @method local(string $string) + * @method env(string $key, string $fallback = "") + * @method encode(array|string $value) + * @method DB(?string $key = null) + * @method head() + * @method url() + * @method dir() + * @method response() + * @method request() + * @method view() + */ class Provider { - public static $container; + public static ?ContainerInterface $container = null; + /** + * @throws NotFoundExceptionInterface + * @throws ContainerExceptionInterface + * @throws Exception + */ public function __construct( ContainerInterface $container, ResponseInterface $response, @@ -39,7 +60,7 @@ public function __construct( $url->setHandler($urlHandler); $dir->setHandler($dirHandler); - + $this->getConfProviders(); $this->getBuiltFactories(); } @@ -48,35 +69,33 @@ public function __construct( /** * Some custom factory providers * @return void + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception */ private function getBuiltFactories(): void { - self::$container->set("date", DateTime::value("now") - ->setLanguage(self::$container->get("lang")->prefix())); + self::$container->set("date", Clock::value("now") + ->setLocale(self::$container->get("lang")->prefix())); self::$container->set("string", function ($value) { return new Str($value); }); - self::$container->set("env", function (string $key, string $fallback = "") { - + self::$container->set("dto", function ($value) { + return Traverse::value($value); + }); - /* - if(is_array($key)) { - $keyA = strtoupper($key[0] ?? ""); - $keyB = strtoupper($key[1] ?? ""); - $key = "{$keyA}_{$keyB}" - } - */ + self::$container->set("env", function (string $key, string $fallback = "") { $value = (getenv($key) !== false) ? (string)getenv($key) : $fallback; - return new Str($value); + return Traverse::value($value)->str(); }); - self::$container->set("encode", function ($value) { + self::$container->set("encode", function (array|string $value) { return new Encode($value); }); - self::$container->set("local", function ($langKey) { + self::$container->set("local", function (string $langKey) { $data = [ "auth" => Local::value("auth"), "validate" => Local::value("validate") @@ -84,40 +103,45 @@ private function getBuiltFactories(): void return ($data[$langKey] ?? null); }); - self::$container->set("DB", function () { - return new DB(); + self::$container->set("DB", function (?string $key = null) { + return Connect::getInstance($key); }); } /** * Access the service providers from config file * @return void + * @throws Exception */ private function getConfProviders(): void { $arr = ($_ENV['PROVIDERS_SERVICES'] ?? []); - if(is_array($arr)) foreach($arr as $name => $class) { - - if(is_array($class)) { - $event = new EventHandler(); - foreach($class['handlers'] as $handler => $methods) { - $event->addHandler($handler, $methods); + if(is_array($arr)) { + foreach($arr as $name => $class) { + + if(is_array($class)) { + $event = new EventHandler(); + foreach($class['handlers'] as $handler => $methods) { + $event->addHandler($handler, $methods); + } + foreach($class['events'] as $handler) { + $event->addEvent($handler); + } + $class = $event; } - foreach($class['events'] as $handler) { - $event->addEvent($handler); - } - $class = $event; - } - self::$container->set($name, $class); + self::$container->set($name, $class); + } } } /** * This will make shortcuts to container. - * @param string $method - * @param array $args + * @param string $method + * @param array $args * @return mixed + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface */ public function __call(string $method, array $args): mixed { diff --git a/Http/Responder.php b/Http/Responder.php index e7682b0..271db6e 100755 --- a/Http/Responder.php +++ b/Http/Responder.php @@ -15,8 +15,9 @@ class Responder /** * Use @ServiceResponder to communicate with app frontend code - * @param ResponseInterface $response - * @param Json $json + * @param ContainerInterface $container + * @param ResponseInterface $response + * @param Json $json */ public function __construct(ContainerInterface $container, ResponseInterface $response, Json $json) { @@ -37,7 +38,8 @@ public function json(): Json /** * Add custom data to responder * @param string $key - * @param mixed $data + * @param mixed $data + * @return Responder */ public function add(string $key, mixed $data): self { @@ -48,6 +50,7 @@ public function add(string $key, mixed $data): self /** * Set form values * @param array $values + * @return Responder */ public function setValues(array $values): self { @@ -57,9 +60,9 @@ public function setValues(array $values): self /** * Show message modal - * @param string $textA - * @param string|null $textB - * @return self + * @param string|null $textA + * @param string|null $textB + * @return $this */ public function message(?string $textA, ?string $textB = null): self { @@ -77,9 +80,9 @@ public function message(?string $textA, ?string $textB = null): self /** * Show error modal - * @param string $textA - * @param string|null $textB - * @return self + * @param string|null $textA + * @param string|null $textB + * @return $this */ public function error(?string $textA, ?string $textB = null): self { @@ -89,11 +92,11 @@ public function error(?string $textA, ?string $textB = null): self } /** - * CSRF token error + * CSRF token error * @param string $token New csrf token * @return self */ - public function csrfTokenError(string $token) + public function csrfTokenError(string $token): self { $this->json->add("status", 2); $this->json->add("csrfToken", $token); diff --git a/Http/Url.php b/Http/Url.php index c4f02eb..896305b 100755 --- a/Http/Url.php +++ b/Http/Url.php @@ -7,20 +7,12 @@ class Url implements UrlHandlerInterface { - - private $url; - private $publicDirPath; + private UrlInterface $url; + private ?string $publicDirPath = null; public function __construct(UrlInterface $url) { $this->url = $url; - - /* - $envDir = getenv("APP_PUBLIC_DIR"); - if (is_string($envDir) && $this->validateDir($envDir)) { - $this->publicDirPath = ltrim(rtrim($envDir, "/"), "/"); - } - */ } /** @@ -32,14 +24,14 @@ public function getPublicDirPath(): ?string return $this->publicDirPath; } - /** - * Get URL to public directory - * @param string $path add to URI - * @return string - */ + /** + * Get URL to public directory + * @param string $path add to URI + * @return string + */ public function getPublic(string $path = ""): string { - return $this->url->getRoot("/{$path}"); + return $this->url->getRoot("/$path"); } /** @@ -49,7 +41,7 @@ public function getPublic(string $path = ""): string */ public function getResource(string $path = ""): string { - return $this->url->getRootDir("/resources/{$path}"); + return $this->url->getRootDir("/resources/$path"); } /** @@ -57,9 +49,9 @@ public function getResource(string $path = ""): string * @param string $path add to URI * @return string */ - public function getJs(string $path, bool $isProd = false): string + public function getJs(string $path): string { - return $this->getPublic("js/{$path}"); + return $this->getPublic("js/$path"); } /** @@ -69,7 +61,7 @@ public function getJs(string $path, bool $isProd = false): string */ public function getCss(string $path): string { - return $this->getPublic("css/{$path}"); + return $this->getPublic("css/$path"); } /** @@ -80,6 +72,6 @@ public function getCss(string $path): string public function validateDir(string $path): bool { $fullPath = realpath($_ENV['APP_DIR'].$path); - return (is_string($fullPath) && strpos($fullPath, $_ENV['APP_DIR']) === 0); + return (is_string($fullPath) && str_starts_with($fullPath, $_ENV['APP_DIR'])); } } diff --git a/Kernel/App.php b/Kernel/App.php index 163b1da..01599fc 100755 --- a/Kernel/App.php +++ b/Kernel/App.php @@ -4,10 +4,14 @@ namespace MaplePHP\Foundation\Kernel; +use Exception; +use MaplePHP\Blunder\Handlers\JsonHandler; +use MaplePHP\Blunder\Handlers\SilentHandler; +use MaplePHP\Blunder\Run; +use MaplePHP\Handler\Exceptions\EmitterException; use MaplePHP\Http\Interfaces\ResponseInterface; use MaplePHP\Http\Interfaces\RequestInterface; use MaplePHP\Http\Interfaces\UrlInterface; -use MaplePHP\Foundation\Kernel\AppConfigs; use MaplePHP\Handler\Emitter; use MaplePHP\Handler\RouterDispatcher; use MaplePHP\Http\Dir; @@ -15,57 +19,82 @@ use MaplePHP\DTO\Format\Arr; use MaplePHP\DTO\Format\Local; use MaplePHP\Container\Reflection; +use MaplePHP\Log\Handlers\StreamHandler; +use MaplePHP\Log\Logger; use MaplePHP\Query\Connect; -use Whoops\Handler\HandlerInterface; +use MaplePHP\Query\Exceptions\ConnectException; +use MaplePHP\Query\Handlers\MySQLHandler; +use MaplePHP\Query\Handlers\SQLiteHandler; class App extends AppConfigs { protected Emitter $emitter; protected RouterDispatcher $dispatcher; protected ?object $whoops = null; - - private $installed = false; + private bool $installed = false; public function __construct(Emitter $emitter, RouterDispatcher $dispatcher) { $this->emitter = $emitter; $this->dispatcher = $dispatcher; $this->dir = new Dir( - $this->dispatcher->request()->getUri()->getDir(), + $this->dispatcher->request()->getUri()->getDir(), $this->dispatcher->request()->getUri()->getRootDir() ); } - + /** * Setup a MySql Connection * @return void + * @throws ConnectException */ protected function setupMysqlConnection(): void { - $connect = $this->getenv("MYSQL_HOST"); - $database = $this->getenv("MYSQL_DATABASE"); - - if ($this->hasDBEngine && is_string($connect) && is_string($database)) { - - $port = (int)$this->getenv("MYSQL_PORT"); - if($port === 0) $port = 3306; - - $connect = new Connect( - $connect, - $this->getenv("MYSQL_USERNAME"), - $this->getenv("MYSQL_PASSWORD"), - $database, - $port, - ); - $connect->setCharset($this->getenv("MYSQL_CHARSET")); - $connect->setPrefix($this->getenv("MYSQL_PREFIX")); - $connect->execute(); + + $hasDatabase = $this->getenv("APP_DATABASE_TYPE"); + + if($hasDatabase) { + + $handler = false; + $connect = $this->getenv("DATABASE_HOST"); + $database = $this->getenv("DATABASE_NAME"); + + switch ($hasDatabase) { + case "mysql": case "mariadb": + if ($connect && $database) { + + $port = (int)$this->getenv("DATABASE_PORT"); + if($port === 0) { + $port = 3306; + } + + $handler = new MySQLHandler( + $connect, + $this->getenv("DATABASE_USERNAME", ""), + $this->getenv("DATABASE_PASSWORD", ""), + $database, + $port, + ); + } + break; + case "sqlite": + $handler = new SqliteHandler($this->dir->getRoot("database/database.sqlite")); + break; + } + + if($handler) { + $handler->setCharset($this->getenv("DATABASE_CHARSET", "utf8")); + $handler->setPrefix($this->getenv("DATABASE_PREFIX", "")); + $connect = Connect::setHandler($handler); + $connect->execute(); + } } } /** * Setup configs and install env * @return void + * @throws Exception */ protected function setupConfig(): void { @@ -88,12 +117,11 @@ protected function setupConfig(): void $put['config']['routers']['load'] = ["cli"]; unset($put['config']['mysql']); $env->putenvArray($this->attr + $put); - + //$response, $request $this->dispatcher->get("/", function () { - $this->container->get("view")->setIndex(function() { - $out = ""; - $out .= "
"; + $this->container->get("view")->setIndex(function () { + $out = "
"; $out .= "
"; $out .= "
Welcome to MaplePHP
"; $out .= "

Install the application

"; @@ -106,7 +134,7 @@ protected function setupConfig(): void }); } - // Set default envars, which can be used in config files! + // Set default env:ars, which can be used in config files! $env->execute(); $this->attr = array_merge($this->attr, $env->getData()); } @@ -114,26 +142,28 @@ protected function setupConfig(): void /** * Setup Routers * @return void + * @throws EmitterException + * @throws Exception */ protected function setupRouters(): void { if (($config = $this->getConfig("routers"))) { - if ((bool)($config['cache'] ?? false)) { + if ($config['cache'] ?? false) { $dir = $this->getConfigDir($config['cacheFile']['path']); $file = $config['cacheFile']['prefix'] . $config['cacheFile']['file']; - $this->dispatcher->setRouterCacheFile("{$dir}{$file}", false); + $this->dispatcher->setRouterCacheFile("$dir$file", false); } $routerFiles = is_null($this->routerFiles) ? ($config['load'] ?? null) : $this->routerFiles; - $routerFiles = $this->defualtRoutes($routerFiles); + $routerFiles = $this->defaultRoutes($routerFiles); if (is_array($routerFiles)) { foreach ($routerFiles as $file) { if (!in_array($file, $this->exclRouterFiles)) { $dir = $this->dir->getRoot(); - if(strpos($file, "./") === 0) { + if(str_starts_with($file, "./")) { $path = realpath(dirname(__FILE__).substr($file, 1).".php"); } else { - $path = "{$dir}app/Http/Routes/{$file}.php"; + $path = "{$dir}app/Http/Routes/$file.php"; } $this->includeRoutes($this->dispatcher, $path); } @@ -142,17 +172,21 @@ protected function setupRouters(): void } } - protected function defualtRoutes(array $routerFiles) { + protected function defaultRoutes(array $routerFiles): array + { if($this->dispatcher->getMethod() === "CLI") { $routerFiles[] = "./../Cli/Routers/default"; } return $routerFiles; } + /** + * @throws Exception + */ protected function includeRoutes(RouterDispatcher $routes, string $fullPathToFile): void { if (!is_file($fullPathToFile)) { - throw new \Exception("The file \"{$fullPathToFile}\" do not exist. Make sure it is in the right directory!", 1); + throw new Exception("The file \"$fullPathToFile\" do not exist. Make sure it is in the right directory!", 1); } require_once($fullPathToFile); } @@ -170,7 +204,7 @@ protected function setupLang(): void $appLangDir = getenv("APP_LANG_DIR"); $this->setLangDir(($appLangDir) ? $appLangDir : $this->dir->getRoot() . "resources/lang/"); - // Re-set varible, might have changed above + // Re-set variable, might have changed above if ($appLangDir = getenv("APP_LANG_DIR")) { Local::setDir($appLangDir); } @@ -179,6 +213,7 @@ protected function setupLang(): void /** * Setup view * @return void + * @throws Exception */ protected function setupViews(): void { @@ -197,8 +232,10 @@ protected function setupViews(): void } /** - * Setup the dispatcher + * Set up the dispatcher + * @param callable|null $call * @return void + * @throws EmitterException */ protected function setupDispatch(?callable $call = null): void { @@ -207,21 +244,22 @@ protected function setupDispatch(?callable $call = null): void ResponseInterface &$response, RequestInterface $request, UrlInterface|null $url - ) use ($call): ResponseInterface { - switch ($dispatchStatus) { - case RouterDispatcher::NOT_FOUND: - $response = $response->withStatus(404); - break; - case RouterDispatcher::METHOD_NOT_ALLOWED: - $response = $response->withStatus(403); - break; - case RouterDispatcher::FOUND: - $this->defaultInterfaces($response, $request, $url); - if (is_callable($call)) { - $response = $call($response); - } - break; + if ($dispatchStatus) { + switch ($dispatchStatus) { + case RouterDispatcher::NOT_FOUND: + $response = $response->withStatus(404); + break; + case RouterDispatcher::METHOD_NOT_ALLOWED: + $response = $response->withStatus(403); + break; + case RouterDispatcher::FOUND: + $this->defaultInterfaces($response, $request, $url); + if (is_callable($call)) { + $response = $call($response); + } + break; + } } return $response; }); @@ -230,26 +268,23 @@ protected function setupDispatch(?callable $call = null): void /** * Add a class that will where it's instance will be remembered through the app and its * controllers, To do this, you must first create an interface of the class, which will - * become its uniqe identifier. + * become its unique identifier. + * @param $response + * @param $request + * @param $url * @return void */ final protected function defaultInterfaces($response, $request, $url): void { Reflection::interfaceFactory(function ($className) use ($request, &$response, $url) { - switch ($className) { - case "UrlInterface": - return $url; - case "DirInterface": - return $this->dir; - case "ContainerInterface": - return $this->container; - case "RequestInterface": - return $request; - case "ResponseInterface": - return $response; - default: - return null; - } + return match ($className) { + "UrlInterface" => $url, + "DirInterface" => $this->dir, + "ContainerInterface" => $this->container, + "RequestInterface" => $request, + "ResponseInterface" => $response, + default => null, + }; }); } @@ -260,27 +295,42 @@ final protected function defaultInterfaces($response, $request, $url): void */ protected function setupErrorHandler(): void { - if (getenv("APP_DEBUG")) { - if (!is_null($this->whoops) || (class_exists('\Whoops\Run') && !is_null($this->errorHandler))) { - $class = "\\Whoops\\Handler\\{$this->errorHandler}"; - if (is_null($this->whoops)) { - $this->whoops = new \Whoops\Run(); - $this->whoops->pushHandler($this->getWhoopsHandler($class)); - $this->whoops->register(); - } else { - if (!$this->hasWhoopsHandler($class)) { - $this->whoops->pushHandler($this->getWhoopsHandler($class)); - $this->whoops->register(); - } + if (getenv("APP_DEBUG_DISPLAY") || getenv("APP_DEBUG_LOG")) { + $errorHandler = "\\MaplePHP\\Blunder\\Handlers\\$this->errorHandler"; + $errorHandler = (getenv("APP_DEBUG_DISPLAY") ? $this->getBlunderHandler($errorHandler) : new SilentHandler()); + $run = new Run($errorHandler); + /* + $run->severity() + ->excludeSeverityLevels([E_WARNING, E_USER_WARNING]) + ->redirectTo(function ($errNo, $errStr, $errFile, $errLine) { + return new JsonHandler(); + }); + */ + $run->event(function ($item, $http) { + if(getenv("APP_DEBUG_LOG")) { + $log = new Logger(new StreamHandler($this->dir->getLogs("error.log"), StreamHandler::MAX_SIZE, StreamHandler::MAX_COUNT)); + call_user_func_array([$log, $item->getStatus()], [ + $item->getMessage(), + [ + 'flag' => $item->getSeverity(), + 'file' => $item->getFile(), + 'line' => $item->getLine() + ] + ]); } - } else { - $this->emitter->errorHandler(true, true, true, $this->dir->getRoot() . "storage/logs/error.log"); - } + }); + $run->load(); + + //echo $wqdwq; + //$date = strftime("%e %B %Y", strtotime('2010-01-08')); + //trigger_error("Test", E_USER_DEPRECATED); + //die("ww"); } } /** * Set response headers + * @param $response * @return ResponseInterface */ protected function setupHeaders($response): ResponseInterface @@ -300,9 +350,12 @@ protected function setupHeaders($response): ResponseInterface } /** - * Run the application - * @return void - */ + * Run the application + * @return void + * @throws ConnectException + * @throws EmitterException + * @throws Exception + */ public function run(): void { $this->setupConfig(); @@ -315,7 +368,7 @@ public function run(): void $this->setupDispatch(function ($response) { return $this->setupHeaders($response); }); - + $response = $this->dispatcher->response(); $request = $this->dispatcher->request(); @@ -334,9 +387,9 @@ public function run(): void } if (!($response instanceof ResponseInterface)) { - throw new \Exception("Fatal error: The apps ResponseInterface has not been initilized!", 1); + throw new Exception("Fatal error: The apps ResponseInterface has not been initialized!", 1); } - + $type = $response->getHeaderLineData("content-type"); switch (($type[0] ?? "text/html")) { case "text/html": @@ -349,16 +402,14 @@ public function run(): void $this->enablePlainErrorHandler(); } - // Handle error response IF contentType has changed! - $this->setupErrorHandler(); + //$this->setupErrorHandler(); - // If you set a buffered response string it will get priorities agains all outher response + // If you set a buffered response string it will get priorities against all other response $this->emitter->outputBuffer($this->dispatcher->getBufferedResponse()); $this->emitter->run($response, $request); } - private function htmlDocPlaceholder(): string { return ' diff --git a/Kernel/AppConfigs.php b/Kernel/AppConfigs.php index 4dc7bef..98d92da 100755 --- a/Kernel/AppConfigs.php +++ b/Kernel/AppConfigs.php @@ -18,14 +18,14 @@ class AppConfigs protected $dir; - protected $attr = array(); + protected $attr = []; protected $container; protected $routerFiles; - protected $exclRouterFiles = array(); + protected $exclRouterFiles = []; protected $hasTempEngine = true; protected $hasDBEngine = true; protected $errorHandler; - protected $whoopsHandler; + protected $blunderHandler; /** * Get global ENV @@ -33,7 +33,7 @@ class AppConfigs * @param string|null $fallback Specify possible fallback * @return mixed */ - protected function getenv(string $key, string $fallback = null) + protected function getenv(string $key, ?string $fallback = null) { return ($this->attr[$key] ?? $fallback); } @@ -103,9 +103,9 @@ final protected function requireConfigFile(string $file): array * @param string $class * @return boolean */ - protected function hasWhoopsHandler(string $class): bool + protected function hasBlunderHandler(string $class): bool { - return (isset($this->whoopsHandler[$class])); + return (isset($this->blunderHandler[$class])); } /** @@ -114,12 +114,12 @@ protected function hasWhoopsHandler(string $class): bool * @param string $class handler * @return object */ - protected function getWhoopsHandler(string $class): object + protected function getBlunderHandler(string $class): object { - if (!$this->hasWhoopsHandler($class)) { - $this->whoopsHandler[$class] = new $class(); + if (!$this->hasBlunderHandler($class)) { + $this->blunderHandler[$class] = new $class(); } - return $this->whoopsHandler[$class]; + return $this->blunderHandler[$class]; } /** @@ -195,7 +195,7 @@ public function setLangDir(string $dir): void */ public function enablePrettyErrorHandler(): void { - $this->errorHandler = "PrettyPageHandler"; + $this->errorHandler = "HtmlHandler"; } /** @@ -204,7 +204,7 @@ public function enablePrettyErrorHandler(): void */ public function enableJsonErrorHandler(): void { - $this->errorHandler = "JsonResponseHandler"; + $this->errorHandler = "JsonHandler"; } /** @@ -215,4 +215,13 @@ public function enablePlainErrorHandler(): void { $this->errorHandler = "PlainTextHandler"; } + + /** + * Enables the plain text error handler + * @return void + */ + public function enableCliErrorHandler(): void + { + $this->errorHandler = "CliHandler"; + } } diff --git a/Kernel/AttributeSetter.php b/Kernel/AttributeSetter.php new file mode 100755 index 0000000..9a13459 --- /dev/null +++ b/Kernel/AttributeSetter.php @@ -0,0 +1,23 @@ +value = $value; + } + + public function get(): string + { + return $this->value; + } +} diff --git a/Kernel/Kernel.php b/Kernel/Kernel.php index 8043d8b..171e983 100755 --- a/Kernel/Kernel.php +++ b/Kernel/Kernel.php @@ -4,6 +4,7 @@ namespace MaplePHP\Foundation\Kernel; +use MaplePHP\DTO\Format\Arr; use MaplePHP\Foundation\Kernel\App; use MaplePHP\Http; use MaplePHP\Handler; @@ -11,7 +12,6 @@ class Kernel { - private $dir; private $rootDir; private $stream; @@ -38,7 +38,8 @@ public function __construct(string $dir, ?string $rootDir = null) $this->init(); } - public function getRequest() { + public function getRequest() + { return $this->request; } @@ -49,7 +50,7 @@ private function init(): void $this->emitter = new Handler\Emitter($this->container); $this->app = new App($this->emitter, $this->routes); } - + public function run(?string $path = null): void { $this->app->enablePrettyErrorHandler(); @@ -78,6 +79,13 @@ public function run(?string $path = null): void */ public function runCli(array $argv): void { + $formatArr = new Arr($argv); + $argv = $formatArr->arrayItemExpMerge(":")->get(); + + if(in_array("--help", $argv)) { + $argv = array_merge(array_slice($argv, 0, 2), ['help']); + } + $this->request = new Http\ServerRequest(new Http\Uri($this->env->getUriParts([ "dir" => $this->dir, "rootDir" => $this->rootDir, @@ -86,11 +94,13 @@ public function runCli(array $argv): void $this->init(); - $this->app->enableJsonErrorHandler(); + $this->app->enableCliErrorHandler(); $this->app->setContainer($this->container); $this->app->setRouterFiles(["cli"]); $this->app->enableTemplateEngine(false); - if(($argv[1] ?? NULL) === "config") $this->app->enableDatabaseEngine(false); + if(($argv[1] ?? null) === "config") { + $this->app->enableDatabaseEngine(false); + } // bool $displayError, bool $niceError, bool $logError, string $logErrorFile //$emitter->errorHandler(false, false, true, "/var/www/html/systems/logger-cli.txt"); @@ -99,7 +109,7 @@ public function runCli(array $argv): void $this->routes->setRequestMethod("CLI"); $this->routes->setDispatchPath($this->request->getCliKeyword()); $this->app->run(); - + } } diff --git a/Log/StreamLogger.php b/Log/StreamLogger.php index 633c049..d075140 100755 --- a/Log/StreamLogger.php +++ b/Log/StreamLogger.php @@ -12,8 +12,8 @@ class StreamLogger public const MAX_SIZE = 5000; //KB public const MAX_COUNT = 10; - private $handler; - private $logger; + private StreamHandler $handler; + private ?Logger $logger = null; public function __construct(DirInterface $dir) { @@ -44,5 +44,5 @@ public function __call($method, $args): mixed } } - + } diff --git a/Mail/PHPMailerTest.php b/Mail/PHPMailerTest.php index f858ac2..491779d 100755 --- a/Mail/PHPMailerTest.php +++ b/Mail/PHPMailerTest.php @@ -7,7 +7,6 @@ class PHPMailerTest implements EventInterface { - private $provider; private $output = []; @@ -15,7 +14,7 @@ public function __construct(Provider $provider) { $this->provider = $provider; } - + public function resolve(): void { echo $this->provider->logger()->getMessage()."
"; diff --git a/Media/Resize.php b/Media/Resize.php index f8eaf6d..b3263ec 100755 --- a/Media/Resize.php +++ b/Media/Resize.php @@ -130,7 +130,7 @@ public function setResolution(int $xRes, int $yRes): self */ public function webCompression() { - $this->imagick->setSamplingFactors(array('2x2', '1x1', '1x1')); + $this->imagick->setSamplingFactors(['2x2', '1x1', '1x1']); if ($this->info['mime'] === "image/jpeg") { $this->imagick->setInterlaceScheme(Imagick::INTERLACE_PLANE); } @@ -305,7 +305,7 @@ public function autoRotate() public function frames(callable $call, ?array $match = null) { $this->frameCount = 0; - $this->frames = array(); + $this->frames = []; if (is_null($match) || in_array($this->mime, $match)) { $imagick = $this->imagick->coalesceImages(); foreach ($imagick as $frame) { @@ -487,7 +487,7 @@ public function getAverageColour($asHexStr = true) if (count($pixels) === 0) { throw new Exception("Unexpected error: Could not find ant colors in image", 1); } - + $pixel = reset($pixels); $rgb = $pixel->getColor(); //getcolor diff --git a/Migrate/Migration.php b/Migrate/Migration.php index cc4d9a7..07a0a29 100755 --- a/Migrate/Migration.php +++ b/Migrate/Migration.php @@ -15,18 +15,20 @@ class Migration public function __construct(?RequestInterface $request = null) { - if (!is_null($request)) { + /* + if (!is_null($request)) { $this->args = $request->getCliArgs(); $this->setMigration(ucfirst($this->args['table'] ?? "")); } + */ } public function setMigration(string $table): void { $this->table = $table; - $this->migName = $getMigClass = "MaplePHP\\Foundation\\Migrate\\Tables\\{$this->table}"; - if (class_exists($getMigClass)) { - $this->mig = new $getMigClass(); + $this->migName = $getMigClass = $table; + if (class_exists($table)) { + $this->mig = new $table(); } } @@ -59,7 +61,7 @@ public function hasColumn(string $check): bool return isset($whiteList[$check]); } - public function hasColumns(array $check, &$missing = array()): bool + public function hasColumns(array $check, &$missing = []): bool { if (!$this->hasMigration()) { return false; diff --git a/Migrate/Tables/Logger.php b/Migrate/Tables/Logger.php index 8bf560c..2fda7eb 100755 --- a/Migrate/Tables/Logger.php +++ b/Migrate/Tables/Logger.php @@ -11,7 +11,7 @@ public function __construct() parent::__construct("logger"); } - protected function buildTable(): void + protected function migrateTable(): void { $this->mig->column("id", [ "type" => "int", diff --git a/Migrate/Tables/Login.php b/Migrate/Tables/Login.php index 5b430b5..99f25a9 100755 --- a/Migrate/Tables/Login.php +++ b/Migrate/Tables/Login.php @@ -11,7 +11,7 @@ public function __construct() parent::__construct("login"); } - protected function buildTable(): void + protected function migrateTable(): void { $this->mig->column("login_id", [ diff --git a/Migrate/Tables/Organizations.php b/Migrate/Tables/Organizations.php index 0e033e0..a2556fb 100755 --- a/Migrate/Tables/Organizations.php +++ b/Migrate/Tables/Organizations.php @@ -11,7 +11,7 @@ public function __construct() parent::__construct("organizations"); } - protected function buildTable(): void + protected function migrateTable(): void { $this->mig->column("org_id", [ "type" => "int", diff --git a/Migrate/Tables/Users.php b/Migrate/Tables/Users.php index 2cea603..ef4951b 100755 --- a/Migrate/Tables/Users.php +++ b/Migrate/Tables/Users.php @@ -11,7 +11,7 @@ public function __construct() parent::__construct("users"); } - protected function buildTable(): void + protected function migrateTable(): void { $this->mig->column("id", [ "type" => "int", diff --git a/Migrate/Tables/UsersToken.php b/Migrate/Tables/UsersToken.php index a99a60b..243f644 100755 --- a/Migrate/Tables/UsersToken.php +++ b/Migrate/Tables/UsersToken.php @@ -11,7 +11,7 @@ public function __construct() parent::__construct("users_token"); } - protected function buildTable(): void + protected function migrateTable(): void { // If you want to add multiple primary keys diff --git a/Nav/Middleware/Navigation.php b/Nav/Middleware/Navigation.php index f1fe2ab..fffcf01 100755 --- a/Nav/Middleware/Navigation.php +++ b/Nav/Middleware/Navigation.php @@ -21,27 +21,21 @@ public function __construct(Provider $provider, Navbar $nav) /** * Before controllers - * @param ResponseInterface $response - * @param RequestInterface $request * @return void */ - public function before(ResponseInterface $response, RequestInterface $request) + public function before() { // Set navigate view partial $this->provider->view()->setPartial("navigation.!document/navigation|!navigation", [ "nav" => $this->nav->get() ]); - - return $response; } /** * After controllers - * @param ResponseInterface $response - * @param RequestInterface $request * @return void */ - public function after(ResponseInterface $response, RequestInterface $request) + public function after() { } } diff --git a/Nav/Navbar.php b/Nav/Navbar.php index 7b11f73..4564b69 100755 --- a/Nav/Navbar.php +++ b/Nav/Navbar.php @@ -10,8 +10,8 @@ class Navbar { private $builder; - private $items = array(); - private $envItems = array(); + private $items = []; + private $envItems = []; private $protocol; private $provider; private $config = [ @@ -44,8 +44,9 @@ public function __construct(Provider $provider) * @param string $key Config key * @param mixed $value Config value */ - public function setConfig(string $key, mixed $value) { - if(empty($this->config[key])) { + public function setConfig(string $key, mixed $value) + { + if(empty($this->config[$key])) { $configs = array_keys($this->config); throw new InvalidArgumentException("Config {$key} does not exists, choose from (".implode(", ", $configs).").", 1); } @@ -85,9 +86,9 @@ public function addItem(string $navName, array $arr): self */ private function items(): array { - $items = array(); + $items = []; $arr = array_merge($this->envItems, $this->items); - + foreach ($arr as $menuID => $data) { foreach ($data as $key => $item) { //$pos = ($item['position'] ?? 0); @@ -118,6 +119,7 @@ function ($obj, $li, $active, $level, $id, $parent) { $topItem = ($parent === 0) ? " top-item" : ""; $li->attr("class", "item{$hasChild}{$topItem}{$active}"); + // Create link $li->create("a", $obj->name) ->attr("href", $this->provider->url()->getRoot($obj->uri)) diff --git a/Pollyfill/functions.php b/Polyfill/functions.php similarity index 68% rename from Pollyfill/functions.php rename to Polyfill/functions.php index b5674cb..2394281 100755 --- a/Pollyfill/functions.php +++ b/Polyfill/functions.php @@ -13,7 +13,7 @@ * @return 0 if the two operands are equal, 1 if the num1 is larger than the num2, -1 otherwise. */ if (!function_exists("bccomp")) { - function bccomp(float $number1, float $number2, int $scale = 0) + function bccomp(float $number1, float $number2, int $scale = 0): int { $num1 = (float)number_format($number1, $scale, "", ""); $num2 = (float)number_format($number2, $scale, "", ""); @@ -25,4 +25,18 @@ function bccomp(float $number1, float $number2, int $scale = 0) } return 0; } -} \ No newline at end of file +} + +if(!function_exists('fnmatch')) { + function fnmatch($pattern, $string): bool|int + { + return preg_match("#^".strtr(preg_quote($pattern, '#'), ['\*' => '.*', '\?' => '.'])."$#i", $string); + } +} + +if(!function_exists('mb_substr')) { + function mb_substr(string $string, int $start, ?int $length, ?string $encoding): string + { + return ""; + } +} diff --git a/Profiling/Middleware/Profiling.php b/Profiling/Middleware/Profiling.php index d4a07cc..30326ae 100755 --- a/Profiling/Middleware/Profiling.php +++ b/Profiling/Middleware/Profiling.php @@ -9,7 +9,6 @@ class Profiling implements MiddlewareInterface { - public function __construct() { } diff --git a/Security/Hash.php b/Security/Hash.php index 63cb7f8..bb7f651 100755 --- a/Security/Hash.php +++ b/Security/Hash.php @@ -41,7 +41,7 @@ public function passwordVerify(string $hashedValue): bool */ public function hash(int $cost = 8): string { - return password_hash($this->value, PASSWORD_BCRYPT, array("cost" => $cost)); + return password_hash($this->value, PASSWORD_BCRYPT, ["cost" => $cost]); } // Same as above (use the one you rember) diff --git a/Security/Session.php b/Security/Session.php index b814d9b..6be1cbe 100755 --- a/Security/Session.php +++ b/Security/Session.php @@ -1,4 +1,5 @@