diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b226a1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Kiaan + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c57e4b3 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +## About + +Kiaan is a web application framework for PHP. + +Kiaan is easy, flexible and professional. + +## Security Vulnerabilities + +If you discover a security vulnerability within Kiaan, please send an e-mail to Hassan Kerdash via [kerdashhassan@gmail.com](mailto:kerdashhassan@gmail.com). + +## License + +The Kiaan framework is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT). diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..507304d --- /dev/null +++ b/composer.json @@ -0,0 +1,28 @@ +{ + "name": "kiaanphp/framework", + "description": "Kiaan PHP framework", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Hassan Kerdash", + "email": "kerdashhassan@gmail.com" + } + ], + "minimum-stability": "stable", + "require": { + "ext-mbstring":"*", + "ext-json": "*" + }, + "suggest": { + "ext-intl": "Intl extension is useful for validating formatted numbers" + }, + "autoload" : { + "psr-4" : { + "Kiaan\\" : "src/" + }, + "files" : [ + "src/Helpers/helper.php" + ] + } +} diff --git a/src/App.php b/src/App.php new file mode 100644 index 0000000..f724511 --- /dev/null +++ b/src/App.php @@ -0,0 +1,41 @@ +prepare_path(($this->config())->helpers->path).'.php'; + } + + /** + * Get root of app + * + * @param string $path + * @return string $path + */ + protected function root($path='') { + if (PHP_SAPI != 'cli'){ + $script_filename = str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']); + $script_filename = explode("/", $script_filename); + $script_filename = $script_filename[count($script_filename)-1]; + + $script_folder = trim(preg_replace('/' . $script_filename . '/', '/', $_SERVER['SCRIPT_FILENAME'], 1), '/'); + + $path = $script_folder . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + }else{ + $path = getcwd() . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + } + } + + /** + * Prepare path + * + */ + protected function prepare_path($path) + { + return str_replace(['/', '//', '.'], '\\', $path); + } + + /* + * Services + * + */ + public function services() + { + // Framework name + $framework_name = self::$framework_name; + + // Framework name + $framework_version = self::$framework_version; + + /** + * Settings file name + **/ + $settings_file_name = self::$settings_file_name; + + // Script file name + $script_filename = str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']); + $script_filename = explode("/", $script_filename); + $script_filename = $script_filename[count($script_filename)-1]; + + // Script Folder + $script_folder = trim(preg_replace('/' . $script_filename . '/', '/', $_SERVER['SCRIPT_FILENAME'], 1), '/'); + + return (object) [ + "framework_name" => $framework_name, + "framework_version" => $framework_version, + "script_filename" => $script_filename, + "script_folder" => $script_folder, + "settings_file_name" => $settings_file_name + ]; + } + + /* + * Framework + * + */ + public function framework() + { + // Framework name + $framework_name = self::$framework_name; + + // Framework name + $framework_version = self::$framework_version; + + // Script file name + $script_filename = explode("/", $_SERVER['SCRIPT_FILENAME']); + $script_filename = $script_filename[count($script_filename)-1]; + + // Script Folder + $script_folder = trim(preg_replace('/' . $script_filename . '/', '/', $_SERVER['SCRIPT_FILENAME'], 1), '/'); + + // Script Folder Name + $script_folder_name = explode("/", $script_folder); + $script_folder_name = $script_folder_name[count($script_folder_name)-1]; + + // Script Folder Path + $script_folder_path = $script_folder; + + return (object) [ + "framework" => (object) [ + "name" => $framework_name, + "version" => $framework_version, + ], + "folder" => (object) [ + "name" => $script_folder_name, + "path" => $script_folder_path, + ], + ]; + } + + /* + * Settings + * + * Get Settings + */ + public function settings() + { + $config = (object) include(self::$settings_file_name); + $result = json_decode(json_encode($config), false); + return is_object($result) ? $result : null; + } + + /* + * Config + * + * Get Configurations + */ + public function config() + { + $path = $this->prepare_path(($this->settings())->configuration->path).'.php'; + + $data = include($path); + $data = json_decode(json_encode($data)); + + return $data; + } + +} \ No newline at end of file diff --git a/src/Application/Application.php b/src/Application/Application.php new file mode 100644 index 0000000..3d9e3ec --- /dev/null +++ b/src/Application/Application.php @@ -0,0 +1,36 @@ +helpers()); + } + + /* + * Run framework + */ + public static function run() + { + App::run(); + } + +} \ No newline at end of file diff --git a/src/Application/Interfaces/Application.php b/src/Application/Interfaces/Application.php new file mode 100644 index 0000000..35b1dcb --- /dev/null +++ b/src/Application/Interfaces/Application.php @@ -0,0 +1,30 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + CommandLineinterface::menu([ + 'create : Create configuration file.' + ]); + } + + /** + * Create + * + **/ + public function create($name) + { + // File + $file = __DIR__ . '/Generation/Cli.txt'; + + // Replace variable + $content = str_replace( + array(":namespace:", ":class:", ":method-name:", ":method:"), + array(CommandLineinterface::getNamespace(), $name, ucfirst(CommandLineinterface::getMethod()), CommandLineinterface::getMethod()), + file_get_contents($file) + ); + // File name + $file_name = CommandLineinterface::getPath(). "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + CommandLineinterface::success(("Done, create '$name' Command line interface file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Config.php b/src/Application/Resources/CLICommands/Config.php new file mode 100644 index 0000000..6083768 --- /dev/null +++ b/src/Application/Resources/CLICommands/Config.php @@ -0,0 +1,75 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'create : Create configuration file.' + ]); + } + + /** + * Create + * + **/ + public function create($name) + { + //File + $file = __DIR__ . '/Generation/Config.txt'; + + // Content + $content = file_get_contents($file); + + // File name + $file_name = ConfigClass::getPath() . "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' configuration file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Controller.php b/src/Application/Resources/CLICommands/Controller.php new file mode 100644 index 0000000..61c88fc --- /dev/null +++ b/src/Application/Resources/CLICommands/Controller.php @@ -0,0 +1,88 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'create : Create controller file.' + ]); + } + + /** + * Create + * + **/ + public function create($name, $type='controller') + { + //File + switch ($type) { + case 'controller': + $file = __DIR__ . '/Generation/Controller.txt'; + break; + case 'crud': + $file = __DIR__ . '/Generation/Controller-Crud.txt'; + break; + case 'api': + $file = __DIR__ . '/Generation/Controller-Api.txt'; + break; + } + + // Replace variable + $content = str_replace( + array(":namespace:", ":class:", ":method-name:", ":method:"), + array(ControllerClass::getNamespace(), $name, ucfirst(ControllerClass::getDefaultMethod()), ControllerClass::getDefaultMethod()), + file_get_contents($file) + ); + // File name + $file_name = ControllerClass::getPath(). "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' controller file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Event.php b/src/Application/Resources/CLICommands/Event.php new file mode 100644 index 0000000..f6f9259 --- /dev/null +++ b/src/Application/Resources/CLICommands/Event.php @@ -0,0 +1,77 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu(['create : create event class file.']); + } + + /** + * Create + * + **/ + public function create($name) + { + //File + $file = __DIR__ . '/Generation/Event.txt'; + + // Replace variable + $content = str_replace( + array(":class:"), + array($name), + file_get_contents($file) + ); + + // File name + $file_name = Events::getPath() . "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' event file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Generation/Cli.txt b/src/Application/Resources/CLICommands/Generation/Cli.txt new file mode 100644 index 0000000..cba489a --- /dev/null +++ b/src/Application/Resources/CLICommands/Generation/Cli.txt @@ -0,0 +1,55 @@ + "menu_handle", + "test" => "test action" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu(["test"]); + } + + /** + * Test action + * + **/ + public function test() + { + echo "Hello_World"; + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Generation/Config.txt b/src/Application/Resources/CLICommands/Generation/Config.txt new file mode 100644 index 0000000..2cdc8d4 --- /dev/null +++ b/src/Application/Resources/CLICommands/Generation/Config.txt @@ -0,0 +1,10 @@ +id() + ->submit(); + } + + /** + * Rollback + * + **/ + public function rollback() + { + Schema::deleteTable('table'); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Generation/Model.txt b/src/Application/Resources/CLICommands/Generation/Model.txt new file mode 100644 index 0000000..8613109 --- /dev/null +++ b/src/Application/Resources/CLICommands/Generation/Model.txt @@ -0,0 +1,31 @@ +insert([ + // ... + ]); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Generation/Trans.txt b/src/Application/Resources/CLICommands/Generation/Trans.txt new file mode 100644 index 0000000..b3893f4 --- /dev/null +++ b/src/Application/Resources/CLICommands/Generation/Trans.txt @@ -0,0 +1,10 @@ +requireParameters($this->fillableParams); + + // Getting parameters + // $data = $this->parameter('data'); + + // Check + // ... + } +} diff --git a/src/Application/Resources/CLICommands/Middleware.php b/src/Application/Resources/CLICommands/Middleware.php new file mode 100644 index 0000000..3720407 --- /dev/null +++ b/src/Application/Resources/CLICommands/Middleware.php @@ -0,0 +1,78 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'create : Create middleware file.' + ]); + } + + /** + * Create + * + **/ + public function create($name) + { + //File + $file = __DIR__ . '/Generation/Middleware.txt'; + + // Replace variable + $content = str_replace( + array(":namespace:", ":class:", ":method:"), + array(MiddlewareClass::getNamespace(), $name, MiddlewareClass::getmethod()), + file_get_contents($file) + ); + // File name + $file_name = MiddlewareClass::getPath(). "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' middleware file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Migration.php b/src/Application/Resources/CLICommands/Migration.php new file mode 100644 index 0000000..bce1ba4 --- /dev/null +++ b/src/Application/Resources/CLICommands/Migration.php @@ -0,0 +1,138 @@ + "menu_handle", + "create" => "create", + "run" => "run", + "all" => "all", + "rollback" => "rollback", + "fresh" => "fresh" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'create : Create migration file.', + 'run : Run migration file.', + 'all : Migration all.', + 'rollback : Run rollback for file.', + 'fresh : Run rollback for all files.' + ]); + } + + /** + * Create + * + **/ + public function create($name) + { + //File + $file = __DIR__ . '/Generation/Migrate.txt'; + + // Replace variable + $content = str_replace( + array(":namespace:", ":class:"), + array(Schema::getMigration()->namespace, $name), + file_get_contents($file) + ); + // File name + $file_name = Schema::getMigration()->path . "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' migration file.")); + } + + /** + * Run + * + **/ + public function run($class) + { + // Run + Schema::runMigrate($class); + + // Success + Cli::success(("Done, '$class' is migrated.")); + } + + /** + * All + * + **/ + public function all() + { + // Run + Schema::runMigrate(); + + // Success + Cli::success(("Done, all migrated.")); + } + + /** + * Rollback + * + **/ + public function rollback($class) + { + // Run + Schema::runRollback($class); + + // Success + Cli::success(("Done, '$class' is rollback.")); + } + + /** + * Fresh + * + **/ + public function fresh() + { + // Run + Schema::runRollback(); + + // Success + Cli::success(("Done, all rollback.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Model.php b/src/Application/Resources/CLICommands/Model.php new file mode 100644 index 0000000..4e0a1eb --- /dev/null +++ b/src/Application/Resources/CLICommands/Model.php @@ -0,0 +1,80 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu(['create']); + } + + /** + * Create + * + **/ + public function create($name, $table='') + { + // Table + $table = (empty($table)) ? $name : $table; + + //File + $file = __DIR__ . '/Generation/Model.txt'; + + // Replace variable + $content = str_replace( + array(":namespace:", ":class:", ":table:"), + array(DB::getModel()->namespace, $name, $table), + file_get_contents($file) + ); + + // File name + $file_name = DB::getModel()->path . "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' model file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Plugin.php b/src/Application/Resources/CLICommands/Plugin.php new file mode 100644 index 0000000..7fd6459 --- /dev/null +++ b/src/Application/Resources/CLICommands/Plugin.php @@ -0,0 +1,358 @@ + "menu_handle", + "run" => "run", + "discovery" => "discovery", + "update" => "update", + "create" => "create", + "command" => "command" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'run : Run plugins', + 'update : Update plugins', + 'discovery : Run auto discovery', + 'create : Create new plugin.', + 'submit : Submit plugin.' + ]); + } + + /** + * Get root of app + * + * @param string $path + * @return string $path + */ + protected function root($path='') { + if (PHP_SAPI != 'cli'){ + $script_filename = str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']); + $script_filename = explode("/", $script_filename); + $script_filename = $script_filename[count($script_filename)-1]; + + $script_folder = trim(preg_replace('/' . $script_filename . '/', '/', $_SERVER['SCRIPT_FILENAME'], 1), '/'); + + $path = $script_folder . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + }else{ + $path = getcwd() . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + } + } + + /** + * Run + * + **/ + public function run() + { + shell_exec("composer dumpautoload"); + myPlugin::discovery(); + + Cli::success("Done."); + } + + /** + * Update + * + **/ + public function update() + { + shell_exec("composer update"); + myPlugin::discovery(); + + Cli::success("Done."); + } + + /** + * Update + * + **/ + public function discovery() + { + myPlugin::discovery(); + + Cli::success("Done."); + } + + + /** + * Command + * + **/ + public function command($className, $func) + { + // Plugin + return myPlugin::command($className, $func); + } + + /** + * Create + * + **/ + public function create($vendor, $className) + { + $myVendor = $vendor; + $myClassName = $className; + $vendor = strtolower($vendor); + $className = strtolower($className); + + $dir = $this->root("vendor\\$vendor\\$className"); + + # README.md + $content = '## About + +Plugin for Kiaan framework. +'; + + // Create file + if ( !file_exists($dir) ) {mkdir($dir, 0744);} chmod($dir, 755); + file_put_contents($dir."/README.md", $content); + + # composer.json + $content = '{ + "name": "'.$vendor.'/'.$className.'", + "description": "Kiaan framework", + "type": "project", + "license": "MIT", + "authors": [ + { + "name": "Hassan kerdash", + "email": "kerdashhassan@gmail.com" + } + ], + "minimum-stability": "stable", + "require": { + "php" : "^7", + "ext-mbstring":"*", + "ext-json": "*" + }, + "suggest": { + "ext-intl": "Intl extension is useful for validating formatted numbers" + }, + "autoload" : { + "psr-4" : { + "'.$myVendor.'\\\\'.$myClassName.'\\\\" : "src/" + }, + "files" : [ + "src/helpers.php" + ] + }, + "extra": { + "framework-services-enable": true, + "framework-services-class": "'.$myVendor.'.'.$myClassName.'.Services", + "framework-services-method": "__Services" + } +} +'; + + // Create file + if ( !file_exists($dir) ) {mkdir($dir, 0744);} chmod($dir, 755); + file_put_contents($dir."/composer.json", $content); + + # Create src folder + if (!is_dir($dir.'\\src')){ + mkdir($dir.'\\src', "0777", true); + }; + +# src/Services.php +$content = ' "version" + ]; + } + + /* + * Version + */ + public function version(){ + echo "1.0"; + } + +} +'; + + // Create file + if ( !file_exists($dir) ) {mkdir($dir, 0744);} chmod($dir, 755); + file_put_contents($dir."/src/Services.php", $content); + +# src/Helpers.php +$content = ' "menu_handle", + "list" => "list" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'list : List of routes.' + ]); + } + + /** + * List + * + **/ + public function list() + { + print_r(Router::list()); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Seed.php b/src/Application/Resources/CLICommands/Seed.php new file mode 100644 index 0000000..2f59c74 --- /dev/null +++ b/src/Application/Resources/CLICommands/Seed.php @@ -0,0 +1,109 @@ + "menu_handle", + "create" => "create", + "run" => "run", + "all" => "all" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'create : Create seeding file.', + 'run : Run seed file.', + 'all : Seed all.' + ]); + } + + /** + * Create + * + **/ + public function create($name) + { + //File + $file = __DIR__ . '/Generation/Seed.txt'; + + // Replace variable + $content = str_replace( + array(":namespace:", ":class:"), + array(Schema::getSeed()->namespace, $name), + file_get_contents($file) + ); + // File name + $file_name = Schema::getSeed()->path . "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' seeding file.")); + } + + /** + * Run + * + **/ + public function run($class) + { + // Run + Schema::runSeeds($class); + + // Success + Cli::success(("Done, '$class' is seeded.")); + } + + /** + * All + * + **/ + public function all() + { + // Run + Schema::runSeeds(); + + // Success + Cli::success(("Done, all seeded.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Server.php b/src/Application/Resources/CLICommands/Server.php new file mode 100644 index 0000000..ebb6704 --- /dev/null +++ b/src/Application/Resources/CLICommands/Server.php @@ -0,0 +1,62 @@ + "menu_handle", + "run" => "run" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'run : Run server.' + ]); + } + + /** + * Run + * + **/ + public function run($host="127.0.0.1", $port="8000") + { + shell_exec("php -S $host:$port"); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Trans.php b/src/Application/Resources/CLICommands/Trans.php new file mode 100644 index 0000000..6343930 --- /dev/null +++ b/src/Application/Resources/CLICommands/Trans.php @@ -0,0 +1,75 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'create : Create validator file.' + ]); + } + + /** + * Create + * + **/ + public function create($name, $lang) + { + //File + $file = __DIR__ . '/Generation/Trans.txt'; + + // Content + $content = file_get_contents($file); + + // File name + $file_name = Translation::getPath() . "/$lang/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' configuration file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/CLICommands/Validator.php b/src/Application/Resources/CLICommands/Validator.php new file mode 100644 index 0000000..8148e37 --- /dev/null +++ b/src/Application/Resources/CLICommands/Validator.php @@ -0,0 +1,79 @@ + "menu_handle", + "create" => "create" + ]; + } + + /** + * Menu handle + * + **/ + public function menu_handle() + { + Cli::menu([ + 'create : Create validator file.' + ]); + } + + /** + * Create + * + **/ + public function create($name) + { + //File + $file = __DIR__ . '/Generation/Validator.txt'; + + // Replace variable + $content = str_replace( + array(":namespace:", ":class:"), + array(Validation::getNamespace(), $name), + file_get_contents($file) + ); + + // File name + $file_name = Validation::getPath() . "/$name.php"; + + file_put_contents($file_name, $content); + + // Success + Cli::success(("Done, create '$name' validator file.")); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/Global/ExtendingTrait.php b/src/Application/Resources/Global/ExtendingTrait.php new file mode 100644 index 0000000..f943563 --- /dev/null +++ b/src/Application/Resources/Global/ExtendingTrait.php @@ -0,0 +1,133 @@ +xCommands[$name] = $command; + } + + /** + * Get list of commands. + * + * + * @return bool + */ + public function listXCommand() + { + return $this->xCommands; + } + + /** + * Checks if Command is registered. + * + * @param string $name + * + * @return bool + */ + public function hasXCommand($name) + { + return isset($this->xCommands[$name]); + } + + /** + * Get object from class. + * + * + * @return bool + */ + public function xThis() + { + return $this; + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @return mixed + * + * @throws \BadMethodCallException + */ + public static function __callStatic($method, $parameters) + { + if (isset(static::$xCommands[$method])) { + if (static::$xCommands[$method] instanceof Closure) { + return call_user_func_array(Closure::bind(static::$xCommands[$method], null, get_called_class()), + $parameters); + } else { + return call_user_func_array(static::$xCommands[$method], $parameters); + } + } + + throw new BadMethodCallException("Method {$method} does not exist."); + } + + /** + * Dynamically handle calls to the class. + * + * @param string $method + * @param array $parameters + * + * @return mixed + * + * @throws \BadMethodCallException + */ + public function __call($method, $parameters) + { + if ($this->hasXCommand($method)) { + if ($this->xCommands[$method] instanceof Closure) { + return call_user_func_array($this->xCommands[$method]->bindTo($this, get_class($this)), $parameters); + } else { + return call_user_func_array($this->xCommands[$method], $parameters); + } + } + + throw new BadMethodCallException("Method {$method} does not exist."); + } + +} \ No newline at end of file diff --git a/src/Application/Resources/Global/FilesystemPathTrait.php b/src/Application/Resources/Global/FilesystemPathTrait.php new file mode 100644 index 0000000..c7c98aa --- /dev/null +++ b/src/Application/Resources/Global/FilesystemPathTrait.php @@ -0,0 +1,96 @@ +filesystemRoot; + } + + return $this->filesystemRoot = $path; + } + + /** + * Filesystem path + * + */ + public function filesystemPath(string $path=null) { + if(is_null($path)){ + return $this->filesystemPath; + } + + return $this->filesystemPath = $path; + } + + /** + * Prepare filesystem root + * + */ + protected function filesystem_preoare($var) { + // Filesystem path + $filesystem = rtrim($var, DIRECTORY_SEPARATOR); + $filesystem = empty($filesystem) ? $filesystem : $filesystem . DIRECTORY_SEPARATOR; + + return $filesystem; + } + + /** + * Prepare filesystem root + * + */ + protected function filesystem_root() { + return $this->filesystem_preoare($this->filesystemRoot); + } + + /** + * Prepare filesystem path + * + */ + protected function filesystem_path() { + return $this->filesystem_preoare($this->filesystemPath); + } + + /** + * Get path + * + */ + public function getPath() { + return $this->filesystemPath; + } + +} \ No newline at end of file diff --git a/src/Application/Services.php b/src/Application/Services.php new file mode 100644 index 0000000..e91db43 --- /dev/null +++ b/src/Application/Services.php @@ -0,0 +1,786 @@ +framework_name; + + // Framework name + $_SERVER['FRAMEWORK_VERSION'] = $services->framework_version; + + // Script file name + $_SERVER['SCRIPT_FILE_NAME'] = $services->script_filename; + + // Script Folder + $_SERVER['SCRIPT_FOLDER'] = $services->script_folder; + } + + /** + * launch framework settings + **/ + public static function launchFrameworkSettings() { + $path = self::root(App::services()->settings_file_name); + + $data = include($path); + $data = json_decode(json_encode($data)); + + return self::$settings = $data; + } + + /** + * launch settings + **/ + public static function launchSettings() { + $path = self::$settings->configuration->path.'.php'; + + $data = include($path); + $data = json_decode(json_encode($data)); + + return self::$config = $data; + } + + /** + * Get root of app + * + * @param string $path + * @return string $path + */ + protected static function root($path='') { + if (PHP_SAPI != 'cli'){ + $script_filename = str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']); + $script_filename = explode("/", $script_filename); + $script_filename = $script_filename[count($script_filename)-1]; + + $script_folder = trim(preg_replace('/' . $script_filename . '/', '/', $_SERVER['SCRIPT_FILENAME'], 1), '/'); + + $path = $script_folder . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + }else{ + $path = getcwd() . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + } + } + + /** + * Get url + * + * @param string $path + * @return string $path + */ + protected static function getUrl($path='') { + $protocol = ($_SERVER['REQUEST_SCHEME'] ?? 'http') . '://'; + $host = $_SERVER['HTTP_HOST'] ?? null; + + $script_name = str_replace('\\', '', dirname($_SERVER['SCRIPT_NAME'])); + $script_name = str_replace('/public', '', $script_name).'/'.$path; + + return $protocol . $host . $script_name; + } + + /** + * Url + **/ + public static function url() { + Url::setPublicPath(self::$config->fileSystem->public); + } + + /** + * File + **/ + public static function file() { + new File(); + File::filesystemRoot(self::root()); + } + + /** + * Folder + **/ + public static function folder() { + Folder::filesystemRoot(self::root()); + } + + /** + * Image + **/ + public static function img() { + Img::filesystemRoot(self::root()); + } + + /** + * Session start + **/ + public static function session() { + if (!session_id()) { + ini_set('session.use_only_cookies', 1); + session_start(); + } + } + + /** + * Config + **/ + public static function Config() { + // Config + new Config(); + Config::filesystemRoot(self::root()); + Config::filesystemPath(self::root(self::$config->configuration->path)); + + // String + $string = [ + 'driver' => self::$config->db->driver, + 'host' => self::$config->db->host, + 'db' => self::$config->db->db, + 'user' => self::$config->db->user, + 'pass' => self::$config->db->pass, + 'port' => self::$config->db->port, + ]; + + // Database + $pdo = pdoDB::connection(); + + // Config + Config::setPdo($pdo); + Config::setTable(self::$config->configuration->database->table); + } + + /** + * Trans + **/ + public static function Trans() { + Trans::setLocal(self::$config->languages->defaultLang); + Trans::filesystemRoot(self::root()); + Trans::filesystemPath(self::root(self::$config->languages->path)); + } + + /** + * View + **/ + public static function view() { + // Views folder + new View(); + + // Path + View::setPath(self::$config->views->path); + + // Roots path + View::setRootsPath( + self::root(""), + self::$config->fileSystem->public + ); + + // Method + $method = self::$config->security->method->input; + $method = (empty($method)) ? '_method' : $method ; + View::setMethod($method); + + // CSRF + View::setCsrf( + self::$config->security->csrf->input ?? '_csrf', + Csrf::get() + ); + + // Classes + View::classes([ + "App" => "\Kiaan\App", + "Auth" => "\Kiaan\Auth", + "Classes" => "\Kiaan\Classes", + "Cli" => "\Kiaan\Cli", + "Collection" => "\Kiaan\Collection", + "Config" => "\Kiaan\Config", + "Controller" => "\Kiaan\Controller", + "Cookie" => "\Kiaan\Cookie", + "Cors" => "\Kiaan\Cors", + "Crawl" => "\Kiaan\Crawl", + "Crypt" => "\Kiaan\Crypt", + "Csrf" => "\Kiaan\Csrf", + "DB" => "\Kiaan\DB", + "Debugger" => "\Kiaan\Debugger", + "Env" => "\Kiaan\Env", + "Facade" => "\Kiaan\Facade", + "File" => "\Kiaan\File", + "Folder" => "\Kiaan\Folder", + "Func" => "\Kiaan\Func", + "Http" => "\Kiaan\Http", + "Img" => "\Kiaan\Img", + "Input" => "\Kiaan\Input", + "Ip" => "\Kiaan\Ip", + "Mail" => "\Kiaan\Mail", + "Math" => "\Kiaan\Math", + "Middleware" => "\Kiaan\Middleware", + "Model" => "\Kiaan\Model", + "Number" => "\Kiaan\Number", + "Obfuscate" => "\Kiaan\Obfuscate", + "Password" => "\Kiaan\Password", + "PdoDB" => "\Kiaan\PdoDB", + "Plugin" => "\Kiaan\Plugin", + "Random" => "\Kiaan\Random", + "Request" => "\Kiaan\Request", + "Response" => "\Kiaan\Response", + "Route" => "\Kiaan\Route", + "Schema" => "\Kiaan\Schema", + "Session" => "\Kiaan\Session", + "Str" => "\Kiaan\Str", + "Time" => "\Kiaan\Time", + "Trans" => "\Kiaan\Trans", + "Url" => "\Kiaan\Url", + "Validation" => "\Kiaan\Validation", + "Validator" => "\Kiaan\Validator", + "Variable" => "\Kiaan\Variable", + "View" => "\Kiaan\View", + "Event" => "\Kiaan\Event" + ]); + } + + public static function env() { + // Env + new Env(); + + // FileSystem + Env::filesystemRoot(self::root()); + + // Load + Env::load() + ->file(self::$settings->env->file) + ->submit(); + } + + /** + * Database + **/ + public static function database() { + // String + if(self::$config->db->driver=='sqlite'){ + self::$config->db->host = self::$config->db->path.DIRECTORY_SEPARATOR.self::$config->db->db; + } + + $string = [ + 'driver' => self::$config->db->driver, + 'host' => self::$config->db->host, + 'db' => self::$config->db->db, + 'user' => self::$config->db->user, + 'pass' => self::$config->db->pass, + 'port' => self::$config->db->port, + ]; + + //Pdo connect + if(self::$config->db->connect === true || trim(self::$config->db->connect == 'true')){ + // PDO + new pdoDB(); + + pdoDB::connect( + $string, (self::$config->db->error === true || trim(self::$config->db->error == 'true')) ? true : false + ); + } + + $pdo = pdoDB::connection(); + + // Connect DB + new DB(); + DB::setPdo($pdo); + DB::setPrimaryKey(self::$config->db->models->primaryKey); + DB::setModel(self::$config->db->models->namespace, self::$config->db->models->path); + + // Schema + new Schema(); + Schema::setConnect($pdo); + Schema::setMigration( + self::$config->db->migrations->namespace, + self::$config->db->migrations->table, + ); + Schema::setMigrationPath(self::$config->db->migrations->path); + Schema::setSeed( + self::$config->db->seeds->namespace, + ); + Schema::setSeedPath(self::$config->db->seeds->path); + + // Auth + new Auth(); + Auth::setConnect($pdo); + Auth::setTable(self::$config->db->auth->table); + Auth::setLoginSession(self::$config->db->auth->loginSession); + Auth::setJwtHeader(self::$config->db->auth->jwt->header, self::$config->db->auth->jwt->headerPrefix); + Auth::setFields( + self::$config->db->auth->fields->primaryField, + self::$config->db->auth->fields->idField, + self::$config->db->auth->fields->passField, + self::$config->db->auth->fields->tokenField, + self::$config->db->auth->fields->jwtField + ); + } + + /** + * Mail + **/ + public static function mail() { + new Mail(); + Mail::mailer(self::$config->mail->driver); + Mail::from(self::$config->mail->from->email, self::$config->mail->from->name); + Mail::server(self::$config->mail->host, self::$config->mail->port); + Mail::protocol(self::$config->mail->protocol); + Mail::login(self::$config->mail->username, self::$config->mail->password); + + Mail::filesystemRoot(self::root()); + } + + /** + * launch script + **/ + public static function launchScript() { + $services_script_namespace = self::$settings->services->namespace; + $services_script_method = self::$settings->services->method; + + $handle = new $services_script_namespace; + $handle->{$services_script_method}(); + } + + /** + * Debugger + **/ + public static function debugger() { + // View + self::view(); + + // Debugger + new Debugger; + + // File system root + Debugger::filesystemRoot(self::root()); + + // Pages + foreach (array(404, 500) as $code) { + if(self::$config->debug->{$code}->template && !is_null(self::$config->debug->{$code}->page)){ + Debugger::viewCode(View::html(self::$config->debug->{$code}->page), $code); + }else{ + Debugger::view(self::$config->debug->{$code}->page, $code); + } + } + + // Run + if (self::$config->debug->enable === true || trim(self::$config->debug->enable) === 'true') { + Debugger::run(); + } + } + + /** + * Cross-site request forgery + **/ + public static function csrf() { + Csrf::setKey(self::$config->security->csrf->key ?? '_csrf'); + Csrf::setInput(self::$config->security->csrf->input ?? '_csrf'); + + // Csrf + Csrf::run(); + } + + /** + * Validator + **/ + public static function validator() { + // PDO Connection + $pdo = pdoDB::connection(); + + // Validator + Validator::setPdo($pdo); + Validator::setNamespace(self::$config->validator->namespace); + Validator::setPath(self::$config->validator->path); + Validator::run(); + } + + /** + * Cli + **/ + public static function commandLineInterface() { + // Commands namespace + Cli::setNamespace(self::$config->cli->namespace); + + // Path + Cli::setPath(self::root(self::$config->cli->path)); + + // List of commands + $list = [ + "server" => "Kiaan\\Application\\Resources\\CLICommands\\Server", + "model" => "Kiaan\\Application\\Resources\\CLICommands\\Model", + "migration" => "Kiaan\\Application\\Resources\\CLICommands\\Migration", + "seed" => "Kiaan\\Application\\Resources\\CLICommands\\Seed", + "config" => "Kiaan\\Application\\Resources\\CLICommands\\Config", + "trans" => "Kiaan\\Application\\Resources\\CLICommands\\Trans", + "cli" => "Kiaan\\Application\\Resources\\CLICommands\\Cli", + "controller" => "Kiaan\\Application\\Resources\\CLICommands\\Controller", + "middleware" => "Kiaan\\Application\\Resources\\CLICommands\\Middleware", + "validator" => "Kiaan\\Application\\Resources\\CLICommands\\Validator", + "route" => "Kiaan\\Application\\Resources\\CLICommands\\Route", + "event" => "Kiaan\\Application\\Resources\\CLICommands\\Event", + "plugin" => "Kiaan\\Application\\Resources\\CLICommands\\Plugin" + ]; + + // Cli commands + Cli::commands($list); + + if(PHP_SAPI == 'cli'){ + // Handle + if(sizeof($_SERVER['argv']) > 1){ + Cli::handle(); + }else{ + Cli::warn((" Welcome to Kiaan framework! "), 'red'); + Cli::notice((" Easy, flexible and professional! "), 'yellow'); + } + } + } + + /** + * Plugins + **/ + public static function plugins() { + Plugin::setHandle("handle"); + Plugin::setExtra([ + "servicesEnable" => "framework-services-enable", + "services" => "framework-services-class", + "servicesMethod" => "framework-services-method", + ]); + + // Plugin services run + Plugin::servicesRun(); + } + + /** + * Input + **/ + public static function input() { + new Input(); + Input::filesystemRoot(self::root()); + } + + /** + * Controller + **/ + public static function controller() { + Controller::setNamespace(self::$config->controller->namespace); + Controller::setPath(self::root(self::$config->controller->path)); + Controller::setDefaultMethod(self::$config->controller->defaultMethod); + } + + /** + * Middleware + **/ + public static function middleware() { + Middleware::setNamespace(self::$config->middleware->namespace); + Middleware::setPath(self::root(self::$config->middleware->path)); + } + + /** + * Route + **/ + public static function route() { + // Eanble + if (self::$config->router->enable) { + + // Prefix web + Route::setPrefixGates("web", self::$config->router->web->prefix); + + // Prefix API + Route::setPrefixGates("api", self::$config->router->api->prefix); + + // Controller namespace + Route::setControllerNamespace(Controller::getNamespace()); + + // Controller default method + Route::setControllerDefaultMethod(Controller::getDefaultMethod()); + + // Middleware namespace + Route::setMiddlewareNamespace(Middleware::getNamespace()); + + // Middleware method + Route::setMiddlewareMethod(Middleware::getMethod()); + + // Cross-site request forgery + Route::setCsrf([ + "enable" => self::$config->security->csrf->enable, + "value" => Csrf::get(), + "input"=> self::$config->security->csrf->input ?? '_csrf' + ]); + + // Method input + $method = self::$config->security->method->input; + $method = (empty($method)) ? '_method' : $method ; + Route::setMethodInput($method); + + // Web routes + array_map(function($file){ + $prefix_path = self::$config->router->path; + $file_path = $prefix_path . DIRECTORY_SEPARATOR . $file . '.php'; + $file_path = self::root($file_path); + + if(!is_file($file_path)){ + $file_path = str_replace(['/', '//', '.'], '\\', $file); + $file_path = $file_path . '.php'; + $file_path = self::root($file_path); + } + + include_once($file_path); + }, self::$config->router->web->routes); + + // API routes + Route::api(function(){ + array_map(function($file){ + $prefix_path = self::$config->router->path; + $file_path = $prefix_path . DIRECTORY_SEPARATOR . $file . '.php'; + $file_path = self::root($file_path); + + if(!is_file($file_path)){ + $file_path = str_replace(['/', '//', '.'], '\\', $file); + $file_path = $file_path . '.php'; + $file_path = self::root($file_path); + } + + include_once($file_path); + }, self::$config->router->api->routes); + }); + + // Run + if(PHP_SAPI != 'cli'){ + Route::run(); + } + } + } + + /** + * Event + **/ + public static function event() { + Event::setNamespace(self::$config->events->namespace); + Event::setPath(self::$config->events->path); + } + + /** + * Notifi + **/ + public static function notifi() { + Notifi::setPdo(pdoDB::connection()); + Notifi::setLoginSession(self::$config->db->auth->loginSession); + Notifi::setTable(self::$config->notifications->table); + Notifi::setUserTable(self::$config->db->auth->table); + } + + /** + * Response + **/ + public static function response() { + Response::filesystemRoot(self::root()); + } + + /** + * Crawl + **/ + public static function crawl() { + Crawl::filesystemRoot(self::root()); + } + + /** + * Cross-Origin Resource Sharing + **/ + public static function cors() { + if(self::$config->cors->enable === true || trim(self::$config->cors->enable == 'true')){ + Cors::enable(); + } + } + + /** + * Helpers + **/ + public static function helpers() { + #-------------------------------------------------- + # Helpers for debugger + + #-------------------------------------------------- + /** + * Page + * + */ + Debugger::xCommand("page", function(string $path, string $code){ + Debugger::viewCode(View::html($path), $code); + }); + + #-------------------------------------------------- + + # Helpers for routes + + #-------------------------------------------------- + /** + * Page + * + */ + Route::xCommand("page", function($uri, $page, $data=[], $method='get', $options=[]){ + return Route::{$method}($uri, function () use ($page, $data) { + return View::page($page, $data); + }, $options); + }); + + #-------------------------------------------------- + + # Helpers for inputs + + #-------------------------------------------------- + /** + * Validate + * + */ + Input::xCommand("validate", function(array $rules, array $message = []){ + return Validator::validate(Input::all(), $rules, $message); + }); + + #-------------------------------------------------- + + # Helpers for views + + #-------------------------------------------------- + /** + * Blob + * + */ + View::directive("blob",function($arg){ + return ('data:;base64,'.base64_encode($arg)); + }); + #-------------------------------------------------- + + #-------------------------------------------------- + /** + * Limit + * + */ + View::directive("limit",function($arg, $limit, $symbol='...'){ + return substr_replace($arg, $symbol, $limit); + }); + #-------------------------------------------------- + + #-------------------------------------------------- + /** + * Lower + * + */ + View::directive("lower",function($arg){ + return strtolower($arg); + }); + #-------------------------------------------------- + + #-------------------------------------------------- + /** + * Substr + * + */ + View::directive("substr",function($arg, $frist, $second=1) { + return substr($arg, $frist, $second); + }); + #-------------------------------------------------- + + #-------------------------------------------------- + /** + * Upper + * + */ + View::directive("upper",function($arg) { + return strtoupper($arg); + }); + #-------------------------------------------------- + + # Helpers for emails + + #-------------------------------------------------- + /** + * Page + * + */ + Mail::xCommand("page", function($page, $data=[]){ + return Mail::message(View::html($page, $data))->html(); + }); + #-------------------------------------------------- + } + +} \ No newline at end of file diff --git a/src/Auth.php b/src/Auth.php new file mode 100644 index 0000000..a083fdf --- /dev/null +++ b/src/Auth.php @@ -0,0 +1,41 @@ +cache, 'path'); + if(in_array($path, $cache_path)){ + $index = array_search($path, $cache_path); + $data = $this->cache[$index]['data']; + + return $data; + } + + // Cache + $cache = [[ + "path" => $path, + "data" => $data + ]]; + + $this->cache = array_merge($this->cache, $cache); + + // Return + return $data; + } + + /** + * Load + * + * Load content from string + */ + protected function load($content) { + // file with prefix + $path = $this->filesystem_path() . $content . '.php'; + + if (is_file($path)) { + // load + $data = new Loader(include($path)); + } else { + throw new \Exception("File not found! ('$path') "); + } + + return $this->prepare_load_content($path, $data); + } + + /** + * Load from file + * + */ + public function file(string $path) { + // extension + if(empty(pathinfo($path, PATHINFO_EXTENSION))){ + $ext = '.php'; + }else{ + $ext = ''; + }; + + $path = $this->filesystem_root() . $path . $ext; + + if(!is_file($path)){ + throw new \Exception("File not found! ('$path') "); + } + + $data = new Loader(include($path)); + + return $this->prepare_load_content($path, $data); + } + + /** + * Load from file + * + */ + public function content(array $array) { + if(is_array($array)){ + $data = new Loader($array); + $path = null; + }else{ + throw new \Exception("Not array!"); + } + + return $this->prepare_load_content($path, $data); + } + + /** + * Database + * + */ + public function db() { + // PDO + $pdo = $this->getPdo(); + + // Table + $table = $this->getTable(); + + // Initialize + return new Database($this, $pdo, $table); + } + + /** + * Get + * + */ + public function get($content, $key, $default=null) { + return ($this->load($content))->get($key, array(), $default); + } + + /** + * Set + * + */ + public function set($content, $key, $value) { + return ($this->load($content))->set($key, $value); + } + + /** + * Has + * + */ + public function has($content, $key) { + return ($this->load($content))->has($key); + } + + /** + * Delete + * + */ + public function delete($content, $key) { + return ($this->load($content))->delete($key); + } + + /** + * Destroy + * + */ + public function destroy($content) { + return ($this->load($content))->destroy(); + } + + /** + * Key + * + */ + public function key($content, $key, $newKey) { + return ($this->load($content))->key($key, $newKey); + } + + /** + * Return array for all data + * + */ + public function toArray($content) { + return ($this->load($content))->toArray(); + } + + /** + * Return object for all data + * + */ + public function toObject($content) { + return ($this->load($content))->toObject(); + } + + /** + * Return object for all data + * + */ + public function all($content) { + return ($this->load($content))->all(); + } + +} \ No newline at end of file diff --git a/src/Config/Config/ConfigSystemTrait.php b/src/Config/Config/ConfigSystemTrait.php new file mode 100644 index 0000000..d8e7e38 --- /dev/null +++ b/src/Config/Config/ConfigSystemTrait.php @@ -0,0 +1,69 @@ +pdo; + } + + /** + * Set PDO + * + */ + public function setPdo($value) { + return $this->pdo = $value; + } + + /** + * Get table + * + */ + public function getTable() { + return $this->table; + } + + /** + * Set table + * + */ + public function setTable($value) { + return $this->table = $value; + } + +} + diff --git a/src/Config/Config/Database.php b/src/Config/Config/Database.php new file mode 100644 index 0000000..de5d527 --- /dev/null +++ b/src/Config/Config/Database.php @@ -0,0 +1,265 @@ +config = $config; + $this->pdo = $pdo; + $this->table = $table; + + if(empty($config->db_cache)){ + $this->load(); + } + } + + /** + * Load all rows from database + * + */ + public function load() { + // Execute + $sql = "SELECT * FROM {$this->table}"; + $query = $this->pdo->prepare($sql); + $query->execute(); + + return $this->config->db_cache = $query->fetchAll(); + } + + /** + * Get + * + */ + public function get($key, $default='') { + // Convert data to array + $data = $this->convertToArray(); + + // Get a index + $index = $this->getIndex($data, $key); + + // Default + if(!is_numeric($index)){ + return $default; + } + + return $data[$index][$this->value_field]; + } + + /** + * Set + * + */ + public function set($key, $value) { + // Convert data to array + $data = $this->convertToArray(); + + // Get a index + $index = array_search($key, array_column($data, $this->key_field)); + + if(!is_numeric($index)){ + // Insert + $sql = "INSERT INTO {$this->table} ({$this->key_field}, {$this->value_field}) VALUES ('$key', '$value')"; + }else{ + // Update + $sql = "UPDATE {$this->table} SET {$this->value_field}='$value' WHERE {$this->key_field}='$key'"; + } + + // Execute + $this->pdo->prepare($sql)->execute(); + + // Save to cache + $this->saveTocache($data); + + return clone($this); + } + + /** + * Has + * + */ + public function has($key) { + // Convert data to array + $data = $this->convertToArray(); + + // Get a index + $index = $this->getIndex($data, $key); + + if(!is_numeric($index)){ + return false; + } + + return true; + } + + /** + * Delete + * + */ + public function delete($key) { + // Convert data to array + $data = $this->convertToArray(); + + if(is_array($key)){ + // Keys for remove + $indexs = array(); + foreach ($key as $element) { + $index = $this->getIndex($data, $key); + + if(is_numeric($index)){ + $indexs[] = $index; + } + } + + // Remove from data + $data = array_diff_key($data, array_flip($indexs)); + + // Keys implode + $key = "'".implode("', '", $key)."'"; + }else{ + // Remove from data + $index = $this->getIndex($data, $key); + + if(is_numeric($index)){ + unset($data[$index]); + } + + // Keys implode + $key = "'".$key."'"; + } + + // Delete from database + $sql = "DELETE FROM {$this->table} WHERE {$this->key_field} IN ($key)"; + $this->pdo->prepare($sql)->execute(); + + // Save to cache + $this->saveTocache($data); + + return clone($this); + } + + /** + * Destroy + * + */ + public function destroy() { + // Execute + $sql = "DELETE FROM {$this->table}"; + $this->pdo->prepare($sql)->execute(); + + return $this->config->db_cache = array(); + } + + /** + * Change key name of key + * + */ + public function key($key, $value) { + // Convert data to array + $data = $this->convertToArray(); + + // Get a index + $index = $this->getIndex($data, $key); + + // Update + if(is_numeric($index) && !$this->has($value)){ + $data[$index]['title'] = $value; + + // Database + $sql = "UPDATE {$this->table} SET {$this->key_field}='$value' WHERE {$this->key_field}='$key'"; + $this->pdo->prepare($sql)->execute(); + } + + // Save to cache + $this->saveTocache($data); + + return clone($this); + } + + /* + * Get index for key + * + */ + protected function getIndex($data, $key) { + return array_search(trim($key), array_column($data, $this->key_field)); + } + + /* + * Convert data to array + * + */ + protected function convertToArray() { + return json_decode(json_encode($this->config->db_cache), true); + } + + /* + * Save data to cache + * + */ + protected function saveTocache($data) { + return $this->config->db_cache = json_decode(json_encode($data), false); + } + + /** + * Return array for all data + * + */ + public function toArray() { + return $this->config->db_cache; + } + + /** + * Return object for all data + * + */ + public function toObject() { + return json_decode(json_encode($this->toArray()), false); + } + + /** + * Return object for all data + * + */ + public function all() { + return $this->toObject(); + } + +} \ No newline at end of file diff --git a/src/Config/Config/Loader.php b/src/Config/Config/Loader.php new file mode 100644 index 0000000..c3a808a --- /dev/null +++ b/src/Config/Config/Loader.php @@ -0,0 +1,252 @@ +data = $data; + } + + /* + * Get + * + */ + public function get($key, $vars=[], $default=null) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!isset($array[$k])) { + if(! is_callable($default)){ + return $default; + }else{ + return call_user_func($default); + } + } + + if (sizeof($keys) === 0) { + return $this->prepareValueWithVars($array[$k], $vars); + } + + $array = &$array[$k]; + } + } + + return true; + } + + /* + * Prepare value with vars + * + */ + protected function prepareValueWithVars($value, $vars) + { + $t = $value; + + // Escape + preg_match_all('~\\\{{(.*?)\}}~si', $t, $escape); + $escape = $escape[1]; + + // Matches + preg_match_all('~\{{(.*?)\}}~si', $t, $matches); + $matches = array_diff($matches[1], $escape); + + if ( isset($matches)) { + foreach ( $matches as $var => $value ) { + $t = str_replace('{{' . $value . '}}', $vars[$value], $t); + } + } + + // Escape "\{{" to "{{" + $t = str_replace('\{{', "{{", $t); + + return $t; + } + + /* + * Set + * + */ + public function set($key, $value) + { + $array = $this->data; + + if (is_string($key) && !empty($key)) { + + $keys = explode('.', $key); + $arrTmp = &$array; + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!is_array($arrTmp)) { + $arrTmp = []; + } + + if (!isset($arrTmp[$k])) { + $arrTmp[$k] = []; + } + + if (sizeof($keys) === 0) { + $arrTmp[$k] = $value; + $this->data = $arrTmp; + } + + $arrTmp = &$arrTmp[$k]; + } + } + return true; + } + + /* + * Has + * + */ + public function has($key) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + $tempKey = $array; + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if(!isset($tempKey[$k])){ + return false; + }else{ + $tempKey = $tempKey[$k]; + } + } + + return true; + } + } + + /* + * Delete + * + */ + public function delete($key) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!isset($array[$k])) { + return false; + } + + if (sizeof($keys) === 0) { + unset($array[$k]); + $this->data = $array; + return true; + } + } + } + } + + /* + * Destroy + * + */ + public function destroy() + { + return $this->data = array(); + } + + /* + * Key + * + */ + public function key($key, $newKey) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + $keyString = '$array'; + $keyLastString = ''; + + foreach($keys as $index => $key){ + if($index != array_key_last($array)){ + $keyLastString = "['$key']"; + }else{ + $keyString .= "['$key']"; + } + } + + eval("$keyString"."['$newKey']=$keyString$keyLastString;" . "unset($keyString$keyLastString);"); + + return $this->data = $array; + } + } + + /** + * Return array for all data + * + */ + public function toArray() { + return $this->data; + } + + /** + * Return object for all data + * + */ + public function toObject() { + return json_decode(json_encode($this->toArray()), false); + } + + /** + * Return object for all data + * + */ + public function all() { + return $this->toObject(); + } + +} \ No newline at end of file diff --git a/src/Config/Env.php b/src/Config/Env.php new file mode 100644 index 0000000..6648d96 --- /dev/null +++ b/src/Config/Env.php @@ -0,0 +1,146 @@ +get($key); + $this->set($newKey, $value); + + return $this->delete($key); + } + + /** + * Destroy all data + * + */ + public function destroy() { + foreach($_ENV as $key=>$env){ + putenv("$key="); + putenv("$key"); + } + + return $_ENV = array(); + } + + /** + * Load content + * + */ + public function load(){ + return new Env\Loader($this); + } + + /** + * Load file + * + */ + public function file($path) { + return $this->load()->file($path); + } + + /** + * Load content + * + */ + public function content($content) { + return $this->load()->content($content); + } + + /** + * Return array for all data + * + */ + public function toArray() { + return $_ENV; + } + + /** + * Return object for all data + * + */ + public function toObject() { + return json_decode(json_encode($this->toArray()), false); + } + + /** + * Return object for all data + * + */ + public function all() { + return $this->toObject(); + } + +} \ No newline at end of file diff --git a/src/Config/Env/Loader.php b/src/Config/Env/Loader.php new file mode 100644 index 0000000..bba0ccd --- /dev/null +++ b/src/Config/Env/Loader.php @@ -0,0 +1,226 @@ +env = $env; + } + + /** + * Parse + * + */ + protected function parse(string $content){ + $content = preg_replace("/(^[\r\n]*|[\r\n]+)[\s\t]*[\r\n]+/", "\n", $content); + $content = chop($content); + + $lines = preg_split("/\r\n|\n|\r/", $content); + array_shift($lines); + + $result = array(); + + $regex = '/\\\\\\$|\\$\\{[^}]*\\}/'; + + foreach ($lines as $line) { + // Comments + if (strpos(trim($line), $this->commentSymbol) === 0) { continue; } + + // Define a variable + $key = explode('=', $line); + $name = (isset($key[0])) ? trim($key[0]) : null; + $value = (isset($key[1])) ? trim($key[1]) : null; + + $value = preg_replace_callback($regex, function($matches) use($result) { + if(($matches[0])[0]=='\\'){ + return substr($matches[0], 1); + } + + $var = substr($matches[0], 2); + $var = substr($var, 0, -1); + + return (isset($result[$var])) ? $result[$var] : ''; + }, $value); + + $result[$name] = $value; + } + + return $result; + } + + /** + * Load content from string + * + */ + public function content(String $content) + { + $this->data = $this->parse($content); + + return clone($this); + } + + /** + * Load content from file + * + */ + public function file(String $path) + { + // Path + $path = $this->env->filesystemRoot() . $path; + + if(!file_exists($path)) { + throw new \InvalidArgumentException(sprintf('%s does not exist', $path)); + } + + // Content + $content = file_get_contents($path); + + // Load + $this->data = $this->parse($content); + + return clone($this); + } + + /* + * Add to $_Env and putenv() + * + */ + public function submit() { + $data = $this->data; + + foreach ($data as $key => $value) { + $_ENV[$key] = $value; + putenv(sprintf('%s=%s', $key, $value)); + } + + // Reset + $this->data = array(); + } + + /** + * Set new environment variable + * + * @param string $key + * @param string $value + * + */ + public function set($key, $value) { + $this->data[$key] = $value; + + return clone($this); + } + + /** + * Check that environment variable has the key + * + * @param string $key + * + */ + public function has($key) { + return isset($this->data[$key]); + } + + /** + * Delete environment variable by the given key + * + */ + public function delete($key) { + unset($this->data[$key]); + + return clone($this); + } + + /** + * Change key name of environment variable + * + */ + public function key($key, $newKey) { + $value = $this->get($key); + $this->set($newKey, $value); + $this->delete($key); + + return clone($this); + } + + /** + * Destroy all data + * + */ + public function destroy() { + $this->data = array(); + + return clone($this); + } + + /** + * Get environment variable by the given key + * + */ + public function get($key, $default=null) { + return isset($this->data[$key]) ? $this->data[$key] : $default; + } + + /** + * Return array for all data + * + */ + public function toArray() { + return $this->data; + } + + /** + * Return object for all data + * + */ + public function toObject() { + return json_decode(json_encode($this->toArray()), false); + } + + /** + * Return object for all data + * + */ + public function all() { + return $this->toObject(); + } + +} \ No newline at end of file diff --git a/src/Config/Store.php b/src/Config/Store.php new file mode 100644 index 0000000..9539437 --- /dev/null +++ b/src/Config/Store.php @@ -0,0 +1,164 @@ +get($key); + } + + public function get($key, $default=null) { + $key = trim($key); + + if(isset($this->data[$key])){ + if(is_callable($this->data[$key])){ + $result = call_user_func_array($this->data[$key], array($this)); + }else{ + $result = $this->data[$key]; + } + }else{ + $result = $default; + } + + return $result; + } + + /** + * Set + * + */ + public function __set($key, $value) { + $key = trim($key); + + return $this->set($key, $value); + } + + public function set($key, $value) { + $key = trim($key); + + return $this->data[$key] = $value; + } + + /** + * Has + * + */ + public function has($key) { + $key = trim($key); + + return isset($this->data[$key]); + } + + /** + * Delete + * + */ + public function delete($key) { + $key = trim($key); + + if(isset($this->data[$key])){ + unset($this->data[$key]); + } + + return true; + } + + /** + * Destroy + * + */ + public function destroy() { + return $this->data = array(); + } + + /** + * Key + * + */ + public function key($key, $newKey) { + $key = trim($key); + + if(isset($this->data[$key])){ + $value = $this->data[$key]; + + unset($this->data[$key]); + + $this->data[$newKey] = $value; + } + + return true; + } + + /** + * To array + * + */ + public function toArray() { + $result = array(); + + foreach($this->data as $key => $data){ + if(is_callable($data)){ + $result[$key] = call_user_func_array($this->data[$key], array($this)); + }else{ + $result[$key] = $data; + } + } + + return $result; + } + /** + * To object + * + */ + public function toObject() { + return json_decode(json_encode($this->toArray()), false); + } + + /** + * All + * + */ + public function all() { + return $this->toObject(); + } + + } \ No newline at end of file diff --git a/src/Contact/Mail.php b/src/Contact/Mail.php new file mode 100644 index 0000000..6c3e187 --- /dev/null +++ b/src/Contact/Mail.php @@ -0,0 +1,284 @@ +server = $server; + $this->port = $port; + $this->hostname = gethostname(); + + if($this->port == "smtp"){ + $this->setHeader('X-Mailer', 'PHP/' . phpversion()); + $this->setHeader('MIME-Version', '1.0'); + } + + return clone($this); + } + + /** + * Set SMTP Login authentication + * + * @param string $username + * @param string $password + * @return Email + */ + public function login($username, $password) + { + $this->username = $username; + $this->password = $password; + + return clone($this); + } + + /** + * reset + * + * Resets all properties to initial state. + * + * @return self + */ + protected function _reset() + { + //$this->from = array(); + $this->to = array(); + $this->subject = ''; + $this->isHtml = false; + $this->message = ''; + $this->reply = array(); + $this->cc = array(); + $this->bcc = array(); + $this->attachment = array(); + $this->mailer = 'mail'; + //$this->isTLS = false; + //$this->isSSL = false; + //$this->hostname = ''; + //$this->server = ''; + //$this->port = ''; + //$this->username = ''; + //$this->password = ''; + + // SMTP + $this->socket = ''; + //$this->protocol = null; + $this->logs = array(); + $this->headers = array(); + } + + /* + * From + * + */ + public function from($email, $name='') + { + $this->from = array($email, $name); + return clone($this); + } + + /* + * To + * + */ + public function to($email, $name='') + { + $this->to[] = array($email, $name); + return clone($this); + } + + /* + * Reply + * + */ + public function reply($email, $name='') + { + $this->reply[] = array($email, $name); + return clone($this); + } + + /* + * CC + * + */ + public function cc($email, $name='') + { + $this->cc[] = array($email, $name); + return clone($this); + } + + /* + * BCC + * + */ + public function bcc($email, $name='') + { + $this->bcc[] = array($email, $name); + return clone($this); + } + + + /* + * Subject + * + */ + public function subject($value) + { + $this->subject = $value; + return clone($this); + } + + /* + * HTML + * + * Enable Html in message. + */ + public function html() + { + $this->isHtml = true; + return clone($this); + } + + /* + * Message + * + */ + public function message($value) + { + $this->message = $value; + return clone($this); + } + + /* + * Attach + * + */ + public function attach($file) + { + if (file_exists($file)) { + $this->attachment[] = $this->filesystemRoot() . $file; + } + return clone($this); + } + + /* + * Mailer + * + * Set mailer + */ + public function mailer($mailer='mail') + { + switch ($mailer) { + case "mail": + $this->mailer='mail'; + break; + case "smtp": + $this->mailer='smtp'; + break; + default: + $this->mailer='mail'; + } + return clone($this); + } + + /* + * Prepare + * + * Prepare to send a Email + */ + protected function prepare() + { + switch ($this->mailer) { + case "mail": + $prepare = (object) $this->prepare_mail(); + $send = $this->send_mail($prepare); + break; + case "smtp": + $send = $this->send_smtp(); + break; + } + + return $send; + } + + /* + * Send + * + * Send a Email + */ + public function send() + { + $prepare = $this->prepare(); + + // Reset + $this->_reset(); + + return $prepare; + } + +} \ No newline at end of file diff --git a/src/Contact/Mail/FileSystemTrait.php b/src/Contact/Mail/FileSystemTrait.php new file mode 100644 index 0000000..d03cbb7 --- /dev/null +++ b/src/Contact/Mail/FileSystemTrait.php @@ -0,0 +1,163 @@ +fileSystemRootPath = $path; + } + + /** + * Get root path + * + */ + public function getRootPath() { + return $this->fileSystemRootPath; + } + + /** + * Set folder path + * + */ + public function setFolderPath($path) { + return $this->fileSystemFolderPath = $path; + } + + /** + * Get folder path + * + */ + public function getFolderPath() { + return $this->fileSystemFolderPath; + } + + /** + * FileSystem + * + */ + public function fileSystem($mode='path', $notTemp=false) { + // Temp + if($notTemp){ + $this->tempFileSystemMode = false; + }else{ + $this->tempfileSystemValueMode = $this->fileSystemMode; + $this->tempFileSystemMode = true; + } + + // Mode + $mode = trim($mode); + + switch ($mode) { + case 'path': + $this->fileSystemMode = 'path'; + return clone($this); + break; + case 'full': + $this->fileSystemMode = 'full'; + return clone($this); + break; + case 'folder': + $this->fileSystemMode = 'folder'; + return clone($this); + break; + default: + $this->fileSystemMode = 'path'; + return clone($this); + break; + } + } + + /** + * Get filesystem + * + */ + public function getFileSystem() { + return $this->fileSystemMode; + } + + /** + * Prepare path filesystem + * + */ + protected function preparePathFileSystem($path) { + + // Prepare path + switch ($this->fileSystemMode) { + case 'path': + $preparePath = $this->getRootPath() . '/' . $path; + break; + case 'full': + $preparePath = $path; + break; + case 'folder': + $preparePath = $this->getFolderPath() . '/' . $path; + break; + default: + $preparePath = $this->getRootPath() . '/' . $path; + break; + } + + // Temp + if($this->tempFileSystemMode){ + $this->fileSystemMode = $this->tempfileSystemValueMode; + $this->tempfileSystemValueMode = null; + $this->tempFileSystemMode = false; + } + + return $preparePath; + } + +} + diff --git a/src/Contact/Mail/GatesTrait.php b/src/Contact/Mail/GatesTrait.php new file mode 100644 index 0000000..934434e --- /dev/null +++ b/src/Contact/Mail/GatesTrait.php @@ -0,0 +1,45 @@ +mailer('mail'); + + return clone($this); + } + + /* + * SMTP + * Set mailer as SMTP + */ + public function smtp() + { + $this->mailer('smtp'); + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Contact/Mail/MailTrait.php b/src/Contact/Mail/MailTrait.php new file mode 100644 index 0000000..1116456 --- /dev/null +++ b/src/Contact/Mail/MailTrait.php @@ -0,0 +1,172 @@ +subject; + $subject = '=?UTF-8?B?' . base64_encode($subject_text) . '?='; + + // Message + $message = ($this->message); + + // To + $to = ''; + + foreach($this->to as $item){ + if(empty($item[1])){ + $to .= $item[0]; + }else { + $to .= '=?UTF-8?B?' . base64_encode($item[1]) . '?= <'.$item[0].'>'; + } + } + + // From + $from = ''; + + if(empty($this->from[1])){ + $from .= $this->from[0]; + }else { + $from .= '=?UTF-8?B?' . base64_encode($this->from[1]) . '?= <'.$this->from[0].'>'; + } + + // Reply + $reply = ''; + + foreach($this->reply as $item){ + if(empty($item[1])){ + $reply .= $item[0]; + }else { + $reply .= '=?UTF-8?B?' . base64_encode($item[1]) . '?= <'.$item[0].'>'; + } + } + + if(!empty($reply)){ + $header_reply = 'Reply-To: ' . $reply . "\r\n"; // Reply-To + }else{ + $header_reply=''; + } + + // CC + $cc = ''; + + foreach($this->cc as $item){ + if(empty($item[1])){ + $cc .= $item[0]; + }else { + $cc .= '=?UTF-8?B?' . base64_encode($item[1]) . '?= <'.$item[0].'>'; + } + } + + if(!empty($cc)){ + $header_cc = 'Cc: ' . $cc . "\r\n"; // Cc + }else{ + $header_cc=''; + } + + // BCC + $bcc = ''; + + foreach($this->bcc as $item){ + if(empty($item[1])){ + $bcc .= $item[0]; + }else { + $bcc .= '=?UTF-8?B?' . base64_encode($item[1]) . '?= <'.$item[0].'>'; + } + } + + if(!empty($cc)){ + $header_bcc = 'Bcc: ' . $bcc . "\r\n"; // Bcc + }else{ + $header_bcc = ''; + } + + // Type + if($this->isHtml){ + $type = "text/html"; + }else{ + $type = "text/plain"; + } + + // Headers + $headers = ''; + $headers .= 'From: ' . $from . "\r\n"; // From + $headers .= 'MIME-Version: 1.0' . "\r\n"; // MIME + $headers .= $header_reply; // Reply-To + $headers .= $header_cc; // CC + $headers .= $header_bcc; // BCC + $headers .= 'X-Mailer: PHP/' . phpversion(); // Mailer + + // Attachments + $files = $this->attachment; + + // Boundary + $semi_rand = md5(time()); + $mime_boundary = "==Multipart_Boundary_x{$semi_rand}x"; + + // Headers for attachment + $headers .= "\nContent-Type: multipart/mixed;\n" . " boundary=\"{$mime_boundary}\""; + + // Multipart boundary + $message = "--{$mime_boundary}\n" . "Content-Type: $type; charset=\"UTF-8\"\n" . + "Content-Transfer-Encoding: 7bit\n\n" . $message . "\n\n"; + + // Preparing attachment + if(!empty($files)){ + for($i=0;$ito, $prepare->subject, $prepare->message, $prepare->headers); + } + +} \ No newline at end of file diff --git a/src/Contact/Mail/SmtpTrait.php b/src/Contact/Mail/SmtpTrait.php new file mode 100644 index 0000000..d96c408 --- /dev/null +++ b/src/Contact/Mail/SmtpTrait.php @@ -0,0 +1,321 @@ +headers[$key] = $value; + + return clone($this); + } + + /** + * Get message character set + * + * @param string $charset + * @return Email + */ + public function setCharset($charset) + { + $this->charset = $charset; + + return clone($this); + } + + /** + * Set SMTP Server protocol + * -- default value is null (no secure protocol) + * + * @param string $protocol + * @return Email + */ + public function protocol($protocol = null) + { + switch ($protocol) { + case "tls": + $this->isTLS = true; + $this->isSSL = false; + $this->protocol = "tcp"; + break; + case "ssl": + $this->isTLS = false; + $this->isSSL = true; + $this->protocol = "ssl"; + break; + default: + $this->protocol = $protocol; + } + + return clone($this); + } + + /** + * Get log array + * -- contains commands and responses from SMTP server + * + * @return array + */ + public function getLogs() + { + return $this->logs; + } + + /** + * Send email to recipient via mail server + * + * @return bool + */ + public function send_smtp() + { + $message = null; + $this->socket = fsockopen( + $this->getServer(), + $this->port, + $errorNumber, + $errorMessage, + $this->connectionTimeout + ); + + if (empty($this->socket)) { + return false; + } + + $this->logs['CONNECTION'] = $this->getResponse(); + $this->logs['HELLO'][1] = $this->sendCommand('EHLO ' . $this->hostname); + + if ($this->isTLS) { + $this->logs['STARTTLS'] = $this->sendCommand('STARTTLS'); + stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); + $this->logs['HELLO'][2] = $this->sendCommand('EHLO ' . $this->hostname); + } + + $this->logs['AUTH'] = $this->sendCommand('AUTH LOGIN'); + $this->logs['USERNAME'] = $this->sendCommand(base64_encode($this->username)); + $this->logs['PASSWORD'] = $this->sendCommand(base64_encode($this->password)); + $this->logs['MAIL_FROM'] = $this->sendCommand('MAIL FROM: <' . $this->from[0] . '>'); + + $recipients = array_merge($this->to, $this->cc, $this->bcc); + foreach ($recipients as $address) { + $this->logs['RECIPIENTS'][] = $this->sendCommand('RCPT TO: <' . $address[0] . '>'); + } + + $this->setHeader('Date', date('r')); + $this->setHeader('Subject', $this->subject); + $this->setHeader('From', $this->formatAddress($this->from)); + $this->setHeader('Return-Path', $this->formatAddress($this->from)); + $this->setHeader('To', $this->formatAddressList($this->to)); + + if (!empty($this->replyTo)) { + $this->setHeader('Reply-To', $this->formatAddressList($this->replyTo)); + } + + if (!empty($this->cc)) { + $this->setHeader('Cc', $this->formatAddressList($this->cc)); + } + + if (!empty($this->bcc)) { + $this->setHeader('Bcc', $this->formatAddressList($this->bcc)); + } + + $boundary = md5(uniqid(microtime(true), true)); + $this->setHeader('Content-Type', 'multipart/mixed; boundary="mixed-' . $boundary . '"'); + + if (!empty($this->attachment)) { + $this->headers['Content-Type'] = 'multipart/mixed; boundary="mixed-' . $boundary . '"'; + $message .= '--mixed-' . $boundary . $this->CRLF; + $message .= 'Content-Type: multipart/alternative; boundary="alt-' . $boundary . '"' . $this->CRLF . $this->CRLF; + } else { + $this->headers['Content-Type'] = 'multipart/alternative; boundary="alt-' . $boundary . '"'; + } + + + // Type + if($this->isHtml){ + $type = "text/html"; + }else{ + $type = "text/plain"; + } + + // Message + + $message .= '--alt-' . $boundary . $this->CRLF; + $message .= "Content-Type: $type; charset=" . $this->charset . $this->CRLF; + $message .= 'Content-Transfer-Encoding: base64' . $this->CRLF . $this->CRLF; + $message .= chunk_split(base64_encode($this->message)) . $this->CRLF; + + + $message .= '--alt-' . $boundary . '--' . $this->CRLF . $this->CRLF; + + + if (!empty($this->attachment)) { + foreach ($this->attachment as $attachment) { + $filename = pathinfo($attachment, PATHINFO_BASENAME); + $contents = file_get_contents($attachment); + $type = mime_content_type($attachment); + if (!$type) { + $type = 'application/octet-stream'; + } + + $message .= '--mixed-' . $boundary . $this->CRLF; + $message .= 'Content-Type: ' . $type . '; name="' . $filename . '"' . $this->CRLF; + $message .= 'Content-Disposition: attachment; filename="' . $filename . '"' . $this->CRLF; + $message .= 'Content-Transfer-Encoding: base64' . $this->CRLF . $this->CRLF; + $message .= chunk_split(base64_encode($contents)) . $this->CRLF; + } + + $message .= '--mixed-' . $boundary . '--'; + } + + $headers = ''; + foreach ($this->headers as $k => $v) { + $headers .= $k . ': ' . $v . $this->CRLF; + } + + $this->logs['MESSAGE'] = $message; + $this->logs['HEADERS'] = $headers; + $this->logs['DATA'][1] = $this->sendCommand('DATA'); + $this->logs['DATA'][2] = $this->sendCommand($headers . $this->CRLF . $message . $this->CRLF . '.'); + $this->logs['QUIT'] = $this->sendCommand('QUIT'); + fclose($this->socket); + + return substr($this->logs['DATA'][2], 0, 3) == 250; + } + + /** + * Get server url + * -- if set SMTP protocol then prepend it to server + * + * @return string + */ + protected function getServer() + { + return ($this->protocol) ? $this->protocol . '://' . $this->server : $this->server; + } + + /** + * Get Mail Server response + * @return string + */ + protected function getResponse() + { + $response = ''; + stream_set_timeout($this->socket, $this->responseTimeout); + while (($line = fgets($this->socket, 515)) !== false) { + $response .= trim($line) . "\n"; + if (substr($line, 3, 1) == ' ') { + break; + } + } + + return trim($response); + } + + /** + * Send command to mail server + * + * @param string $command + * @return string + */ + protected function sendCommand($command) + { + fputs($this->socket, $command . $this->CRLF); + + return $this->getResponse(); + } + + /** + * Format email address (with name) + * + * @param array $address + * @return string + */ + protected function formatAddress($address) + { + return (empty($address[1])) ? $address[0] : '"' . addslashes($address[1]) . '" <' . $address[0] . '>'; + } + + /** + * Format email address to list + * + * @param array $addresses + * @return string + */ + protected function formatAddressList(array $addresses) + { + $data = array(); + foreach ($addresses as $address) { + $data[] = $this->formatAddress($address); + } + + return implode(', ', $data); + } + +} \ No newline at end of file diff --git a/src/Contact/Notifi.php b/src/Contact/Notifi.php new file mode 100644 index 0000000..cdcf5c7 --- /dev/null +++ b/src/Contact/Notifi.php @@ -0,0 +1,535 @@ +pdo->prepare($sql); + $db->execute(); + $db->setFetchMode(\PDO::FETCH_OBJ); + $fetch = $db->fetchAll(); + + $this->lastId = $this->pdo->lastInsertId(); + + return $fetch; + } + + /** + * Set pdo connection + * + */ + public function setPdo($pdo) + { + return $this->pdo = $pdo; + } + + /** + * Get pdo connection + * + */ + public function getPdo() + { + return $this->pdo; + } + + /** + * Get login session + * + */ + public function getLoginSession() + { + return $this->login_session; + } + + /** + * Set login session + * + */ + public function setLoginSession($key='auth') + { + $this->login_session = $key; + } + + /** + * Get table + * + */ + public function getTable() + { + return $this->table; + } + + /** + * Set table + * + */ + public function setTable($table) + { + return $this->table = $table; + } + + /** + * Get user table + * + */ + public function getUserTable() + { + return $this->users_table; + } + + /** + * Set user table. + * + */ + public function setUserTable($table) + { + return $this->users_table = $table; + } + + /** + * Get primary field. + * + */ + public function getPrimaryField() + { + return $this->primary_field; + } + + /** + * Set primary field. + * + */ + public function setPrimaryField($id) + { + return $this->primary_field = $id; + } + + /** + * Select current user ID. + * + */ + public function me() + { + if(!isset($_SESSION[$this->login_session])){ + return clone($this); + } + + return $this->user($_SESSION[$this->login_session]); + } + + /** + * User ID. + * + */ + public function user($id) + { + // ID + if(is_string($id)){ + $id = explode(",", $id); + } + + if(is_int($id)){ + $id = array($id); + } + + // Remove white space + $id = array_map('trim', $id); + + // Add to users list + $this->users = $id; + + // Return + return clone($this); + } + + /** + * Select ID. + * + */ + public function select($id) + { + // ID + if(is_string($id)){ + $id = explode(",", $id); + } + + if(is_int($id)){ + $id = array($id); + } + + // Remove white space + $id = array_map('trim', $id); + + // Add to select list + $this->select = $id; + + // Return + return clone($this); + } + + /** + * Clean data. + * + */ + public function clean($lastId=false) + { + $this->users = array(); + $this->select = array(); + $this->limit = null; + $this->offset = null; + $this->read = null; + $this->data = array(); + if($lastId===false){$this->lastId = null;} + } + + /** + * List of all read and unread notifications. + * + */ + public function readAndUnread() + { + $this->read = null; + + return clone($this); + } + + public function UnreadAndRead() + { + $this->read = null; + + return clone($this); + } + + /** + * List of all read notifications. + * + */ + public function read() + { + $this->read = true; + + return clone($this); + } + + /** + * List of all unread notifications. + * + */ + public function unread() + { + $this->read = false; + + return clone($this); + } + + /** + * List of all notifications with limit. + * + */ + public function limit(int $count) + { + $this->limit = $count; + + return clone($this); + } + + /** + * List of all notifications with limit and offset. + * + */ + public function offset(int $count) + { + $this->offset = $count; + + return clone($this); + } + + /** + * Generate query string. + * + */ + protected function generateQuery() + { + // Query + $query = ""; + + // Where + $where = array(); + $users = implode(",", $this->users); + $select = implode(",", $this->select); + + if(!empty($users)){ + $where['users'] = $users; + } + + if(!empty($select)){ + $where['select'] = $select; + } + + if(!is_null($this->read)){ + $where['read'] = ($this->read===true) ? 'seen=1' : 'seen=0'; + } + + + foreach ($where as $key => $item) { + if ($key === array_key_first($where)){ + $query .= " WHERE "; + }else{ + $query .= " AND "; + } + + switch ($key) { + case "users": + $query .= "user_id IN ({$item})"; + break; + case "select": + $query .= "id IN ({$item})"; + break; + case "read": + $query .= $item; + break; + } + } + + // Limit + if(!is_null($this->limit)){ + $query .= " LIMIT {$this->limit}"; + } + + // Offset + if(!is_null($this->offset)){ + $query .= " OFFSET {$this->offset}"; + } + + return $query; + } + + /** + * List of all notifications. + * + */ + public function get() + { + // Query + $query = "SELECT * FROM {$this->table}"; + + // Generate query string. + $query .= $this->generateQuery(); + + // Clean data. + $this->clean(); + + // Execute + return $this->execute($query . ";"); + } + + /** + * Mark all selected as read. + * + */ + public function asRead(bool $read=true) + { + // Read + $read = ($read===true) ? 1 : 0; + + // Date + $date = date("Y-m-d h:i:s"); + + // Query + $query = "UPDATE {$this->table} SET seen={$read}, seen_at='{$date}'"; + + // Generate query string. + $query .= $this->generateQuery(); + + // Clean data. + $this->clean(); + + // Execute + $this->execute($query . ";"); + + return true; + } + + /** + * Mark all selected as un read. + * + */ + public function asUnRead() + { + return $this->asRead(false); + } + + /** + * Send notification. + * + */ + public function send(string $data) + { + return $this->insert($data); + } + + /** + * Send notification. + * + */ + public function insert(string $data) + { + // Send + try { + foreach ($this->users as $user) { + $this->execute("INSERT INTO {$this->table} (id, user_id, data) VALUES (NULL, {$user}, '{$data}')"); + } + } catch (\Throwable $th) { + // Return false + return false; + } + + // Clean data. + $this->clean(true); + + // Return true + return $this->lastId; + } + + /** + * Update data. + * + */ + public function update(string $data) + { + // Query + $query = "UPDATE {$this->table} SET data='{$data}'"; + + // Generate query string. + $query .= $this->generateQuery(); + + // Clean data. + $this->clean(); + + // Execute + $this->execute($query . ";"); + + return true; + } + + /** + * Delete row of notifications. + * + */ + public function delete() + { + // Query + $query = "DELETE FROM {$this->table}"; + + // Generate query string. + $query .= $this->generateQuery(); + // Clean data. + $this->clean(); + + // Execute + $this->execute($query . ";"); + + return true; + } + +} \ No newline at end of file diff --git a/src/Controller.php b/src/Controller.php new file mode 100644 index 0000000..13d4282 --- /dev/null +++ b/src/Controller.php @@ -0,0 +1,41 @@ +toArray()), false); + } + + /** + * Return object for all cookies + * + */ + public function all() { + return $this->toObject(); + } + +} \ No newline at end of file diff --git a/src/Cors.php b/src/Cors.php new file mode 100644 index 0000000..3ad03df --- /dev/null +++ b/src/Cors.php @@ -0,0 +1,41 @@ +list = $items; + + return clone($this); + } + + /** + * Add an element onto the end of the map without returning a new map. + * + * Examples: + * Collection::collect( ['a', 'b'] )->add( 'c' ); + * Collection::collect( ['a' => 'a', 'b' => 'b'] )->add( ['c' => 'c'] ); + * + * + * @param mixed $value Value to add to the end + * @return self Same map for fluid interface + */ + public function add($value) + { + if(is_object($value)){ + $value = json_decode(json_encode($value), true); + } + + if (is_array($value)) { + $this->list = array_merge($this->list, $value); + }else{ + $this->list[] = $value; + } + + return clone($this); + } + + /** + * Gets a value in an array by dot notation for the keys. + * + * #### Example + * $array = [ + * 'foo' => 'bar', + * 'baz' => [ + * 'qux' => 'foobar' + * ] + * ]; + * + * Collection::collect($array)->get("baz.qux"); + * Collection::collect($array)->get("bazr.quxr", 'default'); + * Collection::collect($array)->get("baz.qux", function () { return 'email@example.com'; }); + */ + public function get($key, $default=null) + { + $array = $this->list; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!isset($array[$k])) { + if(! is_callable($default)){ + return $default; + }else{ + return call_user_func($default); + } + } + + if (sizeof($keys) === 0) { + return $array[$k]; + } + + $array = &$array[$k]; + } + } + + return clone($this); + } + + /** + * Set a value in an array by dot notation for the keys. + * + * #### Example + * $array = [ + * 'foo' => 'bar', + * 'baz' => [ + * 'qux' => 'foobar' + * ] + * ]; + * + * Collection::collect($array)->set('baz.qux', 'value'); + */ + public function set($key, $value) + { + $array = $this->list; + + if (is_string($key) && !empty($key)) { + + $keys = explode('.', $key); + $arrTmp = &$array; + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!is_array($arrTmp)) { + $arrTmp = []; + } + + if (!isset($arrTmp[$k])) { + $arrTmp[$k] = []; + } + + if (sizeof($keys) === 0) { + $arrTmp[$k] = $value; + $this->list = $arrTmp; + } + + $arrTmp = &$arrTmp[$k]; + } + } + + $this->list = $array; + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Data/Collection/HelpersTrait.php b/src/Data/Collection/HelpersTrait.php new file mode 100644 index 0000000..17e51b0 --- /dev/null +++ b/src/Data/Collection/HelpersTrait.php @@ -0,0 +1,318 @@ + $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Results array of items from Collection or Arrayable. + * + * @param mixed $items + * @return array + */ + protected function getArrayableItems($items) + { + return (array) $items; + } + + /** + * Get an operator checker callback. + * + * @param string $key + * @param string|null $operator + * @param mixed $value + * @return \Closure + */ + protected function operatorForWhere($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + $value = true; + + $operator = '='; + } + + if (func_num_args() === 2) { + $value = $operator; + + $operator = '='; + } + + return function ($item) use ($key, $operator, $value) { + $retrieved = $this->arr_data_get($item, $key); + + $strings = array_filter([$retrieved, $value], function ($value) { + return is_string($value) || (is_object($value) && method_exists($value, '__toString')); + }); + + if (count($strings) < 2 && count(array_filter([$retrieved, $value], 'is_object')) == 1) { + return in_array($operator, ['!=', '<>', '!==']); + } + + switch ($operator) { + default: + case '=': + case '==': return $retrieved == $value; + case '!=': + case '<>': return $retrieved != $value; + case '<': return $retrieved < $value; + case '>': return $retrieved > $value; + case '<=': return $retrieved <= $value; + case '>=': return $retrieved >= $value; + case '===': return $retrieved === $value; + case '!==': return $retrieved !== $value; + } + }; + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param iterable $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + protected function arrFirst($array, callable $callback = null, $default = null) + { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return $value; + } + } + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + protected function arrLast($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + $default = $default instanceof \Closure ? $default() : $default; + return empty($array) ? ($default) : end($array); + } + + + return $this->arrFirst(array_reverse($array, true), $callback, $default); + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + protected function arrPrepend($array, $value, $key = null) + { + if (func_num_args() == 2) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Determine if the given value is callable, but not a string. + * + * @param mixed $value + * @return bool + */ + protected function useAsCallable($value) + { + return ! is_string($value) && is_callable($value); + } + + /** + * Determine if an item exists at an offset. + * + * @param mixed $key + * @return bool + */ + protected function offsetExists($key) + { + return isset($this->list[$key]); + } + + /** + * Get an item at a given offset. + * + * @param mixed $key + * @return mixed + */ + protected function offsetGet($key) + { + return $this->list[$key]; + } + + /** + * Set the item at a given offset. + * + * @param mixed $key + * @param mixed $value + * @return void + */ + protected function offsetSet($key, $value) + { + if (is_null($key)) { + $this->list[] = $value; + } else { + $this->list[$key] = $value; + } + } + + /** + * Unset the item at a given offset. + * + * @param string $key + * @return void + */ + protected function offsetUnset($key) + { + unset($this->list[$key]); + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @param bool|false $preserveKeys + * @return mixed + * + * @throws \InvalidArgumentException + */ + protected function arrRandom($array, $number = null, $preserveKeys = false) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + if ($requested > $count) { + throw new \InvalidArgumentException( + "You requested {$requested} items, but there are only {$count} items available." + ); + } + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if ((int) $number === 0) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + if ($preserveKeys) { + foreach ((array) $keys as $key) { + $results[$key] = $array[$key]; + } + } else { + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + } + + return $results; + } + + /* + * Arr cols + * + */ + protected function arrCols(array $array, $keys) + { + if (!is_array($keys)) $keys = [$keys]; + return array_map(function ($el) use ($keys) { + $o = []; + foreach($keys as $key){ + $o[$key] = isset($el[$key])?$el[$key]:false; + } + return $o; + }, $array); + } + + /* + * Arr data get + * + */ + protected function arr_data_get($array, $key, $default=null) + { + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!isset($array[$k])) { + if(! is_callable($default)){ + return $default; + }else{ + return call_user_func($default); + } + } + + if (sizeof($keys) === 0) { + return $array[$k]; + } + + $array = &$array[$k]; + } + } + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Data/Collection/MethodsTrait.php b/src/Data/Collection/MethodsTrait.php new file mode 100644 index 0000000..87395e1 --- /dev/null +++ b/src/Data/Collection/MethodsTrait.php @@ -0,0 +1,1015 @@ +chunk(4); + * Collection::collect(["Name"=>"Hassan", "Country"=>"Egypt"])->chunk(1); + */ + public function chunk($size) + { + $chunks = []; + + foreach (array_chunk($this->list, $size, false) as $chunk) { + $chunks[] = $chunk; + } + + return $this->collect($chunks); + } + + /** + * Collapse the collection of items into a single array. + * + * Example + * Collection::collect([[1, 2, 3],[4, 5, 6],[7, 8, 9]])->collapse(); + * Collection::collect([["Name"=>"Hassan", "Country"=>"Egypt"],[1, 2, 3]])->collapse(); + */ + public function collapse() + { + $array = $this->list; + $results = []; + + foreach ($array as $values) { + $values = $values; + $results[] = $values; + } + + $collapse = array_merge([], ...$results); + + return $this->collect($collapse); + } + + /** + * Combine + * Create a collection by using this collection for keys and another for its values. + * + * Example + * Collection::collect(['name', 'job'])->combine(['Hassan', 'programmer']); + */ + public function combine($values) + { + return $this->collect(array_combine($this->list, $values)); + } + + /** + * Cross join with the given lists, returning all possible permutations. + * + * Example + * Collection::collect([1, 2])->crossJoin(['a', 'b']); + */ + public function crossJoin(...$lists) + { + return $this->collect($this->arrCrossJoin( + $this->list, + ...array_map([$this, 'getArrayableItems'], $lists) + )); + } + + /** + * Get the items in the collection that are not present in the given items. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->diff([2, 4, 6, 8]); + * Collection::collect(['color' => 'orange', 'type' => 'fruit', 'remain' => 6])->diff(['color' => 'yellow', 'type' => 'fruit', 'remain' => 3, 'used' => 6]); + */ + public function diff($items) + { + if (preg_match('/[a-z]/i', implode(array_keys($items)))) { + return $this->collect(array_diff_assoc($this->list, $items)); + } else { + return $this->collect(array_diff($this->list, $items)); + } + } + + /** + * Get the items in the collection whose keys are not present in the given items. + * + * Example + * Collection::collect(['one' => 10, 'two' => 20, 'three' => 30, 'four' => 40, 'five' => 50])->diffKeys(['two' => 2, 'four' => 4, 'six' => 6, 'eight' => 8]); + */ + public function diffKeys($items) + { + return $this->collect(array_diff_key($this->list, $items)); + } + + /** + * Get the items in the collection whose keys are not present in the given items. + * + * Example + * Collection::collect(['a', 'b', 'a', 'c', 'b'])->duplicates(); + * Collection::collect([ ['email' => 'mohammed@example.com', 'position' => 'Developer'], ['email' => 'hassan@example.com', 'position' => 'Designer'], ['email' => 'ali@example.com', 'position' => 'Developer'], ])->duplicates('position'); + */ + public function duplicates($column=null) + { + if (is_null($column)) { + $arr = $this->list; + $duplicates = array_diff_key($arr, array_unique($arr)); + } else { + $arr = array_column($this->list, $column); + $duplicates = array_diff_key($arr, array_unique($arr)); + } + + return $this->collect($duplicates); + } + + /** + * Execute a callback over each item. + * + * Example + * Collection::collect([1, 2])->each(function ($item, $key) { // code }); + */ + public function each(callable $callback) + { + foreach ($this->list as $key => $item) { + if ($callback($item, $key) === false) { + break; + } + } + + return clone($this); + } + + /** + * Create a new collection consisting of every n-th element. + * + * Example + * Collection::collect(['a', 'b', 'c', 'd', 'e', 'f'])->nth(4); + * Collection::collect(['a', 'b', 'c', 'd', 'e', 'f'])->nth(4,1); + */ + public function nth($step, $offset = 0) + { + $new = []; + + $position = 0; + + foreach ($this->list as $item) { + if ($position % $step === $offset) { + $new[] = $item; + } + + $position++; + } + + return $this->collect($new); + } + + /** + * Get all items except for those with the specified keys. + * + * Example + * Collection::collect(['ID' => 1, 'Name' => "Hassan", 'Country' => "Egypt"])->except(['ID', 'Country']); + * Collection::collect(['ID' => 1, 'Name' => "Hassan", 'Country' => "Egypt"])->except('ID', 'Country'); + */ + public function except($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + return $this->collect(array_diff_key($this->list, array_flip($keys))); + } + + /** + * Run a filter over each of the items. + * + * Example + * Collection::collect([1, 2, 3, 4])->filter(function ($value) { return $value < 3; }); + * Collection::collect([1, 2, 3, null, false, '', 0, []])->filter(); + * + */ + public function filter(callable $callback = null) + { + if ($callback) { + return $this->collect(array_filter($this->list, $callback)); + } + + return $this->collect(array_filter($this->list)); + } + + /** + * Get the first item from the collection. + * + * Example + * Collection::collect([1, 2, 3, 4])->first(); + * Collection::collect([1, 2, 3, 4])->first(function ($value, $key) { return $value > 2; }); + */ + public function first(callable $callback = null, $default = null) + { + if (is_null($callback)) { + return count($this->list) > 0 ? reset($this->list) : null; + } + + return $this->arrFirst($this->list, $callback, $default); + } + + /** + * Get a flattened array of the items in the collection. + * + * Example + * Collection::collect(['name'=>'Hassan','languages'=>['php', 'javascript']])->flatten(); + * Collection::collect([1,2,3])->flatten(); + */ + public function flatten() + { + $array = $this->list; + $return = array(); + + while (count($array)) { + $value = array_shift($array); + if (is_array($value)) { + foreach ($value as $sub) { + $array[] = $sub; + } + } else { + $return[] = $value; + } + } + + return $this->collect($return); + } + + /** + * Flip the items in the collection. + * + * Example + * Collection::collect(['Name' => 'Hassan', 'Framework' => 'Kiaan'])->flip(); + * Collection::collect(['a','b','c'])->flip(); + */ + public function flip() + { + return $this->collect(array_flip($this->list)); + } + + /** + * Remove an item from the collection by key. + * + * Example + * Collection::collect(['a','b','c'])->forget(0); + * Collection::collect(['ID' => 1, 'Name' => "Hassan", 'Country' => "Egypt"])->forget(['ID', 'Country']); + */ + public function forget($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + foreach ((array)$keys as $key) { + unset($this->list[$key]); + } + + return clone($this); + } + + /** + * "Paginate" the collection by slicing it into a smaller collection. + * + * Example + * Collection::collect([1, 2, 3, 4, 5, 6, 7, 8, 9])->paginate(2, 3); + */ + public function paginate($page, $perPage) + { + $offset = max(0, ($page - 1) * $perPage); + + return $this->collect(array_slice($this->list, $offset, $perPage, true)); + } + + /** + * Function that groups an array of associative arrays by some key. + * + * Example + * Collection::collect([ ['id' => '10', 'product' => 'pc'], ['id' => '10', 'product' => 'mobile'], ['id' => '11', 'product' => 'laptop'], ])->groupBy('id'); + */ + function groupBy($key) { + $result = array(); + + foreach($this->list as $val) { + if(array_key_exists($key, $val)){ + $result[$val[$key]][] = $val; + }else{ + $result[""][] = $val; + } + } + + return $this->collect($result); + } + + /** + * Intersect the collection with the given items. + * + * Example + * Collection::collect(['mobile', 'pc', 'laptop'])->intersect(['tv', 'pc', 'laptop']); + */ + public function intersect($items) + { + return $this->collect(array_intersect($this->list, $items)); + } + + /** + * Intersect the collection with the given items by key. + * + * Example + * Collection::collect(['serial' => 'HS321', 'type' => 'tv', 'year' => 2020])->intersectByKeys(['reference' => 'HS123', 'type' => 'mobile', 'year' => 2021]); + */ + public function intersectByKeys($items) + { + return $this->collect(array_intersect_key($this->list, $items)); + } + + /** + * Get the keys of the collection items. + * + * Example + * Collection::collect(["name"=>"hassan", "country"=>"egypt"])->keys(); + */ + public function keys() + { + return $this->collect(array_keys($this->list)); + } + + /** + * Get the first item from the collection. + * + * Example + * Collection::collect([1, 2, 3, 4])->last(); + * Collection::collect([1, 2, 3, 4])->last(function ($value, $key) { return $value < 3; }); + */ + public function last(callable $callback = null, $default = null) + { + return $this->arrLast($this->list, $callback, $default); + } + + /** + * Run a map over each of the items. + * + * Example + * Collection::collect([1, 2, 3, 4])->map(function ($item, $key) { return $item * 2; }); + */ + public function map(callable $callback) + { + $keys = array_keys($this->list); + + $items = array_map($callback, $this->list, $keys); + + return $this->collect(array_combine($keys, $items)); + } + + /** + * Run an associative map over each of the items. + * + * Example + * Collection::collect([ [ 'name' => 'hassan', 'department' => 'Sales', 'email' => 'hassan@example.com', ], [ 'name' => 'ahmed', 'department' => 'Marketing', 'email' => 'ahmed@example.com', ] ])->mapWithKeys(function ($item) { return [$item['email'] => $item['name']]; }); + */ + public function mapWithKeys(callable $callback) + { + $result = []; + + foreach ($this->list as $key => $value) { + $assoc = $callback($value, $key); + + foreach ($assoc as $mapKey => $mapValue) { + $result[$mapKey] = $mapValue; + } + } + + return $this->collect($result); + } + + /** + * Run an associative map over each of the items. + * + * Example + * Collection::collect(['product_id' => 1, 'price' => 100])->merge(['price' => 200, 'discount' => false]); + * Collection::collect(['Desk', 'Chair'])->merge(['Bookcase', 'Door']); + */ + public function merge($items) + { + return $this->collect(array_merge($this->list, $items)); + } + + /** + * Recursively merge the collection with the given items. + * + * Example + * Collection::collect(['product_id' => 1, 'price' => 100])->mergeRecursive([ 'product_id' => 2, 'price' => 200, 'discount' => false ]); + */ + public function mergeRecursive($items) + { + return $this->collect(array_merge_recursive($this->list, $items)); + } + + /** + * Get the items with the specified keys. + * + * Example + * Collection::collect([ 'product_id' => 1, 'name' => 'Desk', 'price' => 100, 'discount' => false ])->only(['product_id', 'name']); + */ + public function only($keys) + { + if (is_null($keys)) { + return $this->collect($this->list); + } + + $keys = is_array($keys) ? $keys : func_get_args(); + + $res = array_intersect_key($this->list, array_flip((array) $keys)); + + return $this->collect($res); + } + + /** + * Pad collection to the specified length with a value. + * + * Example + * Collection::collect(['A', 'B', 'C'])->pad(5, 0); + * Collection::collect(['A', 'B', 'C'])->pad(-5, 0); + */ + public function pad($size, $value) + { + return $this->collect(array_pad($this->list, $size, $value)); + } + + /** + * Push an item onto the beginning of the collection. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->prepend(0); + * Collection::collect([1, 2, 3, 4, 5])->prepend(0, 'zero'); + */ + public function prepend($value, $key = null) + { + $this->list = $this->arrPrepend($this->list, ...func_get_args()); + + return clone($this); + } + + /** + * Push one or more items onto the end of the collection. + * + * Example + * Collection::collect([1, 2, 3, 4])->push(5); + */ + public function push(...$values) + { + foreach ($values as $value) { + $this->list[] = $value; + } + + return clone($this); + } + + /** + * Put an item in the collection by key. + * + * Example + * Collection::collect([1, 2, 3, 4])->put(3, 5); + */ + public function put($key, $value) + { + $this->offsetSet($key, $value); + + return clone($this); + } + + /** + * Get one or a specified number of items randomly from the collection. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->random(); + * Collection::collect([1, 2, 3, 4, 5])->random(3); + */ + public function random($number = null) + { + if (is_null($number)) { + return $this->arrRandom($this->list); + } + + return $this->collect($this->arrRandom($this->list, $number)); + } + + /** + * Reduce the collection to a single value. + * + * Example + * Collection::collect([1, 2, 3])->reduce(function ($carry, $item) { return $carry + $item; }); + * Collection::collect([1, 2, 3])->reduce(function ($carry, $item) { return $carry + $item; }, 4); + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->list, $callback, $initial); + } + + /** + * Create a collection of all elements that do not pass a given truth test. + * + * Example + * Collection::collect([1, 2, 3, 4])->reject(function ($value) { return $value > 2; }); + */ + public function reject(callable $callback) + { + if (! is_string($callback) && is_callable($callback)) { + return $this->filter(function ($item) use ($callback) { + return !$callback($item); + }); + } + + return $this->filter(function ($item) use ($callback) { + return $item != $callback; + }); + } + + /** + * Replace the collection items with the given items. + * + * Example + * Collection::collect(['Hassan', 'Mohammed', 'Ahmed'])->replace([1 => 'Ali', 3 => 'Taha']); + */ + public function replace($items) + { + return $this->collect(array_replace($this->list, $items)); + } + + /** + * Recursively replace the collection items with the given items. + * + * Example + * Collection::collect(['Hassan','Ahmed',['Ali','Mohhamed','Ibrahim']])->replaceRecursive([ 'Salah', 2 => [1 => 'Yasmin'] ]); + */ + public function replaceRecursive($items) + { + return $this->collect(array_replace_recursive($this->list, $items)); + } + + /** + * Reverse items order. + * + * Example + * Collection::collect(['a', 'b', 'c', 'd', 'e'])->reverse(); + */ + public function reverse() + { + return $this->collect(array_reverse($this->list, true)); + } + + /** + * Get and remove the first item from the collection. + * + * Example + * Collection::collect(['a', 'b', 'c', 'd', 'e'])->shift(); + */ + public function shift() + { + array_shift($this->list); + return clone($this); + } + + /** + * Shuffle the items in the collection. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->shuffle(); + */ + public function shuffle($seed = null) + { + if (is_null($seed)) { + shuffle($this->list); + } else { + mt_srand($seed); + shuffle($this->list); + mt_srand(); + } + + return clone($this); + } + + /** + * Skip the first {$count} items. + * + * Example + * Collection::collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->skip(5); + */ + public function skip($count) + { + return $this->collect(array_slice($this->list, $count, null, true)); + } + + /** + * Slice the underlying collection array. + * + * Example + * Collection::collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->slice(4); + * Collection::collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->slice(4, 2); + */ + public function slice($offset, $length = null) + { + return $this->collect(array_slice($this->list, $offset, $length, true)); + } + + /** + * Sort through each item with a callback. + * + * Example + * Collection::collect([5, 3, 1, 2, 4])->sort(); + */ + public function sort($callback = null) + { + $items = $this->list; + + $callback && is_callable($callback) + ? uasort($items, $callback) + : asort($items); + + return $this->collect($items); + } + + /** + * Sort items in descending order. + * + * Example + * Collection::collect([5, 3, 1, 2, 4])->sortDesc(); + */ + public function sortDesc() + { + $items = $this->list; + + arsort($items, SORT_REGULAR); + + return $this->collect($items); + } + + /** + * Sort the collection keys. + * + * Example + * Collection::collect([ 'id' => 123, 'first' => 'Hassan', 'last' => 'Kerdash'])->sortKeys(); + */ + public function sortKeys($options = SORT_REGULAR, $descending = false) + { + $items = $this->list; + + $descending ? krsort($items, $options) : ksort($items, $options); + + return $this->collect($items); + } + + /** + * Sort the collection keys in descending order. + * + * Example + * Collection::collect([ 'id' => 123, 'first' => 'Hassan', 'last' => 'Kerdash'])->sortKeysDesc(); + */ + public function sortKeysDesc() + { + return $this->sortKeys(SORT_REGULAR, true); + } + + /** + * Splice a portion of the underlying collection array. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->splice(2); + * Collection::collect([1, 2, 3, 4, 5])->splice(2, 1); + * Collection::collect([1, 2, 3, 4, 5])->splice(2, 1, [10, 11]); + */ + public function splice($offset, $length = null, $replacement = []) + { + if (func_num_args() === 1) { + return $this->collect(array_splice($this->list, $offset)); + } + + return $this->collect(array_splice($this->list, $offset, $length, $replacement)); + } + + /** + * Split a collection into a certain number of groups. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->split(3); + */ + public function split($numberOfGroups) + { + if ($this->isEmpty()) { + return $this->collect; + } + + $groups = $this->collect; + + $groupSize = floor($this->count() / $numberOfGroups); + + $remain = $this->count() % $numberOfGroups; + + $start = 0; + + for ($i = 0; $i < $numberOfGroups; $i++) { + $size = $groupSize; + + if ($i < $remain) { + $size++; + } + + if ($size) { + $groups->push($this->collect(array_slice($this->list, $start, $size))); + + $start += $size; + } + } + + return $groups; + } + + /** + * Split a collection into a certain number of groups, and fill the first groups completely. + * + * Example + * Collection::collect([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])->splitIn(3); + */ + public function splitIn($numberOfGroups) + { + return $this->chunk(ceil($this->count() / $numberOfGroups)); + } + + /** + * Take the first or last {$limit} items. + * + * Example + * Collection::collect([0, 1, 2, 3, 4, 5])->take(3); + * Collection::collect([0, 1, 2, 3, 4, 5])->take(-2); + */ + public function take($limit) + { + if ($limit < 0) { + return $this->slice($limit, abs($limit)); + } + + return $this->slice(0, $limit); + } + + /** + * Transform each item in the collection using a callback. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->transform(function ($item, $key) { return $item * 2; }); + */ + public function transform(callable $callback) + { + $this->list = $this->map($callback)->all(); + + return clone($this); + } + + /** + * Union the collection with the given items. + * + * Example + * Collection::collect([1 => ['a'], 2 => ['b']])->union([3 => ['c'], 1 => ['b']]); + */ + public function union($items) + { + return $this->collect($this->list + $items); + } + + /** + * Return only unique items from the collection array. + * + * Example + * Collection::collect(["name"] => ["Hassan", "Ali", "Hassan", "Ahmed"])->unique("name"); + * Collection::collect([ ["id"=>1, "value"=>1], ["id"=>2, "value"=>2], ["id"=>3, "value"=>1], ])->unique("value"); + */ + public function unique($column='') + { + if(empty($column)){ + return array_unique($this->list); + }else{ + return array_unique(array_column($this->list, $column)); + } + } + + /** + * Filter items by the given key value pair. + * + * Example + * Collection::collect([ ['product' => 'Pc', 'price' => 200], ['product' => 'Phone', 'price' => 100], ['product' => 'Mobile', 'price' => 150], ['product' => 'Laptop', 'price' => 100], ])->where('price', 100); + * Collection::collect([ ['product' => 'Pc', 'price' => 200], ['product' => 'Phone', 'price' => 100], ['product' => 'Mobile', 'price' => 150], ['product' => 'Laptop', 'price' => 100], ])->where('price', '=' ,100); + */ + public function where($key, $operator = null, $value = null) + { + return $this->filter($this->operatorForWhere(...func_get_args())); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * Example + * Collection::collect([ ['product' => 'Pc', 'price' => 200], ['product' => 'Phone', 'price' => 100], ['product' => 'Mobile', 'price' => 150], ['product' => 'Laptop', 'price' => 100], ])->whereStrict('price', 100); + */ + public function whereStrict($key, $value) + { + return $this->where($key, '===', $value); + } + + /** + * Filter items such that the value of the given key is between the given values. + * + * Example + * Collection::collect([ ['product' => 'TV', 'price' => 200], ['product' => 'Laptop', 'price' => 80], ['product' => 'Speaker', 'price' => 150], ['product' => 'Mobile', 'price' => 30], ['product' => 'PC', 'price' => 100], ])->whereBetween('price', [100, 200]); + */ + public function whereBetween($key, $values) + { + return $this->where($key, '>=', reset($values))->where($key, '<=', end($values)); + } + + /** + * Filter items by the given key value pair. + * + * Example + * Collection::collect([ ['product' => 'PC', 'price' => 200], ['product' => 'Mobile', 'price' => 100], ['product' => 'Phone', 'price' => 150], ['product' => 'TV', 'price' => 100], ])->whereIn('price', [150, 200]); + */ + public function whereIn($key, $values, $strict = false) + { + $values = $this->getArrayableItems($values); + + return $this->filter(function ($item) use ($key, $values, $strict) { + return in_array($this->arr_data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * Example + * Collection::collect([ ['product' => 'PC', 'price' => 200], ['product' => 'Mobile', 'price' => 100], ['product' => 'Phone', 'price' => 150], ['product' => 'TV', 'price' => 100], ])->whereInStrict('price', [150, 200]); + */ + public function whereInStrict($key, $values) + { + return $this->whereIn($key, $values, true); + } + + /** + * Filter the items, removing any items that don't match the given type. + * + * Example + * Collection::collect([ new User, new User, new Admin, ])->whereInstanceOf(User::class); + */ + public function whereInstanceOf($type) + { + return $this->filter(function ($value) use ($type) { + return $value instanceof $type; + }); + } + + /** + * Filter items such that the value of the given key is not between the given values. + * + * Example + * Collection::collect([ ['product' => 'TV', 'price' => 200], ['product' => 'Laptop', 'price' => 80], ['product' => 'Speaker', 'price' => 150], ['product' => 'Mobile', 'price' => 30], ['product' => 'PC', 'price' => 100], ])->whereNotBetween('price', [100, 200]); + */ + public function whereNotBetween($key, $values) + { + return $this->filter(function ($item) use ($key, $values) { + return $this->arr_data_get($item, $key) < reset($values) || $this->arr_data_get($item, $key) > end($values); + }); + } + + /** + * Filter items by the given key value pair. + * + * Example + * Collection::collect([ ['product' => 'PC', 'price' => 200], ['product' => 'Mobile', 'price' => 100], ['product' => 'Phone', 'price' => 150], ['product' => 'TV', 'price' => 100], ])->whereNotIn('price', [150, 200]); + */ + public function whereNotIn($key, $values, $strict = false) + { + return $this->reject(function ($item) use ($key, $values, $strict) { + return in_array($this->arr_data_get($item, $key), $values, $strict); + }); + } + + /** + * Filter items by the given key value pair using strict comparison. + * + * Example + * Collection::collect([ ['product' => 'PC', 'price' => 200], ['product' => 'Mobile', 'price' => 100], ['product' => 'Phone', 'price' => 150], ['product' => 'TV', 'price' => 100], ])->whereNotInStrict('price', [150, 200]); + */ + public function whereNotInStrict($key, $values) + { + return $this->whereNotIn($key, $values, true); + } + + /** + * Filter items where the value for the given key is not null. + * + * Example + * Collection::collect([ ['name' => 'pc'], ['name' => null], ['name' => 'phone'], ])->whereNotNull('name'); + */ + public function whereNotNull($key = null) + { + return $this->where($key, '!==', null); + } + + /** + * Filter items where the value for the given key is null. + * + * Example + * Collection::collect([ ['name' => 'pc'], ['name' => null], ['name' => 'phone'], ])->whereNull('name'); + */ + public function whereNull($key = null) + { + return $this->whereStrict($key, null); + } + + /** + * Zip the collection together with one or more arrays. + * + * Example + * Collection::collect(['Phone', 'PC'])->zip([100, 200]); + */ + public function zip($items) + { + $arrayableItems = array_map(function ($items) { + return $items; + }, func_get_args()); + + $params = array_merge([function () { + return $this->collect(func_get_args()); + }, $this->list], $arrayableItems); + + return $this->collect(array_map(...$params)); + } + + /** + * Apply the callback if the value is truthy. + * + * Example + * Collection::collect([1,2,3])->when(true, function ($map) {return $map->push(4);}); + */ + public function when($value, callable $callback = null, callable $default = null) + { + if (! $callback) { + return new WhenProxy($this, $value); + } + + if ($value) { + return $callback($this, $value); + } elseif ($default) { + return $default($this, $value); + } + + return clone($this); + } + + /** + * Apply the callback if the collection is empty. + * + * Example + * Collection::collect([])->whenEmpty(function ($map) { return $map->push('Hassan'); }); + */ + public function whenEmpty(callable $callback, callable $default = null) + { + return $this->when(empty($this->list), $callback, $default); + } + + /** + * Apply the callback if the collection is not empty. + * + * Example + * Collection::collect(['Ahmed', 'Mohammed'])->whenNotEmpty(function ($map) { return $map->push('Hassan'); }); + */ + public function whenNotEmpty(callable $callback, callable $default = null) + { + return $this->when(!empty($this->list), $callback, $default); + } + + /** + * Create a collection with the given range. + * + * Example + * Collection::range(1, 10); + */ + public function range($from, $to) + { + return $this->collect(range($from, $to)); + } + +} \ No newline at end of file diff --git a/src/Data/Collection/ResponseTrait.php b/src/Data/Collection/ResponseTrait.php new file mode 100644 index 0000000..8500b46 --- /dev/null +++ b/src/Data/Collection/ResponseTrait.php @@ -0,0 +1,64 @@ +list : $item = $items; + return json_decode(json_encode($item), true); + } + + /** + * Returns a object of the given elements. + * + */ + public function toObject() + { + $result = json_decode(json_encode($this->list), false); + return is_object($result) ? $result : null; + } + + /** + * Returns a json of the given elements. + * + */ + public function toJson() + { + return json_encode($this->list); + } + +} \ No newline at end of file diff --git a/src/Data/Collection/ReturnsTrait.php b/src/Data/Collection/ReturnsTrait.php new file mode 100644 index 0000000..ea97221 --- /dev/null +++ b/src/Data/Collection/ReturnsTrait.php @@ -0,0 +1,335 @@ +avg(); + * Collection::collect([['price' => 10],['price' => 10],['price' => 20],['price' => 40]])->avg("price"); + */ + public function avg($key='') + { + if(!empty($key)) { + $count = sizeof($this->list); + $average = array_sum(array_column($this->list, $key)) / $count; + }else{ + $arr = array_filter($this->list); + $average = array_sum($arr)/count($arr); + } + + return $average; + } + + /** + * All + * + * Get all of the items in the collection. + * Example + * Collection::collect([1, 1, 2, 4])->all(); + */ + public function all() + { + return $this->list; + } + + /** + * Contains + * Determine if an item (value) exists in the collection. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->contains(1); + * Collection::collect(['Name'=>"Hassan", 'Country'=>"Egypt"])->contains("Egypt"); + */ + public function contains($value) + { + return in_array($value, $this->list); + } + + /** + * Count the number of items in the collection. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->count(); + * Collection::collect(['Name'=>"Hassan", 'Country'=>"Egypt"])->count(); + */ + public function count() + { + return count($this->list); + } + + /** + * Count the number of items in the collection by a field. + * + * Example + * Collection::collect([1, 2, 3, 3])->countBy(3); + */ + public function countBy($field=null) + { + if(!is_null($field)){ + return array_count_values($this->list)[$field] ?? false; + } + + return array_count_values($this->list); + } + + /** + * Determine if all items pass the given truth test. + * + * Example + * Collection::collect([1, 2, 3, 4])->every(function ($value, $key) { return $value > 2; }); + */ + public function every($key, $operator = null, $value = null) + { + if (func_num_args() === 1) { + $callback = $key; + + foreach ($this as $k => $v) { + if (! $callback($v, $k)) { + return false; + } + } + + return true; + } + + return $this->every($this->operatorForWhere(...func_get_args())); + } + + /** + * Convert the array into a query string. + * + * Example + * Collection::collect(['id'=>1, "page"=>2])->query(); + */ + public function query() + { + return http_build_query($this->list, '', '&', PHP_QUERY_RFC3986); + } + + /** + * Determine if an item exists in the collection by key. + * + * Example + * Collection::collect(["name"=>"hassan", "country"=>"egypt"])->has("name", "country"); + * Collection::collect(["name"=>"hassan", "country"=>"egypt"])->has(["name", "age"]); + * Collection::collect(["name"=>"hassan", "country"=>"egypt"])->has("country"); + */ + public function has($keys) + { + $keys = is_array($keys) ? $keys : func_get_args(); + + $res = true; + + foreach($keys as $key){ + if($res === false){ + break; + } + if (!isset($this->list[$key])) { + $res = false; + } + } + + return $res; + } + + /** + * Concatenate values of a given key as a string. + * + * Example + * Collection::collect([1,2,3])->implode(); + * Collection::collect([1,2,3])->implode("-"); + * Collection::collect(["name"=>"hassan", "country"=>"egypt"])->implode("-"); + */ + public function implode($value='') + { + return implode($value, $this->list); + } + + /** + * Determine if the collection is empty or not. + * + * Example + * Collection::collect([])->isEmpty(); + */ + public function isEmpty() + { + return empty($this->list); + } + + /** + * Determine if the collection is not empty. + * + * Example + * Collection::collect([])->isNotEmpty(); + */ + public function isNotEmpty() + { + return ! $this->isEmpty(); + } + + /** + * Get the max value of a given key. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->max(); + * Collection::collect([ ['foo' => 10], ['foo' => 20] ])->max(); + * Collection::collect([ ['foo' => 10], ['foo' => 20], ['boo' => 30], ['boo' => 40] ])->max("boo"); + */ + public function max($column=null){ + + if(!is_null($column)){ + $res = array_column($this->list, $column); + }else { + $res = $this->list; + } + + $res = max(array_values($res)); + $res = (is_array($res)) ? array_values($res)[0] : $res; + + return $res; + } + + /** + * Get the min value of a given key. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->min(); + * Collection::collect([ ['foo' => 10], ['foo' => 20] ])->min(); + * Collection::collect([ ['foo' => 10], ['foo' => 20], ['boo' => 30], ['boo' => 40] ])->min("boo"); + */ + public function min($column=null){ + + if(!is_null($column)){ + $res = array_column($this->list, $column); + }else { + $res = $this->list; + } + + $res = min(array_values($res)); + $res = (is_array($res)) ? array_values($res)[0] : $res; + + return $res; + } + + /** + * Pass the collection to the given callback and return the result. + * + * Example + * Collection::collect([1, 2, 3])->pipe(function ($map) { return $Collection::all(); }); + */ + public function pipe(callable $callback) + { + return $callback($this); + } + + /** + * Get and remove the last item from the collection. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->pop(); + */ + public function pop() + { + return array_pop($this->list); + } + + /** + * Search the collection for a given value and return the corresponding key if successful. + * + * Example + * Collection::collect([2, 4, 6, 8])->search(8); + * Collection::collect([2, 4, 6, 8])->search(4, $strict = true); + * Collection::collect([2, 4, 6, 8])->search(function ($item, $key) { return $item > 5; }); + * + */ + public function search($value, $strict = false) + { + if (!is_callable($value)) { + return array_search($value, $this->list, $strict); + } + + foreach ($this->list as $key => $item) { + if ($value($item, $key)) { + return $key; + } + } + + return false; + } + + /** + * Get the sum of the given values. + * + * Example + * Collection::collect([1, 2, 3, 4, 5])->sum(); + * Collection::collect([ ['name' => 'tv', 'price' => 1], ['name' => 'phone', 'price' => 2], ])->sum(); + */ + public function sum($column='') + { + if(empty($column)){ + return array_sum($this->list); + }else { + $sumDetail = $column; + $total = array_reduce($this->list, + function($runningTotal, $record) use($sumDetail) { + $runningTotal += $record[$sumDetail]; + return $runningTotal; + }, + 0 + ); + return $total; + } + } + + /** + * Returns the values from a single column. + * + * Example + * Collection::collect([ ['name' => 'tv', 'price' => 1], ['name' => 'phone', 'price' => 2], ])->column('name'); + */ + public function column(string $column) + { + return array_column($this->list, $column); + } + + /* + * Returns a new object created from the resource. + * + */ + public function resource(callable $function) + { + // Data + $data = array(); + + // Map + foreach($this->list as $array){ + $item = json_decode(json_encode($array), false); + $result = call_user_func_array($function, [$item]); + $data[] = $result; + } + + // Return + return json_decode(json_encode($data), false); + } +} diff --git a/src/Data/Collection/WhenProxy.php b/src/Data/Collection/WhenProxy.php new file mode 100644 index 0000000..512884e --- /dev/null +++ b/src/Data/Collection/WhenProxy.php @@ -0,0 +1,76 @@ +condition = $condition; + $this->collection = $collection; + } + + /** + * Proxy accessing an attribute onto the collection. + * + * @param string $key + * @return mixed + */ + public function __get($key) + { + return $this->condition + ? $this->collection->{$key} + : $this->collection; + } + + /** + * Proxy a method call onto the collection. + * + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->condition + ? $this->collection->{$method}(...$parameters) + : $this->collection; + } + +} \ No newline at end of file diff --git a/src/Data/Crawl.php b/src/Data/Crawl.php new file mode 100644 index 0000000..11cc0c4 --- /dev/null +++ b/src/Data/Crawl.php @@ -0,0 +1,530 @@ +registerNodeClass('DOMElement', Element::class); + } + + /** + * Load HTML from URL. + * + */ + public function url($url){ + $content = file_get_contents($url); + + $this->content = $content; + + return $this->loadHTML($content); + } + + /** + * Load HTML from file. + * + */ + public function file($path){ + $path = $this->filesystemRoot() . $path; + + $content = file_get_contents($path); + + $this->content = $content; + + return $this->loadHTML($content); + } + + /** + * Load HTML from a string. + * + */ + public function string(string $string){ + $this->content = $string; + + return $this->loadHTML($string); + } + + /** + * Load HTML from a string. + * + */ + public function loadHTML($source, $options = 0) + { + // Enables libxml errors handling + $internalErrorsOptionValue = libxml_use_internal_errors(); + if ($internalErrorsOptionValue === false) { + libxml_use_internal_errors(true); + } + + $source = trim($source); + + // Add CDATA around script tags content + $matches = null; + preg_match_all('//', $source, $matches); + if (isset($matches[0])) { + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $match) { + if (substr($match, -2, 1) !== '/') { // check if ends with /> + $source = str_replace($match, $match . '', '-html5-dom-document-internal-cdata]]>', $source); // Add CDATA before the end tag + $source = str_replace('', '', $source); // Clean empty script tags + $matches = null; + preg_match_all('/\/s', $source, $matches); + if (isset($matches[0])) { + $matches[0] = array_unique($matches[0]); + foreach ($matches[0] as $match) { + if (strpos($match, '/', $source) === 0 && preg_match('/\/', $source) === 0 && preg_match('/\/', $source) === 0 && preg_match('/\/', $source) === 0) { + $source = '' . $source . ''; + } + + // Add DOCTYPE if missing + if ($autoAddDoctype && strtoupper(substr($source, 0, 9)) !== '\n" . $source; + } + + // Adds temporary head tag + $charsetTag = ''; + $matches = []; + preg_match('/\/', $source, $matches); + $removeHeadTag = false; + $removeHtmlTag = false; + if (isset($matches[0])) { // has head tag + $insertPosition = strpos($source, $matches[0]) + strlen($matches[0]); + $source = substr($source, 0, $insertPosition) . $charsetTag . substr($source, $insertPosition); + } else { + $matches = []; + preg_match('/\/', $source, $matches); + if (isset($matches[0])) { // has html tag + $source = str_replace($matches[0], $matches[0] . '' . $charsetTag . '', $source); + } else { + $source = '' . $charsetTag . '' . $source; + $removeHtmlTag = true; + } + $removeHeadTag = true; + } + + // Preserve html entities + $source = preg_replace('/&([a-zA-Z]*);/', 'html5-dom-document-internal-entity1-$1-end', $source); + $source = preg_replace('/&#([0-9]*);/', 'html5-dom-document-internal-entity2-$1-end', $source); + + $result = parent::loadHTML('' . $source, $options); + if ($internalErrorsOptionValue === false) { + libxml_use_internal_errors(false); + } + if ($result === false) { + return false; + } + $this->encoding = 'utf-8'; + foreach ($this->childNodes as $item) { + if ($item->nodeType === XML_PI_NODE) { + $this->removeChild($item); + break; + } + } + /** @var Element|null */ + $metaTagElement = $this->getElementsByTagName('meta')->item(0); + if ($metaTagElement !== null) { + if ($metaTagElement->getAttribute('data-html5-dom-document-internal-attribute') === 'charset-meta') { + $headElement = $metaTagElement->parentNode; + $htmlElement = $headElement->parentNode; + $metaTagElement->parentNode->removeChild($metaTagElement); + if ($removeHeadTag && $headElement !== null && $headElement->parentNode !== null && ($headElement->firstChild === null || ($headElement->childNodes->length === 1 && $headElement->firstChild instanceof \DOMText))) { + $headElement->parentNode->removeChild($headElement); + } + if ($removeHtmlTag && $htmlElement !== null && $htmlElement->parentNode !== null && $htmlElement->firstChild === null) { + $htmlElement->parentNode->removeChild($htmlElement); + } + } + } + + $this->loaded = true; + + return clone($this); + } + + /** + * Xpath + * + */ + public function xpath(string $selector) { + $doc = new \DomDocument(); + libxml_use_internal_errors(true); + $doc->loadHTML($this->content); + $xpath = new \DomXpath($doc); + + return $xpath->query($selector); + } + + /** + * CSS selector + * + */ + public function selector(string $selector) { + return $this->xpath($this->selector_to_xpath($selector)); + } + + /** + * Convert selector css to xpath + * + */ + private function selector_to_xpath($selector) { + // remove spaces around operators + $selector = preg_replace('/\s*>\s*/', '>', $selector); + $selector = preg_replace('/\s*~\s*/', '~', $selector); + $selector = preg_replace('/\s*\+\s*/', '+', $selector); + $selector = preg_replace('/\s*,\s*/', ',', $selector); + $selectors = preg_split('/\s+(?![^\[]+\])/', $selector); + + foreach ($selectors as &$selector) { + // , + $selector = preg_replace('/,/', '|descendant-or-self::', $selector); + // + + $selector = preg_replace('/\+([_\w-]+[_\w\d-]*)/', '/following-sibling::\1[position()=1]', $selector); + // input:checked, :disabled, etc. + $selector = preg_replace('/(.+)?:(checked|disabled|required|autofocus)/', '\1[@\2="\2"]', $selector); + // input:autocomplete, :autocomplete + $selector = preg_replace('/(.+)?:(autocomplete)/', '\1[@\2="on"]', $selector); + // input:button, input:submit, etc. + $selector = preg_replace('/:(text|password|checkbox|radio|button|submit|reset|file|hidden|image|datetime|datetime-local|date|month|time|week|number|range|email|url|search|tel|color)/', 'input[@type="\1"]', $selector); + // foo[id] + $selector = preg_replace('/(\w+)\[([_\w-]+[_\w\d-]*)\]/', '\1[@\2]', $selector); + // [id] + $selector = preg_replace('/\[([_\w-]+[_\w\d-]*)\]/', '*[@\1]', $selector); + // foo[id=foo] + $selector = preg_replace('/\[([_\w-]+[_\w\d-]*)=[\'"]?(.*?)[\'"]?\]/', '[@\1="\2"]', $selector); + // [id=foo] + $selector = preg_replace('/^\[/', '*[', $selector); + // div#foo + $selector = preg_replace('/([_\w-]+[_\w\d-]*)\#([_\w-]+[_\w\d-]*)/', '\1[@id="\2"]', $selector); + // #foo + $selector = preg_replace('/\#([_\w-]+[_\w\d-]*)/', '*[@id="\1"]', $selector); + // div.foo + $selector = preg_replace('/([_\w-]+[_\w\d-]*)\.([_\w-]+[_\w\d-]*)/', '\1[contains(concat(" ",@class," ")," \2 ")]', $selector); + // .foo + $selector = preg_replace('/\.([_\w-]+[_\w\d-]*)/', '*[contains(concat(" ",@class," ")," \1 ")]', $selector); + // div:first-child + $selector = preg_replace('/([_\w-]+[_\w\d-]*):first-child/', '*/\1[position()=1]', $selector); + // div:last-child + $selector = preg_replace('/([_\w-]+[_\w\d-]*):last-child/', '*/\1[position()=last()]', $selector); + // :first-child + $selector = str_replace(':first-child', '*/*[position()=1]', $selector); + // :last-child + $selector = str_replace(':last-child', '*/*[position()=last()]', $selector); + // :nth-last-child + $selector = preg_replace('/:nth-last-child\((\d+)\)/', '[position()=(last() - (\1 - 1))]', $selector); + // div:nth-child + $selector = preg_replace('/([_\w-]+[_\w\d-]*):nth-child\((\d+)\)/', '\1[(count(preceding-sibling::*)+1) = \2]', $selector); + // :nth-child + $selector = preg_replace('/:nth-child\((\d+)\)/', '*/*[position()=\1]', $selector); + // :contains(Foo) + $selector = preg_replace('/([_\w-]+[_\w\d-]*):contains\((.*?)\)/', '\1[contains(string(.),"\2")]', $selector); + // > + $selector = preg_replace('/>/', '/', $selector); + // ~ + $selector = preg_replace('/~/', '/following-sibling::', $selector); + // Finish + $selector = str_replace(']*', ']', $selector); + $selector = str_replace(']/*', ']', $selector); + } + + // ' ' + $selector = implode('/descendant::', $selectors); + $selector = 'descendant-or-self::' . $selector; + // :scope + $selector = preg_replace('/(((\|)?descendant-or-self::):scope)/', '.\3', $selector); + // $element + $sub_selectors = explode(',', $selector); + + foreach ($sub_selectors as $key => $sub_selector) { + $parts = explode('$', $sub_selector); + $sub_selector = array_shift($parts); + + if (count($parts) && preg_match_all('/((?:[^\/]*\/?\/?)|$)/', $parts[0], $matches)) { + $results = $matches[0]; + $results[] = str_repeat('/..', count($results) - 2); + $sub_selector .= implode('', $results); + } + + $sub_selectors[$key] = $sub_selector; + } + + $selector = implode(',', $sub_selectors); + + return $selector; + } + + /** + * Adds the HTML tag to the document if missing. + * + */ + private function addHtmlElementIfMissing(): bool + { + if ($this->getElementsByTagName('html')->length === 0) { + if (!isset(self::$newObjectsCache['htmlelement'])) { + self::$newObjectsCache['htmlelement'] = new \DOMElement('html'); + } + $this->appendChild(clone (self::$newObjectsCache['htmlelement'])); + return true; + } + return false; + } + + /** + * Adds the HEAD tag to the document if missing. + * + */ + private function addHeadElementIfMissing(): bool + { + if ($this->getElementsByTagName('head')->length === 0) { + $htmlElement = $this->getElementsByTagName('html')->item(0); + if (!isset(self::$newObjectsCache['headelement'])) { + self::$newObjectsCache['headelement'] = new \DOMElement('head'); + } + $headElement = clone (self::$newObjectsCache['headelement']); + if ($htmlElement->firstChild === null) { + $htmlElement->appendChild($headElement); + } else { + $htmlElement->insertBefore($headElement, $htmlElement->firstChild); + } + return true; + } + return false; + } + + /** + * Adds the BODY tag to the document if missing. + * + */ + private function addBodyElementIfMissing(): bool + { + if ($this->getElementsByTagName('body')->length === 0) { + if (!isset(self::$newObjectsCache['bodyelement'])) { + self::$newObjectsCache['bodyelement'] = new \DOMElement('body'); + } + $this->getElementsByTagName('html')->item(0)->appendChild(clone (self::$newObjectsCache['bodyelement'])); + return true; + } + return false; + } + + /** + * Dumps the internal document into a string using HTML formatting. + * + */ + public function source(\DOMNode $node = null): string + { + $nodeMode = $node !== null; + if ($nodeMode && $node instanceof \DOMDocument) { + $nodeMode = false; + } + + if ($nodeMode) { + if (!isset(self::$newObjectsCache['domdocument'])) { + self::$newObjectsCache['domdocument'] = new $this(); + } + $tempDomDocument = clone (self::$newObjectsCache['domdocument']); + if ($node->nodeName === 'html') { + $tempDomDocument->loadHTML(''); + $tempDomDocument->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->source(); + $html = substr($html, 16); // remove the DOCTYPE + the new line after + } elseif ($node->nodeName === 'head' || $node->nodeName === 'body') { + $tempDomDocument->loadHTML("\n"); + $tempDomDocument->childNodes[1]->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->source(); + $html = substr($html, 22, -7); // remove the DOCTYPE + the new line after + html tag + } else { + $isInHead = false; + $parentNode = $node; + for ($i = 0; $i < 1000; $i++) { + $parentNode = $parentNode->parentNode; + if ($parentNode === null) { + break; + } + if ($parentNode->nodeName === 'body') { + break; + } elseif ($parentNode->nodeName === 'head') { + $isInHead = true; + break; + } + } + $tempDomDocument->loadHTML("\n" . ($isInHead ? '' : '') . ''); + $tempDomDocument->childNodes[1]->childNodes[0]->appendChild($tempDomDocument->importNode(clone ($node), true)); + $html = $tempDomDocument->source(); + $html = substr($html, 28, -14); // remove the DOCTYPE + the new line + html + body or head tags + } + $html = trim($html); + } else { + $removeHtmlElement = false; + $removeHeadElement = false; + $headElement = $this->getElementsByTagName('head')->item(0); + if ($headElement === null) { + if ($this->addHtmlElementIfMissing()) { + $removeHtmlElement = true; + } + if ($this->addHeadElementIfMissing()) { + $removeHeadElement = true; + } + $headElement = $this->getElementsByTagName('head')->item(0); + } + $meta = $this->createElement('meta'); + $meta->setAttribute('data-html5-dom-document-internal-attribute', 'charset-meta'); + $meta->setAttribute('http-equiv', 'content-type'); + $meta->setAttribute('content', 'text/html; charset=utf-8'); + if ($headElement->firstChild !== null) { + $headElement->insertBefore($meta, $headElement->firstChild); + } else { + $headElement->appendChild($meta); + } + $html = parent::saveHTML(); + $html = rtrim($html, "\n"); + + if ($removeHeadElement) { + $headElement->parentNode->removeChild($headElement); + } else { + $meta->parentNode->removeChild($meta); + } + + if (strpos($html, 'html5-dom-document-internal-entity') !== false) { + $html = preg_replace('/html5-dom-document-internal-entity1-(.*?)-end/', '&$1;', $html); + $html = preg_replace('/html5-dom-document-internal-entity2-(.*?)-end/', '&#$1;', $html); + } + + $codeToRemove = [ + 'html5-dom-document-internal-content', + '', + '', '', '
', '', '', '', '', '', '', '', '', '', '', '', '', '', + '', '-html5-dom-document-internal-cdata-endtagfix' + ]; + if ($removeHeadElement) { + $codeToRemove[] = ''; + } + if ($removeHtmlElement) { + $codeToRemove[] = ''; + } + + $html = str_replace($codeToRemove, '', $html); + } + return $html; + } + + /** + * Returns the first document element matching the selector. + * + */ + public function first(string $selector) + { + return $this->internalQuerySelector($selector); + } + + /** + * Returns a list of document elements matching the selector. + * + */ + public function get(string $selector) + { + return $this->internalQuerySelectorAll($selector); + } + +} diff --git a/src/Data/Crawl/Element.php b/src/Data/Crawl/Element.php new file mode 100644 index 0000000..70bb55f --- /dev/null +++ b/src/Data/Crawl/Element.php @@ -0,0 +1,166 @@ + $match) { + $search[] = $match; + $replace[] = html_entity_decode(($matches[1][$i] === '1' ? '&' : '&#') . $matches[2][$i] . ';'); + } + $value = str_replace($search, $replace, $value); + self::$foundEntitiesCache[0] = array_merge(self::$foundEntitiesCache[0], $search); + self::$foundEntitiesCache[1] = array_merge(self::$foundEntitiesCache[1], $replace); + unset($search); + unset($replace); + unset($matches); + } + return $value; + } + + /** + * Returns the updated nodeValue Property + * + */ + public function value(): string + { + return $this->updateResult($this->nodeValue); + } + + /** + * Returns the updated $textContent Property + * + */ + public function text(): string + { + return $this->updateResult($this->textContent); + } + + /** + * Returns html source code + * + */ + public function html(): string + { + if ($this->firstChild === null) { + $nodeName = $this->nodeName; + $attributes = $this->attributes(); + $result = '<' . $nodeName . ''; + foreach ($attributes as $name => $value) { + $result .= ' ' . $name . '="' . htmlentities($value) . '"'; + } + if (array_search($nodeName, ['area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'meta', 'param', 'source', 'track', 'wbr']) === false) { + $result .= '>'; + } else { + $result .= '/>'; + } + return $result; + } + return $this->ownerDocument->source($this); + } + + /** + * Returns the value for the attribute name specified. + * + */ + public function attribute($name): string + { + if ($this->attributes->length === 0) { // Performance optimization + return ''; + } + $value = parent::getAttribute($name); + return $value !== '' ? (strstr($value, 'html5-dom-document-internal-entity') !== false ? $this->updateResult($value) : $value) : ''; + } + + /** + * Returns an array containing all attributes. + * + */ + public function attributes(): array + { + $attributes = []; + foreach ($this->attributes as $attributeName => $attribute) { + $value = $attribute->value; + $attributes[$attributeName] = $value !== '' ? (strstr($value, 'html5-dom-document-internal-entity') !== false ? $this->updateResult($value) : $value) : ''; + } + return $attributes; + } + + /** + * Returns the element outerHTML. + * + */ + public function __toString(): string + { + return $this->outerHTML; + } + + /** + * Returns the first child element matching the selector. + * + */ + public function first(string $selector) + { + return $this->internalQuerySelector($selector); + } + + /** + * Returns a list of children elements matching the selector. + * + */ + public function get(string $selector) + { + return $this->internalQuerySelectorAll($selector); + } +} diff --git a/src/Data/Crawl/NodeList.php b/src/Data/Crawl/NodeList.php new file mode 100644 index 0000000..7f8e306 --- /dev/null +++ b/src/Data/Crawl/NodeList.php @@ -0,0 +1,44 @@ +offsetExists($index) ? $this->offsetGet($index) : null; + } + + /** + * Returns the value for the property specified. + * + */ + public function __get(string $name) + { + if ($name === 'length') { + return sizeof($this); + } + throw new \Exception('Undefined property: ' . $name); + } +} diff --git a/src/Data/Crawl/QuerySelectors.php b/src/Data/Crawl/QuerySelectors.php new file mode 100644 index 0000000..7740173 --- /dev/null +++ b/src/Data/Crawl/QuerySelectors.php @@ -0,0 +1,530 @@ +internalQuerySelectorAll($selector, 1); + return $result->item(0); + } + + /** + * Returns a list of document elements matching the selector. + * + */ + private function internalQuerySelectorAll(string $selector, $preferredLimit = null) + { + $selector = trim($selector); + + $cache = []; + $walkChildren = function (\DOMNode $context, $tagNames, callable $callback) use (&$cache) { + if (!empty($tagNames)) { + $children = []; + foreach ($tagNames as $tagName) { + $elements = $context->getElementsByTagName($tagName); + foreach ($elements as $element) { + $children[] = $element; + } + } + } else { + $getChildren = function () use ($context) { + $result = []; + $process = function (\DOMNode $node) use (&$process, &$result) { + foreach ($node->childNodes as $child) { + if ($child instanceof \DOMElement) { + $result[] = $child; + $process($child); + } + } + }; + $process($context); + return $result; + }; + if ($this === $context) { + $cacheKey = 'walk_children'; + if (!isset($cache[$cacheKey])) { + $cache[$cacheKey] = $getChildren(); + } + $children = $cache[$cacheKey]; + } else { + $children = $getChildren(); + } + } + foreach ($children as $child) { + if ($callback($child) === true) { + return true; + } + } + }; + + $getElementById = function (\DOMNode $context, $id, $tagName) use (&$walkChildren) { + if ($context instanceof \DOMDocument) { + $element = $context->getElementById($id); + if ($element && ($tagName === null || $element->tagName === $tagName)) { + return $element; + } + } else { + $foundElement = null; + $walkChildren($context, $tagName !== null ? [$tagName] : null, function ($element) use ($id, &$foundElement) { + if ($element->attributes->length > 0 && $element->getAttribute('id') === $id) { + $foundElement = $element; + return true; + } + }); + return $foundElement; + } + return null; + }; + + $simpleSelectors = []; + + // all + $simpleSelectors['\*'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + if ($mode === 'validate') { + return true; + } else { + $walkChildren($context, [], function ($element) use ($add) { + if ($add($element)) { + return true; + } + }); + } + }; + + // tagname + $simpleSelectors['[a-zA-Z0-9\-]+'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $tagNames = []; + foreach ($matches as $match) { + $tagNames[] = strtolower($match[0]); + } + if ($mode === 'validate') { + return array_search($context->tagName, $tagNames) !== false; + } + $walkChildren($context, $tagNames, function ($element) use ($add) { + if ($add($element)) { + return true; + } + }); + }; + + // tagname[target] or [target] // Available values for targets: attr, attr="value", attr~="value", attr|="value", attr^="value", attr$="value", attr*="value" + $simpleSelectors['(?:[a-zA-Z0-9\-]*)(?:\[.+?\])'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $run = function ($match) use ($mode, $context, $add, $walkChildren) { + $attributeSelectors = explode('][', substr($match[2], 1, -1)); + foreach ($attributeSelectors as $i => $attributeSelector) { + $attributeSelectorMatches = null; + if (preg_match('/^(.+?)(=|~=|\|=|\^=|\$=|\*=)\"(.+?)\"$/', $attributeSelector, $attributeSelectorMatches) === 1) { + $attributeSelectors[$i] = [ + 'name' => strtolower($attributeSelectorMatches[1]), + 'value' => $attributeSelectorMatches[3], + 'operator' => $attributeSelectorMatches[2] + ]; + } else { + $attributeSelectors[$i] = [ + 'name' => $attributeSelector + ]; + } + } + $tagName = strlen($match[1]) > 0 ? strtolower($match[1]) : null; + $check = function ($element) use ($attributeSelectors) { + if ($element->attributes->length > 0) { + foreach ($attributeSelectors as $attributeSelector) { + $isMatch = false; + $attributeValue = $element->getAttribute($attributeSelector['name']); + if (isset($attributeSelector['value'])) { + $valueToMatch = $attributeSelector['value']; + switch ($attributeSelector['operator']) { + case '=': + if ($attributeValue === $valueToMatch) { + $isMatch = true; + } + break; + case '~=': + $words = preg_split("/[\s]+/", $attributeValue); + if (array_search($valueToMatch, $words) !== false) { + $isMatch = true; + } + break; + + case '|=': + if ($attributeValue === $valueToMatch || strpos($attributeValue, $valueToMatch . '-') === 0) { + $isMatch = true; + } + break; + + case '^=': + if (strpos($attributeValue, $valueToMatch) === 0) { + $isMatch = true; + } + break; + + case '$=': + if (substr($attributeValue, -strlen($valueToMatch)) === $valueToMatch) { + $isMatch = true; + } + break; + + case '*=': + if (strpos($attributeValue, $valueToMatch) !== false) { + $isMatch = true; + } + break; + } + } else { + if ($attributeValue !== '') { + $isMatch = true; + } + } + if (!$isMatch) { + return false; + } + } + return true; + } + return false; + }; + if ($mode === 'validate') { + return ($tagName === null ? true : $context->tagName === $tagName) && $check($context); + } else { + $walkChildren($context, $tagName !== null ? [$tagName] : null, function ($element) use ($check, $add) { + if ($check($element)) { + if ($add($element)) { + return true; + } + } + }); + } + }; + // todo optimize + foreach ($matches as $match) { + if ($mode === 'validate') { + if ($run($match)) { + return true; + } + } else { + $run($match); + } + } + if ($mode === 'validate') { + return false; + } + }; + + // tagname#id or #id + $simpleSelectors['(?:[a-zA-Z0-9\-]*)#(?:[a-zA-Z0-9\-\_]+?)'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($getElementById) { + $run = function ($match) use ($mode, $context, $add, $getElementById) { + $tagName = strlen($match[1]) > 0 ? strtolower($match[1]) : null; + $id = $match[2]; + if ($mode === 'validate') { + return ($tagName === null ? true : $context->tagName === $tagName) && $context->getAttribute('id') === $id; + } else { + $element = $getElementById($context, $id, $tagName); + if ($element) { + $add($element); + } + } + }; + // todo optimize + foreach ($matches as $match) { + if ($mode === 'validate') { + if ($run($match)) { + return true; + } + } else { + $run($match); + } + } + if ($mode === 'validate') { + return false; + } + }; + + // tagname.classname, .classname, tagname.classname.classname2, .classname.classname2 + $simpleSelectors['(?:[a-zA-Z0-9\-]*)\.(?:[a-zA-Z0-9\-\_\.]+?)'] = function (string $mode, array $matches, \DOMNode $context, callable $add = null) use ($walkChildren) { + $rawData = []; // Array containing [tag, classnames] + $tagNames = []; + foreach ($matches as $match) { + $tagName = strlen($match[1]) > 0 ? $match[1] : null; + $classes = explode('.', $match[2]); + if (empty($classes)) { + continue; + } + $rawData[] = [$tagName, $classes]; + if ($tagName !== null) { + $tagNames[] = $tagName; + } + } + $check = function ($element) use ($rawData) { + if ($element->attributes->length > 0) { + $classAttribute = ' ' . $element->getAttribute('class') . ' '; + $tagName = $element->tagName; + foreach ($rawData as $rawMatch) { + if ($rawMatch[0] !== null && $tagName !== $rawMatch[0]) { + continue; + } + $allClassesFound = true; + foreach ($rawMatch[1] as $class) { + if (strpos($classAttribute, ' ' . $class . ' ') === false) { + $allClassesFound = false; + break; + } + } + if ($allClassesFound) { + return true; + } + } + } + return false; + }; + if ($mode === 'validate') { + return $check($context); + } + $walkChildren($context, $tagNames, function ($element) use ($check, $add) { + if ($check($element)) { + if ($add($element)) { + return true; + } + } + }); + }; + + $isMatchingElement = function (\DOMNode $context, string $selector) use ($simpleSelectors) { + foreach ($simpleSelectors as $simpleSelector => $callback) { + $match = null; + if (preg_match('/^' . (str_replace('?:', '', $simpleSelector)) . '$/', $selector, $match) === 1) { + return call_user_func($callback, 'validate', [$match], $context); + } + } + }; + + $complexSelectors = []; + + $getMatchingElements = function (\DOMNode $context, string $selector, $preferredLimit = null) use (&$simpleSelectors, &$complexSelectors) { + + $processSelector = function (string $mode, string $selector, $operator = null) use (&$processSelector, $simpleSelectors, $complexSelectors, $context, $preferredLimit) { + $supportedSimpleSelectors = array_keys($simpleSelectors); + $supportedSimpleSelectorsExpression = '(?:(?:' . implode(')|(?:', $supportedSimpleSelectors) . '))'; + $supportedSelectors = $supportedSimpleSelectors; + $supportedComplexOperators = array_keys($complexSelectors); + if ($operator === null) { + $operator = ','; + foreach ($supportedComplexOperators as $complexOperator) { + array_unshift($supportedSelectors, '(?:(?:(?:' . $supportedSimpleSelectorsExpression . '\s*\\' . $complexOperator . '\s*))+' . $supportedSimpleSelectorsExpression . ')'); + } + } + $supportedSelectorsExpression = '(?:(?:' . implode(')|(?:', $supportedSelectors) . '))'; + + $vallidationExpression = '/^(?:(?:' . $supportedSelectorsExpression . '\s*\\' . $operator . '\s*))*' . $supportedSelectorsExpression . '$/'; + if (preg_match($vallidationExpression, $selector) !== 1) { + return false; + } + $selector .= $operator; // append the seprator at the back for easier matching below + + $result = []; + if ($mode === 'execute') { + $add = function ($element) use ($preferredLimit, &$result) { + $found = false; + foreach ($result as $addedElement) { + if ($addedElement === $element) { + $found = true; + break; + } + } + if (!$found) { + $result[] = $element; + if ($preferredLimit !== null && sizeof($result) >= $preferredLimit) { + return true; + } + } + return false; + }; + } + + $selectorsToCall = []; + $addSelectorToCall = function ($type, $selector, $argument) use (&$selectorsToCall) { + $previousIndex = sizeof($selectorsToCall) - 1; + // todo optimize complex too + if ($type === 1 && isset($selectorsToCall[$previousIndex]) && $selectorsToCall[$previousIndex][0] === $type && $selectorsToCall[$previousIndex][1] === $selector) { + $selectorsToCall[$previousIndex][2][] = $argument; + } else { + $selectorsToCall[] = [$type, $selector, [$argument]]; + } + }; + for ($i = 0; $i < 100000; $i++) { + $matches = null; + preg_match('/^(?' . $supportedSelectorsExpression . ')\s*\\' . $operator . '\s*/', $selector, $matches); // getting the next subselector + if (isset($matches['subselector'])) { + $subSelector = $matches['subselector']; + $selectorFound = false; + foreach ($simpleSelectors as $simpleSelector => $callback) { + $match = null; + if (preg_match('/^' . (str_replace('?:', '', $simpleSelector)) . '$/', $subSelector, $match) === 1) { // if simple selector + if ($mode === 'parse') { + $result[] = $match[0]; + } else { + $addSelectorToCall(1, $simpleSelector, $match); + //call_user_func($callback, 'execute', $match, $context, $add); + } + $selectorFound = true; + break; + } + } + if (!$selectorFound) { + foreach ($complexSelectors as $complexOperator => $callback) { + $subSelectorParts = $processSelector('parse', $subSelector, $complexOperator); + if ($subSelectorParts !== false) { + $addSelectorToCall(2, $complexOperator, $subSelectorParts); + //call_user_func($callback, $subSelectorParts, $context, $add); + $selectorFound = true; + break; + } + } + } + if (!$selectorFound) { + throw new \Exception('Internal error for selector "' . $selector . '"!'); + } + $selector = substr($selector, strlen($matches[0])); // remove the matched subselector and continue parsing + if (strlen($selector) === 0) { + break; + } + } + } + foreach ($selectorsToCall as $selectorToCall) { + if ($selectorToCall[0] === 1) { // is simple selector + call_user_func($simpleSelectors[$selectorToCall[1]], 'execute', $selectorToCall[2], $context, $add); + } else { // is complex selector + call_user_func($complexSelectors[$selectorToCall[1]], $selectorToCall[2][0], $context, $add); // todo optimize and send all arguments + } + } + return $result; + }; + + return $processSelector('execute', $selector); + }; + + // div p (space between) - all

elements inside

elements + $complexSelectors[' '] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + $temp = array_merge($temp, $getMatchingElements($element, $part)); + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // div > p - all

elements where the parent is a

element + $complexSelectors['>'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + foreach ($element->childNodes as $child) { + if ($child instanceof \DOMElement && $isMatchingElement($child, $part)) { + $temp[] = $child; + } + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // div + p - all

elements that are placed immediately after

elements + $complexSelectors['+'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + if ($element->nextSibling !== null && $isMatchingElement($element->nextSibling, $part)) { + $temp[] = $element->nextSibling; + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + // p ~ ul - all
    elements that are preceded by a

    element + $complexSelectors['~'] = function (array $parts, \DOMNode $context, callable $add = null) use (&$getMatchingElements, &$isMatchingElement) { + $elements = null; + foreach ($parts as $part) { + if ($elements === null) { + $elements = $getMatchingElements($context, $part); + } else { + $temp = []; + foreach ($elements as $element) { + $nextSibling = $element->nextSibling; + while ($nextSibling !== null) { + if ($isMatchingElement($nextSibling, $part)) { + $temp[] = $nextSibling; + } + $nextSibling = $nextSibling->nextSibling; + } + } + $elements = $temp; + } + } + foreach ($elements as $element) { + $add($element); + } + }; + + $result = $getMatchingElements($this, $selector, $preferredLimit); + if ($result === false) { + throw new \InvalidArgumentException('Unsupported selector (' . $selector . ')'); + } + + return new NodeList($result); + } +} diff --git a/src/Data/Crawl/TokenList.php b/src/Data/Crawl/TokenList.php new file mode 100644 index 0000000..211aa54 --- /dev/null +++ b/src/Data/Crawl/TokenList.php @@ -0,0 +1,152 @@ +element = $element; + $this->attributeName = $attributeName; + $this->previousValue = null; + $this->tokenize(); + } + + /** + * Returns an item in the list by its index (returns null if the number is greater than or equal to the length of the list). + * + */ + public function item(int $index) + { + $this->tokenize(); + if ($index >= count($this->tokens)) { + return null; + } + return $this->tokens[$index]; + } + + /** + * Returns true if the list contains the given token, otherwise false. + * + */ + public function contains(string $token): bool + { + $this->tokenize(); + return in_array($token, $this->tokens); + } + + /** + * + */ + public function __toString(): string + { + $this->tokenize(); + return implode(' ', $this->tokens); + } + + /** + * Returns an iterator allowing you to go through all tokens contained in the list. + * + */ + public function entries(): ArrayIterator + { + $this->tokenize(); + return new ArrayIterator($this->tokens); + } + + /** + * Returns the count of items + * + */ + public function count() + { + $this->tokenize(); + return count($this->tokens); + } + + /** + * + */ + private function tokenize() + { + $current = $this->element->getAttribute($this->attributeName); + if ($this->previousValue === $current) { + return; + } + $this->previousValue = $current; + $tokens = explode(' ', $current); + $finals = []; + foreach ($tokens as $token) { + if ($token === '') { + continue; + } + if (in_array($token, $finals)) { + continue; + } + $finals[] = $token; + } + $this->tokens = $finals; + } + + /** + * + */ + private function setAttributeValue() + { + $value = implode(' ', $this->tokens); + if ($this->previousValue === $value) { + return; + } + $this->previousValue = $value; + $this->element->setAttribute($this->attributeName, $value); + } +} diff --git a/src/Database/Auth.php b/src/Database/Auth.php new file mode 100644 index 0000000..dd37a14 --- /dev/null +++ b/src/Database/Auth.php @@ -0,0 +1,415 @@ +pdo = $pdo; + } + + /** + * Get connect + * + */ + public function getConnect() + { + return $this->pdo; + } + + /** + * Execute + * + */ + protected function execute($sql) + { + $db = $this->pdo->prepare($sql); + $db->execute(); + $db->setFetchMode(\PDO::FETCH_OBJ); + return $db->fetchAll()[0] ?? $db->fetchAll(); + } + + /** + * Get login session + * + */ + public function getLoginSession() + { + return $this->login_session; + } + + /** + * Set login session + * + */ + public function setLoginSession($key='auth') + { + $this->login_session = $key; + } + + /** + * Get table + * + */ + public function getTable() + { + return $this->table; + } + + /** + * Set table + * + */ + public function setTable($table) + { + return $this->table = $table; + } + + /** + * Get fields + * + */ + public function getFields() + { + return [ + "primary_field" => $this->primary_field, + "id_field" => $this->id_field, + "pass_field" => $this->pass_field, + "token_field" => $this->token_field, + "jwt_field" => $this->jwt_field + ]; + } + + /** + * Set fields + * + */ + public function setFields($primary="id", $id="email", $pass="password", $token="token", $jwt="jwt") + { + $this->primary_field = $primary; + $this->id_field = $id; + $this->pass_field = $pass; + $this->token_field = $token; + $this->jwt_field = $jwt; + } + + /** + * Set JWT header + * + */ + public function setJwtHeader($header, $prefix) + { + $this->jwt_header = $header; + $this->jwt_header_prefix = $prefix; + + return clone($this); + } + + /** + * Get JWT header + * + */ + public function getJwtHeader() + { + return [ + "header" => $this->jwt_header, + "prefix" => $this->jwt_header_prefix + ]; + } + + /* + * Attempt + * + */ + public function attempt($id, $primary_field='', $jwt=false) + { + $primary_field = (empty($primary_field)) ? $this->primary_field : $primary_field; + + $auth = $this->execute("SELECT * FROM {$this->table} WHERE {$primary_field} = '{$id}' LIMIT 1"); + + if ($auth) { + $primary_field = $this->primary_field; + + if(!$jwt){ + $_SESSION[$this->login_session] = $auth->$primary_field; + } + + return $auth; + } + + return false; + } + + /* + * Login + * + */ + public function login($id, $pass, $password_is_hash=true) + { + // Fields + $pass_field = $this->pass_field; + $primary_field = $this->primary_field; + + // Get auth + $auth = $this->execute("SELECT * FROM {$this->table} WHERE {$this->id_field} = '{$id}' LIMIT 1"); + + if($auth){ + // Password Verify + if($password_is_hash){ + if(password_verify($pass, $auth->$pass_field)) { + $_SESSION[$this->login_session] = $auth->$primary_field; + + return $auth; + } + }else{ + if($pass == $auth->$pass_field) { + $_SESSION[$this->login_session] = $auth->$primary_field; + + return $auth; + } + } + } + + return false; + } + + /* + * Logout + * + */ + public function logout() { + unset($_SESSION[$this->login_session]); + } + + /* + * Check + * + */ + public function check() { + if(isset(($_SESSION[$this->login_session]))){ + return true; + } + + return false; + } + + /* + * User + * + */ + public function user() { + if(isset(($_SESSION[$this->login_session]))){ + $key = $_SESSION[$this->login_session]; + $user = $this->execute("SELECT * FROM {$this->table} WHERE {$this->primary_field} = '{$key}' LIMIT 1"); + unset($user->{$this->pass_field}); + return $user; + } + + return false; + } + + /** + * Password hash + * + */ + public function hash($pass) + { + return password_hash($pass, PASSWORD_DEFAULT); + } + + /** + * Password verify + * + */ + public function verify($pass, $hash) + { + return password_verify($pass, $hash); + } + + /** + * Token + * + */ + public function token($id, $pass, $expire=60, $password_is_hash=true) + { + // Login + $login = $this->login($id, $pass, $password_is_hash); + + if(!$login){ return false; } + + // ID + $id = $login->{$this->primary_field}; + + // Return + return $this->generate_jwt_code($id, $expire); + } + + /** + * JWT + * + */ + public function jwt($jwt=null) + { + // JWT + if(empty($jwt)){ + $header = "HTTP_".strtoupper($this->jwt_header); + if(isset($_SERVER[$header])){ + $jwt = ltrim($_SERVER[$header], "{$this->jwt_header_prefix}"); + $jwt = trim($jwt, " "); + }else{ + return false; + } + } + + // Check token + $tokenParts = explode('.', $jwt); + + if(count($tokenParts)==3){ + $payload = json_decode(base64_decode($tokenParts[1])); + }else{ + return false; + } + + // Check isset fields + if(!isset($payload->exp) || !isset($payload->secret)){ + return false; + } + + // Check the expiration time + if(!($payload->exp - time()) < 0){ + return false; + } + + // Check login + $auth = $this->execute("SELECT * FROM {$this->table} WHERE {$this->jwt_field} = '{$jwt}' and {$this->token_field} = '{$payload->secret}' LIMIT 1"); + if(!$auth){ + return false; + } + + return $auth; + } + + /** + * JWT + * + */ + public function reToken($expire=60, $jwt=null) + { + // Check token + $auth = $this->jwt($jwt); + if(!$auth){ + return false; + } + + // ID + $id = $auth->{$this->primary_field}; + + // Return + return $this->generate_jwt_code($id, $expire); + } + + /** + * Update token + * + */ + public function updateToken($id, $expire=60) + { + return $this->generate_jwt_code($id, $expire); + } + + /* + * JWT attempt + * + */ + public function jwtAttempt($id, $primary_field='') + { + return $this->attempt($id, $primary_field, true); + } + +} \ No newline at end of file diff --git a/src/Database/Auth/Helpers.php b/src/Database/Auth/Helpers.php new file mode 100644 index 0000000..4665cc8 --- /dev/null +++ b/src/Database/Auth/Helpers.php @@ -0,0 +1,87 @@ +generate_token_jwt(); + $headers = array('alg'=>'HS256','typ'=>'JWT'); + $payload = array('sub'=>'1234567890','id'=>$id, 'secret'=>$secret, 'exp'=>(time() + $expire)); + $token = $this->generate_jwt($headers, $payload, $secret); + + // Update + $this->pdo->exec("UPDATE {$this->table} SET {$this->token_field}='{$secret}', {$this->jwt_field}='{$token}' WHERE {$this->primary_field}='{$id}' LIMIT 1"); + + // Return + return $token; + } + + /** + * Generate JWT + * + */ + protected function generate_jwt($headers, $payload, $secret = 'secret') { + $headers_encoded = rtrim(strtr(base64_encode(json_encode($headers)), '+/', '-_'), '='); + + $payload_encoded = rtrim(strtr(base64_encode(json_encode($payload)), '+/', '-_'), '='); + + $signature = hash_hmac('SHA256', "$headers_encoded.$payload_encoded", $secret, true); + $signature_encoded = rtrim(strtr(base64_encode($signature), '+/', '-_'), '='); + + $jwt = "$headers_encoded.$payload_encoded.$signature_encoded"; + + return $jwt; + } + + /** + * Generate Token Jwt + * + */ + protected function generate_token_jwt($length=14, $container=["alphaSmall"=>true, "alphaCaps"=>true, "numeric"=>true]) + { + // small letters + $_alphaSmall = ("alphaSmall") ? 'abcdefghijklmnopqrstuvwxyz' : ''; + $_alphaCaps = ("alphaCaps") ? strtoupper($_alphaSmall) : ''; + $_numerics = ("numeric") ? '1234567890' : ''; + $_container = $_alphaSmall.$_alphaCaps.$_numerics; + + $token = ''; + for($i = 0; $i < $length; $i++) { + $_rand = rand(0, strlen($_container) - 1); + $token .= substr($_container, $_rand, 1); + } + + // Returns + return $token; + } + +} \ No newline at end of file diff --git a/src/Database/DB.php b/src/Database/DB.php new file mode 100644 index 0000000..c334ec1 --- /dev/null +++ b/src/Database/DB.php @@ -0,0 +1,483 @@ + null, + 'where' => null, + 'limit' => null, + 'orderBy' => null, + ]; + + /** + * Soft delete at field + */ + protected $soft_delete_at = 'soft_delete_at'; + + /** + * pagination + */ + protected $page; + + /** + * @var array SQL operators + */ + protected $operators = ['=', '!=', '<', '>', '<=', '>=', '<>']; + + /** + * @var Cache|null + */ + protected $cache = null; + + /** + * @var int Total query count + */ + protected $queryCount = 0; + + /** + * @var bool + */ + protected $debug = true; + + /** + * @var int Total transaction count + */ + protected $transactionCount = 0; + + /* + * Getter + * + */ + public $only = array(); + public $hidden = array(); + + /** + * Set PDO + */ + public function setPdo($pdo=null) + { + if($pdo==null){ + return $this->pdo = self::$pdo_static; + } + + // PDO class + $this->pdo = $pdo; + + // PDO Static + self::$pdo_static = $pdo; + } + + /** + * Get PDO + */ + public function getPdo() + { + return $this->pdo; + } + + /** + * Set model + */ + public function setModel($namespace, $path) + { + return $this->model_config = array("namespace" => $namespace, "path" => $path); + } + + /** + * Get model + */ + public function getModel() + { + return (object) $this->model_config; + } + + /** + * Destruct + * + * @return void + */ + public function __destruct() + { + $this->pdo = null; + } + + /** + * @return string|null + */ + public function getQuery() + { + return $this->query; + } + + /** + * @return mixed + */ + public function exec() + { + if (is_null($this->query)) { + return null; + } + + $query = $this->pdo->exec($this->query); + if ($query === false) { + $this->error = $this->pdo->errorInfo()[2]; + //$this->error(); + } + + return $query; + } + + /** + * @param string $type + * @param string $argument + * @param bool $all + * + * @return mixed + */ + public function fetch($type = null, $argument = null, $all = false) + { + if (is_null($this->query)) { + return null; + } + + $query = $this->pdo->query($this->query); + if (!$query) { + $this->error = $this->pdo->errorInfo()[2]; + } + + $type = $this->getFetchType($type); + if ($type === PDO::FETCH_CLASS) { + $query->setFetchMode($type, $argument); + } else { + $query->setFetchMode($type); + } + + $result = $all ? $query->fetchAll() : $query->fetch(); + $this->size = is_array($result) ? count($result) : 1; + + // Result + return $result; + } + + /** + * @param string $type + * @param string $argument + * + * @return mixed + */ + public function fetchAll($type = null, $argument = null) + { + return $this->fetch($type, $argument, true); + } + + /** + * @return int + */ + public function size() + { + return $this->size; + } + + /** + * @return int|null + */ + public function lastId() + { + return $this->insertId; + } + + /** + * @return mixed + */ + public function analyze() + { + return $this->query('ANALYZE TABLE ' . $this->table, false); + } + + /** + * @return mixed + */ + public function check() + { + return $this->query('CHECK TABLE ' . $this->table, false); + } + + /** + * @return mixed + */ + public function checksum() + { + return $this->query('CHECKSUM TABLE ' . $this->table, false); + } + + /** + * @return mixed + */ + public function optimize() + { + return $this->query('OPTIMIZE TABLE ' . $this->table, false); + } + + /** + * @return mixed + */ + public function repair() + { + return $this->query('REPAIR TABLE ' . $this->table, false); + } + + /** + * @return bool + */ + public function transaction() + { + if (!$this->transactionCount++) { + return $this->pdo->beginTransaction(); + } + + $this->pdo->exec('SAVEPOINT trans' . $this->transactionCount); + return $this->transactionCount >= 0; + } + + /** + * @return bool + */ + public function commit() + { + if (!--$this->transactionCount) { + return $this->pdo->commit(); + } + + return $this->transactionCount >= 0; + } + + /** + * @return bool + */ + public function rollBack() + { + if (--$this->transactionCount) { + $this->pdo->exec('ROLLBACK TO trans' . ($this->transactionCount + 1)); + return true; + } + + return $this->pdo->rollBack(); + } + + /* + * Cache some query + */ + protected function cache_some_query() + { + $this->cache_some_query = [ + 'table' => $this->table, + 'where' => $this->where, + 'limit' => $this->limit, + 'orderBy' => $this->orderBy, + ]; + } + + /** + * @param $query + * @param bool $all + * @param string $type + * @param string $argument + * + * @return self::class|mixed + */ + public function query($query, $all = true, $type = null, $argument = null) + { + // Cache some query + $this->cache_some_query(); + + // Reset + $this->reset(); + + // Query + if (is_array($all) || func_num_args() === 1) { + $params = explode('?', $query); + $newQuery = ''; + foreach ($params as $key => $value) { + if (! empty($value)) { + $newQuery .= $value . (isset($all[$key]) ? $this->escape($all[$key]) : ''); + } + } + + $this->query = $newQuery; + return clone($this); + } + + $this->query = preg_replace('/\s\s+|\t\t+/', ' ', trim($query)); + $str = false; + foreach (['select', 'optimize', 'check', 'repair', 'checksum', 'analyze'] as $value) { + if (stripos($this->query, $value) === 0) { + $str = true; + break; + } + } + + $type = $this->getFetchType($type); + $cache = false; + if (! is_null($this->cache) && $type !== PDO::FETCH_CLASS) { + $cache = $this->cache->getCache($this->query, $type === PDO::FETCH_ASSOC); + } + + if (! $cache && $str) { + $sql = $this->pdo->query($this->query); + + if ($sql) { + $this->numRows = $sql->rowCount(); + if (($this->numRows > 0)) { + if ($type === PDO::FETCH_CLASS) { + $sql->setFetchMode($type, $argument); + } else { + $sql->setFetchMode($type); + } + $this->result = $all ? $sql->fetchAll() : $sql->fetch(); + } + + if (! is_null($this->cache) && $type !== PDO::FETCH_CLASS) { + $this->cache->setCache($this->query, $this->result); + } + $this->cache = null; + } else { + $this->cache = null; + $this->error = $this->pdo->errorInfo()[2]; + } + } elseif ((! $cache && ! $str) || ($cache && ! $str)) { + $this->cache = null; + $this->result = $this->pdo->exec($this->query); + + if ($this->result === false) { + $this->error = $this->pdo->errorInfo()[2]; + } + } else { + $this->cache = null; + $this->result = $cache; + $this->numRows = is_array($this->result) ? count($this->result) : ($this->result == '' ? 0 : 1); + } + + // Count + $this->queryCount++; + + // Return result + return $this->result; + } + + /** + * Distinct + * + */ + public function distinct() + { + $this->distinct = 'DISTINCT '; + + return clone($this); + } + + /** + * @param string $primary_key + * + */ + public function key($primary_key) + { + // set primary key + $this->primary_key = $primary_key; + + return clone($this); + } + + /* + * Get primary key + */ + public function getPrimaryKey() + { + return $this->primary_key; + } + + /* + * Set primary key + */ + public function setPrimaryKey($primary_key) + { + return $this->primary_key = $primary_key; + } + +} diff --git a/src/Database/DB/Build/Model.php b/src/Database/DB/Build/Model.php new file mode 100644 index 0000000..dcb22bd --- /dev/null +++ b/src/Database/DB/Build/Model.php @@ -0,0 +1,46 @@ +pdo = self::$pdo_static; + } + + /* + * This + * + * Call as static + */ + public static function this() + { + return new (get_called_class()); + } +} diff --git a/src/Database/DB/Build/ModelBuild.php b/src/Database/DB/Build/ModelBuild.php new file mode 100644 index 0000000..87e2308 --- /dev/null +++ b/src/Database/DB/Build/ModelBuild.php @@ -0,0 +1,30 @@ +pdo = $pdo; + } + + /** + * Get connect + */ + public function getConnect() + { + return $this->pdo; + } + +} \ No newline at end of file diff --git a/src/Database/DB/ExcuteTrait.php b/src/Database/DB/ExcuteTrait.php new file mode 100644 index 0000000..82430c5 --- /dev/null +++ b/src/Database/DB/ExcuteTrait.php @@ -0,0 +1,598 @@ +limit = 1; + $query = $this->get(true); + + if ($type === true) { + return $query; + } + + $query = $this->query($query, false, $type, $argument); + + // Count + if(is_array($this->result)){ + $this->count = count($this->result); + }else{ + $this->count = count(array($this->result)); + } + + // Excute getter method + $this->excuteGetter(); + + // Return + $this->data = $this->result; + + return clone($this); + } + + /** + */ + public function firstOrFail() + { + // Result + $result = $this->first(); + + // Fail + if(!$result->data){ + throw new \Exception("Not found any rows."); + } + + // Return data + return $result; + } + + /** + * @param bool|string $type + * @param string|null $argument + * + * @return mixed|string + */ + public function get($type = null, $argument = null) + { + $query = 'SELECT ' . $this->distinct . $this->select . ' FROM ' . $this->table; + + if (! is_null($this->join)) { + $query .= $this->join; + } + + if (! is_null($this->where)) { + $query .= ' WHERE ' . $this->where; + } + + if (! is_null($this->groupBy)) { + $query .= ' GROUP BY ' . $this->groupBy; + } + + if (! is_null($this->having)) { + $query .= ' HAVING ' . $this->having; + } + + if (! is_null($this->orderBy)) { + $query .= ' ORDER BY ' . $this->orderBy; + } + + if (! is_null($this->limit)) { + $query .= ' LIMIT ' . $this->limit; + } + + if (! is_null($this->offset)) { + $query .= ' OFFSET ' . $this->offset; + } + + if ($type === true) { + return $query; + } + + $query = $this->query($query, true, $type, $argument); + + // Size + $this->size = sizeof($this->result); + + // Count + $this->count = count($this->result); + + // Excute getter method + $this->excuteGetter(); + + // Return + $this->data = $this->result; + + return clone($this); + } + + /** + * + * Excute getter method + * + */ + protected function excuteGetter() + { + if(!empty($this->only)){ + $this->only($this->only); + } + + if(!empty($this->hidden)){ + $this->hidden($this->hidden); + } + + $func = $this->__getter(); + if($func!==false && is_callable($func)){ + $this->map($func); + } + } + + /** + * + * Excute setter method + * + */ + protected function excuteSetter($data) + { + $items = json_decode(json_encode($data), false); + + $items = $this->__setter($items); + + $items = (empty($items)) ? $data : $items; + + return json_decode(json_encode($items), true); + } + + /** + * @param bool|string $type + * @param string|null $argument + * + * @return mixed|string + */ + public function all($type = null, $argument = null) + { + return $this->get($type, $argument); + } + + /** + * @param $perPage + * + * @return self::class + */ + public function page($perPage, $page=null) + { + // page + if($page < 1){ $page = 1; } + + // total + $result = $this->get(); + + $total = $this->size(); + + //Pagination + $this->pagination($perPage, $page); + + $this->query = $this->query . " LIMIT {$this->limit} OFFSET {$this->offset}"; + + // Result + $this->result = $this->fetch(null, null, true); + + // Pages + $pages = ceil($total / $perPage); + + // Next & back + if($page > $pages){$page = $pages;} + $next = $page+1; + $back = $page-1; + if($page >= $pages){$next = 0;} + + // Page + $page = (object) [ + "pages" => $pages, + "current" => $page, + "total" => $total, + "count" => $perPage, + "next" => $next, + "back" => $back + ]; + + $this->page = $page; + + // Count + $this->count = count($this->result); + + // Return + $this->data = $this->result; + + return clone($this); + } + + /** + * @param string $field + * + * @return $this + */ + public function max($field) + { + $column = 'MAX(' . $field . ')' . (!is_null(null) ? ' AS ' . null : ''); + $this->optimizeSelect($column); + foreach ($this->first()->data() as $result) break; + + return $result; + } + + /** + * @param string $field + * + * @return $this + */ + public function exists() + { + return ($this->get()->count()!=0) ? true : false ; + } + + /** + * @param string $field + * + * @return $this + */ + public function min($field) + { + $column = 'MIN(' . $field . ')' . (!is_null(null) ? ' AS ' . null : ''); + $this->optimizeSelect($column); + foreach ($this->first()->data() as $result) break; + + return $result; + } + + /** + * @param string $field + * + * @return $this + */ + public function sum($field) + { + $column = 'SUM(' . $field . ')' . (!is_null(null) ? ' AS ' . null : ''); + $this->optimizeSelect($column); + foreach ($this->first()->data() as $result) break; + + return $result; + } + + /* + * Count + * + */ + public function count($field='id') + { + $column = 'COUNT(' . $field . ')' . (!is_null(null) ? ' AS ' . null : ''); + $this->optimizeSelect($column); + foreach ($this->first()->data() as $result) break; + + return $result; + } + + /** + * @param string $field + * + * @return $this + */ + public function avg($field) + { + $column = 'AVG(' . $field . ')' . (!is_null(null) ? ' AS ' . null : ''); + $this->optimizeSelect($column); + foreach ($this->first()->data() as $result) break; + + return $result; + } + + /** + * @param array $data + * @param bool $type + * + * @return bool|string|int|null + */ + public function insert(array $data=array(), $type = false) + { + if(empty($data)){ + $data = json_decode(json_encode($this->data), true); + $table = $this->cache_some_query['table']; + }else{ + $table = $this->table; + } + + // Excute setter method + $data = $this->excuteSetter($data); + + // Query + $query = 'INSERT INTO ' . $table; + + $values = array_values($data); + if (isset($values[0]) && is_array($values[0])) { + $column = implode(', ', array_keys($values[0])); + $query .= ' (' . $column . ') VALUES '; + foreach ($values as $value) { + $val = implode(', ', array_map([$this, 'escape'], $value)); + $query .= '(' . $val . '), '; + } + $query = trim($query, ', '); + } else { + $column = implode(', ', array_keys($data)); + $val = implode(', ', array_map([$this, 'escape'], $data)); + $query .= ' (' . $column . ') VALUES (' . $val . ')'; + } + + if ($type === true) { + return $query; + } + + if ($this->query($query, false)) { + $this->insertId = ($this->pdo->lastInsertId() + count($data)) - 1; + + return $this->lastId(); + } + + return false; + } + + /* + * Push update from model + */ + protected function pushUpdate($not_string=false, $type=false) + { + // Data + $data = json_decode(json_encode($this->data), true); + + // Update + $query = 'UPDATE ' . $this->cache_some_query['table'] . ' SET '; + $values = []; + + foreach ($data as $column => $val) { + $val = ($not_string == true) ? trim($this->escape($val), "'") : $this->escape($val); + $values[] = $column . '=' . $val; + } + $query .= implode(',', $values); + + if (!is_null($this->cache_some_query['where'])) { + $query .= ' WHERE ' . $this->cache_some_query['where']; + } + + if (!is_null($this->cache_some_query['orderBy'])) { + $query .= ' ORDER BY ' . $this->cache_some_query['orderBy']; + } + + if (!is_null($this->cache_some_query['limit'])) { + $query .= ' LIMIT ' . $this->cache_some_query['limit']; + } + + return $type === true ? $query : $this->query($query, false); + } + + /** + * @param array $data + * @param bool $type + * + * @return mixed|string + */ + public function update(array $data=array(), $not_string=false, $type=false) + { + //Push update from model + if(empty($data)){ + return $this->pushUpdate(false, false); + } + + // Update + $query = 'UPDATE ' . $this->table . ' SET '; + $values = []; + + // Excute setter method + $data = $this->excuteSetter($data); + + foreach ($data as $column => $val) { + $val = ($not_string == true) ? trim($this->escape($val), "'") : $this->escape($val); + $values[] = $column . '=' . $val; + } + $query .= implode(',', $values); + + if (!is_null($this->where)) { + $query .= ' WHERE ' . $this->where; + } + + if (!is_null($this->orderBy)) { + $query .= ' ORDER BY ' . $this->orderBy; + } + + if (!is_null($this->limit)) { + $query .= ' LIMIT ' . $this->limit; + } + + return $type === true ? $query : $this->query($query, false); + } + + /* + * It tries to find a model matching the attributes you pass in the first parameter. + * If a model is not found, it automatically creates and saves a new Model after applying any attributes passed in the second parameter + * + */ + public function firstOrInsert(array $data){ + // Raw + $raw = clone($this); + + // Result + $result = $this->first(); + + // Insert + if($this->count==0){ + return $raw->insert($data); + } + + // Return + return $result; + } + + /* + * The method retrieves the first Model from a query, or if no matching Model is found, it will call a callback passed. + * + */ + public function firstOr(callable $callback){ + $result = $this->first(); + + if($this->count==0){ + return call_user_func($callback); + } + + return $result; + } + + /* + * Update an existing record in the database if matching the condition or create if no matching record exists. + * + */ + public function updateOrInsert(array $conditions, array $data){ + // Conditions + foreach ($conditions as $key => $condition) { + $this->where($key, $condition); + } + + // Raw + $raw = clone($this); + + // Result + $result = $this->get(); + + // Count + $count = count($result->data()); + + // Insert + if($count == 0){ + return $this->insert($data); + } + + // Update + return $raw->update($data); + } + + /* + * Incrementing. + * + */ + public function increment($column, $value=1){ + $data = array($column => $column.'+'.$value); + + return $this->update($data, true, false); + } + + /* + * Decrementing . + * + */ + public function decrement($column, $value=1){ + $data = array($column => $column.'-'.$value); + + return $this->update($data, true, false); + } + + /* + * Truncate. + * + */ + public function truncate(){ + return $this->table($this->table)->delete(); + } + + /** + * @param bool $type + * + * @return mixed|string + */ + public function delete($type = false) + { + $query = 'DELETE FROM ' . $this->table; + + if (!is_null($this->where)) { + $query .= ' WHERE ' . $this->where; + } + + if (!is_null($this->orderBy)) { + $query .= ' ORDER BY ' . $this->orderBy; + } + + if (!is_null($this->limit)) { + $query .= ' LIMIT ' . $this->limit; + } + + if ($query === 'DELETE FROM ' . $this->table) { + $query = 'TRUNCATE TABLE ' . $this->table; + } + + return $type === true ? $query : $this->query($query, false); + } + + /** + * @param string $value + * @param string|null $primary_key + * + * @return mixed|string + */ + public function find($value, $primary_key='') + { + // primary key + if(empty($primary_key)){ + $primary_key = $this->primary_key; + } + + // return + return $this->where($primary_key, $value)->first(); + } + + /** + */ + public function findOrFail($value, $primary_key='') + { + // Result + $result = $this->find($value, $primary_key); + + // Fail + if(!$result->data){ + throw new \Exception("Not found any rows."); + } + + // Return data + return $result; + } + + + /** + * Get next auto increment + * + * @return integer + */ + public function nextId() + { + return $this->query("SHOW TABLE STATUS LIKE ?", [$this->table])->fetch()->Auto_increment; + } + + +} \ No newline at end of file diff --git a/src/Database/DB/FunctionsTrait.php b/src/Database/DB/FunctionsTrait.php new file mode 100644 index 0000000..6c9ef5e --- /dev/null +++ b/src/Database/DB/FunctionsTrait.php @@ -0,0 +1,651 @@ +reset(); + + // Query + if (is_array($table)) { + $from = ''; + foreach ($table as $key) { + $from .= $this->prefix . $key . ', '; + } + $this->table = rtrim($from, ', '); + } else { + if (strpos($table, ',') > 0) { + $tables = explode(',', $table); + foreach ($tables as $key => &$value) { + $value = $this->prefix . ltrim($value); + } + $this->table = implode(', ', $tables); + } else { + $this->table = $this->prefix . $table; + } + } + + // Cache some query + $this->cache_some_query(); + + // Return + return clone($this); + } + + /** + * @param array|string $fields + * + * @return $this + */ + public function select($fields) + { + $select = is_array($fields) ? implode(', ', $fields) : $fields; + $this->optimizeSelect($select); + return clone($this); + } + + /** + * @param string $table + * @param string|null $field1 + * @param string|null $operator + * @param string|null $field2 + * @param string $type + * + * @return $this + */ + public function join($table, $field1 = null, $operator = null, $field2 = null, $type = '') + { + $on = $field1; + $table = $this->prefix . $table; + + if (!is_null($operator)) { + $on = !in_array($operator, $this->operators) + ? $field1 . ' = ' . $operator . (!is_null($field2) ? ' ' . $field2 : '') + : $field1 . ' ' . $operator . ' ' . $field2; + } + + $this->join = (is_null($this->join)) + ? ' ' . $type . 'JOIN' . ' ' . $table . ' ON ' . $on + : $this->join . ' ' . $type . 'JOIN' . ' ' . $table . ' ON ' . $on; + + return clone($this); + } + + /** + * @param string $table + * @param string $field1 + * @param string $operator + * @param string $field2 + * + * @return $this + */ + public function innerJoin($table, $field1, $operator = '', $field2 = '') + { + return $this->join($table, $field1, $operator, $field2, 'INNER '); + } + + /** + * @param string $table + * @param string $field1 + * @param string $operator + * @param string $field2 + * + * @return $this + */ + public function leftJoin($table, $field1, $operator = '', $field2 = '') + { + return $this->join($table, $field1, $operator, $field2, 'LEFT '); + } + + /** + * @param string $table + * @param string $field1 + * @param string $operator + * @param string $field2 + * + * @return $this + */ + public function rightJoin($table, $field1, $operator = '', $field2 = '') + { + return $this->join($table, $field1, $operator, $field2, 'RIGHT '); + } + + /** + * @param string $table + * @param string $field1 + * @param string $operator + * @param string $field2 + * + * @return $this + */ + public function fullOuterJoin($table, $field1, $operator = '', $field2 = '') + { + return $this->join($table, $field1, $operator, $field2, 'FULL OUTER '); + } + + /** + * @param string $table + * @param string $field1 + * @param string $operator + * @param string $field2 + * + * @return $this + */ + public function leftOuterJoin($table, $field1, $operator = '', $field2 = '') + { + return $this->join($table, $field1, $operator, $field2, 'LEFT OUTER '); + } + + /** + * @param string $table + * @param string $field1 + * @param string $operator + * @param string $field2 + * + * @return $this + */ + public function rightOuterJoin($table, $field1, $operator = '', $field2 = '') + { + return $this->join($table, $field1, $operator, $field2, 'RIGHT OUTER '); + } + + + /** + * @param array|string $where + * @param string $operator + * @param string $val + * @param string $type + * @param string $andOr + * + * @return $this + */ + public function where($where, $operator = null, $val = null, $type = '', $andOr = 'AND') + { + if (is_array($where) && !empty($where)) { + $_where = []; + foreach ($where as $column => $data) { + $_where[] = $type . $column . '=' . $this->escape($data); + } + $where = implode(' ' . $andOr . ' ', $_where); + } else { + if (is_null($where) || empty($where)) { + return clone($this); + } + + if (is_array($operator)) { + $params = explode('?', $where); + $_where = ''; + foreach ($params as $key => $value) { + if (!empty($value)) { + $_where .= $type . $value . (isset($operator[$key]) ? $this->escape($operator[$key]) : ''); + } + } + $where = $_where; + } elseif (!in_array($operator, $this->operators) || $operator == false) { + $where = $type . $where . ' = ' . $this->escape($operator); + } else { + $where = $type . $where . ' ' . $operator . ' ' . $this->escape($val); + } + } + + if ($this->grouped) { + $where = '(' . $where; + $this->grouped = false; + } + + $this->where = is_null($this->where) + ? $where + : $this->where . ' ' . $andOr . ' ' . $where; + + return clone($this); + } + + /** + * @param array|string $where + * @param string|null $operator + * @param string|null $val + * + * @return $this + */ + public function orWhere($where, $operator = null, $val = null) + { + return $this->where($where, $operator, $val, '', 'OR'); + } + + /** + * @param array|string $where + * @param string|null $operator + * @param string|null $val + * + * @return $this + */ + public function notWhere($where, $operator = null, $val = null) + { + return $this->where($where, $operator, $val, 'NOT ', 'AND'); + } + + /** + * @param array|string $where + * @param string|null $operator + * @param string|null $val + * + * @return $this + */ + public function orNotWhere($where, $operator = null, $val = null) + { + return $this->where($where, $operator, $val, 'NOT ', 'OR'); + } + + /** + * @param string $where + * @param bool $not + * + * @return $this + */ + public function whereNull($where, $not = false) + { + $where = $where . ' IS ' . ($not ? 'NOT' : '') . ' NULL'; + $this->where = is_null($this->where) ? $where : $this->where . ' ' . 'AND ' . $where; + + return clone($this); + } + + /** + * @param string $where + * + * @return $this + */ + public function whereNotNull($where) + { + return $this->whereNull($where, true); + } + + /** + * @param string $id + * + * @return $this + */ + public function whereId($id) + { + return $this->where($this->primary_key, $id); + } + + /** + * @param Closure $obj + * + * @return $this + */ + public function grouped(Closure $obj) + { + $this->grouped = true; + call_user_func_array($obj, [$this]); + $this->where .= ')'; + + return clone($this); + } + + /** + * @param string $field + * @param array $keys + * @param string $type + * @param string $andOr + * + * @return $this + */ + public function in($field, array $keys, $type = '', $andOr = 'AND') + { + if (is_array($keys)) { + $_keys = []; + foreach ($keys as $k => $v) { + $_keys[] = is_numeric($v) ? $v : $this->escape($v); + } + $where = $field . ' ' . $type . 'IN (' . implode(', ', $_keys) . ')'; + + if ($this->grouped) { + $where = '(' . $where; + $this->grouped = false; + } + + $this->where = is_null($this->where) + ? $where + : $this->where . ' ' . $andOr . ' ' . $where; + } + + return clone($this); + } + + /** + * @param string $field + * @param array $keys + * + * @return $this + */ + public function notIn($field, array $keys) + { + return $this->in($field, $keys, 'NOT ', 'AND'); + } + + /** + * @param string $field + * @param array $keys + * + * @return $this + */ + public function orIn($field, array $keys) + { + return $this->in($field, $keys, '', 'OR'); + } + + /** + * @param string $field + * @param array $keys + * + * @return $this + */ + public function orNotIn($field, array $keys) + { + return $this->in($field, $keys, 'NOT ', 'OR'); + } + + /** + * @param string $field + * @param string|int $value1 + * @param string|int $value2 + * @param string $type + * @param string $andOr + * + * @return $this + */ + public function between($field, $value1, $value2, $type = '', $andOr = 'AND') + { + $where = '(' . $field . ' ' . $type . 'BETWEEN ' . ($this->escape($value1) . ' AND ' . $this->escape($value2)) . ')'; + if ($this->grouped) { + $where = '(' . $where; + $this->grouped = false; + } + + $this->where = is_null($this->where) + ? $where + : $this->where . ' ' . $andOr . ' ' . $where; + + return clone($this); + } + + /** + * @param string $field + * @param string|int $value1 + * @param string|int $value2 + * + * @return $this + */ + public function notBetween($field, $value1, $value2) + { + return $this->between($field, $value1, $value2, 'NOT ', 'AND'); + } + + /** + * @param string $field + * @param string|int $value1 + * @param string|int $value2 + * + * @return $this + */ + public function orBetween($field, $value1, $value2) + { + return $this->between($field, $value1, $value2, '', 'OR'); + } + + /** + * @param string $field + * @param string|int $value1 + * @param string|int $value2 + * + * @return $this + */ + public function orNotBetween($field, $value1, $value2) + { + return $this->between($field, $value1, $value2, 'NOT ', 'OR'); + } + + + /** + * @param string $field + * @param string $data + * @param string $type + * @param string $andOr + * + * @return $this + */ + public function like($field, $data, $type = '', $andOr = 'AND') + { + $like = $this->escape($data); + $where = $field . ' ' . $type . 'LIKE ' . $like; + + if ($this->grouped) { + $where = '(' . $where; + $this->grouped = false; + } + + $this->where = is_null($this->where) + ? $where + : $this->where . ' ' . $andOr . ' ' . $where; + + return clone($this); + } + + /** + * @param string $field + * @param string $data + * + * @return $this + */ + public function orLike($field, $data) + { + return $this->like($field, $data, '', 'OR'); + } + + /** + * @param string $field + * @param string $data + * + * @return $this + */ + public function notLike($field, $data) + { + return $this->like($field, $data, 'NOT ', 'AND'); + } + + /** + * @param string $field + * @param string $data + * + * @return $this + */ + public function orNotLike($field, $data) + { + return $this->like($field, $data, 'NOT ', 'OR'); + } + + /** + * @param int $limit + * @param int|null $limitEnd + * + * @return $this + */ + public function limit($limit, $limitEnd = null) + { + $this->limit = !is_null($limitEnd) + ? $limit . ', ' . $limitEnd + : $limit; + + return clone($this); + } + + /** + * @param int $offset + * + * @return $this + */ + public function offset($offset) + { + $this->offset = $offset; + + return clone($this); + } + + /** + * @param string $orderBy + * @param string|null $orderDir + * + * @return $this + */ + public function orderBy($orderBy, $orderDir = null) + { + if (!is_null($orderDir)) { + $this->orderBy = $orderBy . ' ' . strtoupper($orderDir); + } else { + $this->orderBy = stristr($orderBy, ' ') || strtolower($orderBy) === 'rand()' + ? $orderBy + : $orderBy . ' ASC'; + } + + return clone($this); + } + + /** + * @return $this + */ + public function idAsc(){ + return $this->orderBy($this->primary_key, "asc"); + } + + /** + * @return $this + */ + public function idDesc(){ + return $this->orderBy($this->primary_key, "desc"); + } + + /** + * @return $this + */ + public function orderByRand(){ + return $this->orderBy("rand()"); + } + + /** + * @param string|array $groupBy + * + * @return $this + */ + public function groupBy($groupBy) + { + $this->groupBy = is_array($groupBy) ? implode(', ', $groupBy) : $groupBy; + + return clone($this); + } + + /** + * @param string $field + * @param string|array|null $operator + * @param string|null $val + * + * @return $this + */ + public function having($field, $operator = null, $val = null) + { + if (is_array($operator)) { + $fields = explode('?', $field); + $where = ''; + foreach ($fields as $key => $value) { + if (!empty($value)) { + $where .= $value . (isset($operator[$key]) ? $this->escape($operator[$key]) : ''); + } + } + $this->having = $where; + } elseif (!in_array($operator, $this->operators)) { + $this->having = $field . ' > ' . $this->escape($operator); + } else { + $this->having = $field . ' ' . $operator . ' ' . $this->escape($val); + } + + return clone($this); + } + + /** + */ + public function softDelete() + { + return $this->update(array($this->soft_delete_at => date("Y-m-d h:i:sa"))); + } + + /** + */ + public function restoreDelete() + { + return $this->update(array($this->soft_delete_at => null)); + } + + /** + */ + public function hideArchived() + { + return $this->whereNull($this->soft_delete_at); + } + + /** + */ + public function onlyArchived() + { + return $this->whereNotNull($this->soft_delete_at); + } + + /** + * Getter method. + * + */ + public function __getter() + { + return false; + } + + /** + * Setter method. + * + */ + public function __setter($item) + { + return false; + } +} \ No newline at end of file diff --git a/src/Database/DB/HelpersTrait.php b/src/Database/DB/HelpersTrait.php new file mode 100644 index 0000000..2d1eb94 --- /dev/null +++ b/src/Database/DB/HelpersTrait.php @@ -0,0 +1,394 @@ + '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(s)tatuses$/i' => '\1tatus', + '/(f)eet$/i' => '\1oot', + '/(t)eeth$/i' => '\1ooth', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/(n)etherlands$/i' => '\1\2etherlands', + '/eaus$/' => 'eau', + '/(currenc)ies$/' => '\1y', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ]; + + /** + * @var array the special rules for converting a word between its plural form and singular form. + * The keys are the special words in singular form, and the values are the corresponding plural form. + */ + protected $specials = [ + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'curve' => 'curves', + 'foe' => 'foes', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'pasta' => 'pasta', + 'penis' => 'penises', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'wave' => 'waves', + 'Amoyese' => 'Amoyese', + 'bison' => 'bison', + 'Borghese' => 'Borghese', + 'bream' => 'bream', + 'breeches' => 'breeches', + 'britches' => 'britches', + 'buffalo' => 'buffalo', + 'cantus' => 'cantus', + 'carp' => 'carp', + 'chassis' => 'chassis', + 'clippers' => 'clippers', + 'cod' => 'cod', + 'coitus' => 'coitus', + 'Congoese' => 'Congoese', + 'contretemps' => 'contretemps', + 'corps' => 'corps', + 'debris' => 'debris', + 'diabetes' => 'diabetes', + 'djinn' => 'djinn', + 'eland' => 'eland', + 'elk' => 'elk', + 'equipment' => 'equipment', + 'Faroese' => 'Faroese', + 'flounder' => 'flounder', + 'Foochowese' => 'Foochowese', + 'gallows' => 'gallows', + 'Genevese' => 'Genevese', + 'Genoese' => 'Genoese', + 'Gilbertese' => 'Gilbertese', + 'graffiti' => 'graffiti', + 'headquarters' => 'headquarters', + 'herpes' => 'herpes', + 'hijinks' => 'hijinks', + 'Hottentotese' => 'Hottentotese', + 'information' => 'information', + 'innings' => 'innings', + 'jackanapes' => 'jackanapes', + 'Kiplingese' => 'Kiplingese', + 'Kongoese' => 'Kongoese', + 'Lucchese' => 'Lucchese', + 'mackerel' => 'mackerel', + 'Maltese' => 'Maltese', + 'mews' => 'mews', + 'moose' => 'moose', + 'mumps' => 'mumps', + 'Nankingese' => 'Nankingese', + 'news' => 'news', + 'nexus' => 'nexus', + 'Niasese' => 'Niasese', + 'Pekingese' => 'Pekingese', + 'Piedmontese' => 'Piedmontese', + 'pincers' => 'pincers', + 'Pistoiese' => 'Pistoiese', + 'pliers' => 'pliers', + 'Portuguese' => 'Portuguese', + 'proceedings' => 'proceedings', + 'rabies' => 'rabies', + 'rice' => 'rice', + 'rhinoceros' => 'rhinoceros', + 'salmon' => 'salmon', + 'Sarawakese' => 'Sarawakese', + 'scissors' => 'scissors', + 'series' => 'series', + 'Shavese' => 'Shavese', + 'shears' => 'shears', + 'siemens' => 'siemens', + 'species' => 'species', + 'swine' => 'swine', + 'testes' => 'testes', + 'trousers' => 'trousers', + 'trout' => 'trout', + 'tuna' => 'tuna', + 'Vermontese' => 'Vermontese', + 'Wenchowese' => 'Wenchowese', + 'whiting' => 'whiting', + 'wildebeest' => 'wildebeest', + 'Yengeese' => 'Yengeese', + ]; + + /** + * Returns the singular of the $word. + * @param string $word the english word to singularize + * @return string Singular noun. + */ + protected function singularize($word) + { + $result = array_search($word, $this->specials, true); + if ($result !== false) { + return $result; + } + foreach ($this->singulars as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return $word; + } + + /** + * Reset + * + * @return void + */ + protected function reset() + { + $this->select = '*'; + // $this->table = null; + $this->where = null; + $this->limit = null; + $this->offset = null; + $this->orderBy = null; + $this->groupBy = null; + $this->having = null; + $this->join = null; + $this->grouped = false; + $this->numRows = 0; + $this->insertId = null; + $this->query = null; + $this->error = null; + $this->result = []; + $this->transactionCount = 0; + } + + /** + * @param int $perPage + * @param int $page + * + * @return $this + */ + protected function pagination($perPage, $page) + { + $this->limit = $perPage; + $this->offset = (($page > 0 ? $page : 1) - 1) * $perPage; + + return clone($this); + } + + /** + * getFetchType + * + * @param $type + * + * @return int + */ + protected function getFetchType($type) + { + return $type === 'class' + ? PDO::FETCH_CLASS + : ($type === 'array' + ? PDO::FETCH_ASSOC + : PDO::FETCH_OBJ); + } + + /** + * optimizeSelect + * + * Optimize Selected fields for the query + * + * @param string $fields + * + * @return void + */ + private function optimizeSelect($fields) + { + $this->select = $this->select === '*' + ? $fields + : $this->select . ', ' . $fields; + } + + /** + * @param $data + * + * @return string + */ + public function escape($data) + { + return $data === null ? 'NULL' : ( + is_int($data) || is_float($data) ? $data : $this->pdo->quote($data) + ); + } + + /** + * @return int + */ + public function queryCount() + { + return $this->queryCount; + } + + /** + * Run an associative map over each of the items. + * + */ + protected function map(callable $callback) + { + // Items + $items = $this->result; + $items_array = json_decode(json_encode($items), true); + + // Multidimensional + $multidimensional = (count($items_array) != count($items_array, COUNT_RECURSIVE)) ? true : false ; + + if(!$multidimensional){ + $items = array($items); + } + + // Result + $result = []; + + foreach ($items as $key => $value) { + $assoc = $callback($value, $key); + + foreach ($assoc as $mapKey => $mapValue) { + $result[$mapKey] = $mapValue; + } + } + + return $result; + } + + /** + * Remove an item from the collection by key. + * + */ + public function hidden(array $keys) + { + $items = json_decode(json_encode($this->result), true); + + // Multidimensional + $multidimensional = (count($items) != count($items, COUNT_RECURSIVE)) ? true : false ; + if(!$multidimensional){ + $items = array($items); + } + + foreach ((array)$keys as $key) { + foreach ($items as $index => $item) { + unset($items[$index][$key]); + } + } + + // Return one array if it is not multidimensional + if(!$multidimensional){ + $items = $items[0]; + } + + $this->result = json_decode(json_encode($items), false); + } + + /** + * Get the items with the specified keys. + * + */ + public function only(array $keys) + { + // Items + $items = json_decode(json_encode($this->result), true); + if(empty($items)){ + return false; + } + + // Multidimensional + if(count($items) == count($items, COUNT_RECURSIVE)){ + $items = array($items); + } + + // Indexs + $indexs = array_keys($items[0]); + + // Diff + $diff = array_diff($indexs, $keys); + + // Hidden + $this->hidden($diff); + } + +} \ No newline at end of file diff --git a/src/Database/DB/RelationshipsTrait.php b/src/Database/DB/RelationshipsTrait.php new file mode 100644 index 0000000..a99eba3 --- /dev/null +++ b/src/Database/DB/RelationshipsTrait.php @@ -0,0 +1,291 @@ +relationships_have_name = $title; + + return clone($this); + } + + /** + * Title + * + * Custom title for relationships + */ + public function title($title) + { + $this->relationships_name = $title; + + return clone($this); + } + + /** + * Clear + * + * Clear data + */ + protected function clear() + { + $this->relationships_name = ''; + } + + /** + * Key + * + * Primary key + */ + protected function key($primary_key) + { + $this->primary_key = $primary_key; + } + + /** + * Prepare relationships + * + * + */ + protected function prepareRelationships($table, $foreign_key, $primary_key, $fetch, $inverse=false, $have=false) + { + // DB + $db = clone($this); + $original_table = $db->table; + $original_relationships_name = $db->relationships_name_data; + $db->reset(); + + // Table + $table = (class_exists($table)) ? trim((new $table)->table) : trim($table); + $original_table = ($inverse) ? $table : $original_table; + + // Primary Key + if (empty($primary_key)) { + $primary_key = ($inverse) ? $this->singularize($original_table)."_".$this->primary_key : $this->primary_key; + }else{ + $primary_key = trim($primary_key); + } + + // Foreign Key + if (empty($foreign_key)) { + $foreign_key = ($inverse) ? $this->primary_key : $this->singularize($original_table)."_$primary_key"; + }else{ + $foreign_key = trim($foreign_key); + } + + // Relationship name + if (empty($this->relationships_name)) { + $relationship_name = $table; + }else{ + $relationship_name = $this->relationships_name; + } + + if(!$have){ + $this->relationships_name_data = $relationship_name; + } + + // Data + $data = json_decode(json_encode($this->data()), true); + $isIndexed = array_values($data) === $data; + $data = ($isIndexed) ? $data : array($data) ; + + // Keys + if($have){ + $original_relationships_data = $this->relationships_data; + $keys = array_unique(array_column($original_relationships_data, $foreign_key)); + $original_foreign_key = $foreign_key; + $foreign_key = $primary_key; + }else{ + $keys = array_column($data, $primary_key); + } + + // Break if keys is zero + if(empty($keys)){ + return clone($this); + } + + // Relationship + $relationship = $db->table($table)->in($foreign_key, $keys)->get(); + $relationship = json_decode(json_encode($relationship->data()), true); + $this->relationships_data = $relationship; + + // Attached + if(!$have){ + foreach ($keys as $index => $key) { + $data[$index][$relationship_name] = array(); + + // Relationship keys + $relationship_keys = array_keys(array_column($relationship, $foreign_key), $key); + if($fetch=="first"){ + $relationship_keys = (empty($relationship_keys)) ? array() : array($relationship_keys[0]); + } + + foreach ($relationship_keys as $relationship_key) { + if($fetch=="first"){ + $data[$index][$relationship_name] = $relationship[$relationship_key]; + }else{ + $data[$index][$relationship_name][] = $relationship[$relationship_key]; + } + } + + } + }else{ + + // Relationships name + $relationship_name = (empty($this->relationships_have_name)) ? $relationship_name : $this->relationships_have_name ; + + foreach ($data as $index => $item) { + foreach ($item[$original_relationships_name] as $nestedIndex => $nestedItem) { + $id = $data[$index][$original_relationships_name][$nestedIndex][$original_foreign_key]; + + if(isset(array_flip(array_column($relationship, $primary_key))[$id])){ + $key = array_flip(array_column($relationship, $primary_key))[$id]; + }else{ + $key = null; + } + + if(isset($relationship[$key])){ + $data[$index][$original_relationships_name][$nestedIndex][$relationship_name] = $relationship[$key]; + }else{ + $data[$index][$original_relationships_name][$nestedIndex][$relationship_name] = array(); + } + + } + } + + $this->relationships_have_name = ''; + } + + // Return + $this->relationships_name = ''; + + if(!$isIndexed && count($data)==1){ + $data = $data[0]; + } + + $this->data = json_decode(json_encode($data), false); + + return clone($this); + } + + /** + * Has one + * + * + */ + public function hasOne($table, $foreign_key='', $primary_key='') + { + return $this->prepareRelationships($table, $foreign_key, $primary_key, 'first'); + } + + /** + * Belong + * + * + */ + public function belongOne($table, $foreign_key='', $primary_key='') + { + return $this->prepareRelationships($table, $primary_key, $foreign_key, 'first', true); + } + + /** + * Has + * + * + */ + public function has($table, $foreign_key='', $primary_key='') + { + return $this->prepareRelationships($table, $foreign_key, $primary_key, 'get'); + } + + /** + * Belong + * + * + */ + public function belong($table, $foreign_key='', $primary_key='') + { + return $this->prepareRelationships($table, $primary_key, $foreign_key, 'get', true); + } + + /** + * have + * + */ + public function have($table, $linked_table, $table_foreign_key='', $foreign_key='', $table_primary_key='', $primary_key='') + { + // Table + if(class_exists($table)){ + $table = (new $table)->table; + } + + // Linked table + if(class_exists($linked_table)){ + $linked_table = (new $linked_table)->table; + } + + // Table foreign key + if(empty($table_foreign_key)){ + $table_foreign_key = $this->singularize($table)."_".$this->primary_key; + } + + // This foreign key + if(empty($foreign_key)){ + $foreign_key = $this->singularize($this->table)."_".$this->primary_key; + } + + // Step 1 : (Linked table) + $this->prepareRelationships($linked_table, $foreign_key, $primary_key, 'get'); + + // Step 2 : (Relationship table) + return $this->prepareRelationships($table, $table_foreign_key, $table_primary_key, 'get', false, true); + } + +} \ No newline at end of file diff --git a/src/Database/DB/ResultTrait.php b/src/Database/DB/ResultTrait.php new file mode 100644 index 0000000..46ceb83 --- /dev/null +++ b/src/Database/DB/ResultTrait.php @@ -0,0 +1,115 @@ +data); + } + + /** + * Returns an element at a given offset. + * + * @param mixed $key Key to return the element for + * @return mixed Value associated to the given key + */ + public function __get($key) + { + return $this->data->{$key}; + } + + /* + * Set magic function. + */ + /* + public function __set($key, $value) + { + if(isset($this->data->{$key})){ + $this->data->{$key} = $value; + }else{ + $this->data[$key] = $value; + } + } + */ + + /** + * Returns data object + * + */ + public function data() + { + return $this->data; + } + + /** + * Value + * + */ + public function value($column) + { + if(is_array($this->data)){ + return array_column($this->data, $column); + }else{ + return $this->data->{$column}; + } + return $this->data; + } + + /** + * Get count of results + */ + public function length() + { + if(!is_array($this->data)){ + return (empty($this->data)) ? 0 : 1; + } + + return count($this->data); + } + + /** + * Return results to json + */ + public function toJson() + { + return $this->data; + } + + /** + * Return results to json + */ + public function toArray() + { + return json_decode($this->data); + } + +} \ No newline at end of file diff --git a/src/Database/Schema.php b/src/Database/Schema.php new file mode 100644 index 0000000..ba747ae --- /dev/null +++ b/src/Database/Schema.php @@ -0,0 +1,504 @@ +connectClass; + } + + /** + * Set connect + */ + public function setConnect($pdo) + { + $this->sqlClass = (new Schema\Sql); + $this->connectClass = $pdo; + $this->sqlClass->drive = $this->driver(); + } + + /** + * Get migration + */ + public function getMigration() + { + return json_decode(json_encode((object) $this->migrations), false); + } + + /** + * Set migration + */ + public function setMigration($namespace, $table) + { + $this->migrations = [ + "namespace" => $namespace, + "table" => $table + ]; + } + + /** + * Set migration path + */ + public function setMigrationPath($path) + { + $this->migrations['path'] = $path; + } + + /** + * Get seed + */ + public function getSeed() + { + return json_decode(json_encode((object) $this->seeds), false); + } + + /** + * Set seed + */ + public function setSeed($namespace) + { + $this->seeds = [ + "namespace" => $namespace, + ]; + } + + /** + * Set seed path + */ + public function setSeedPath($path) + { + $this->seeds['path'] = $path; + } + + /* + * Add to migration list + * + */ + public function migrations(array $migrations){ + $this->migrations_list = array_merge($this->migrations_list, $migrations); + } + + /* + * Add to seeds list + * + */ + public function seeds(array $seeds){ + $this->seeds_list = array_merge($this->seeds_list, $seeds); + } + + /* + * Run migrate + * + */ + public function runMigrate($class_name=null){ + $this->generate_migration($class_name, $this->migrations_method, $this->migrations_list, $this->getMigration()->namespace); + + return true; + } + + /* + * Run migrate rollback + * + */ + public function runRollback($class_name=null){ + $this->generate_migration($class_name, $this->rollback_method, $this->migrations_list, $this->getMigration()->namespace); + + return true; + } + + /* + * Run migrate + * + */ + public function runSeeds($class_name=null){ + $this->generate_migration($class_name, $this->seeds_method, $this->seeds_list, $this->getSeed()->namespace); + + return true; + } + + /* + * Generate migration class prepare + * + */ + protected function generate_migration($class_name, $method, $in_list, $namespace){ + if(!is_null($class_name)){ + if(is_string($class_name)){ + $list = explode(",", $class_name); + } + }else{ + $list = array_keys($in_list); + } + + foreach ($list as $item) { + $item = trim($item); + + if(!isset($in_list[$item])){ + throw new \Exception("'$item' not found in list."); + } + + $class = $this->generate_migration_class_prepare($item, $namespace); + $class->{$method}(); + } + + return true; + } + + /* + * Generate migration class prepare + * + */ + protected function generate_migration_class_prepare($class_name, $prefix){ + if(!class_exists($class_name)){ + $class = $prefix . '\\' . $class_name; + } + + return new $class; + } + + /** + * execute Query + * + */ + public function execute($sql, $parms=[], $fetch="obj") + { + // fetch type + switch ($fetch) { + case 'obj': + $fetch = PDO::FETCH_OBJ; + break; + + case 'num': + $fetch = PDO::FETCH_NUM; + break; + + case 'name': + $fetch = PDO::FETCH_NAMED; + break; + + case 'lazy': + $fetch = PDO::FETCH_LAZY; + break; + + case 'into': + $fetch = PDO::FETCH_INTO; + break; + + case 'class': + $fetch = PDO::FETCH_CLASS; + break; + + case 'bound': + $fetch = PDO::FETCH_BOUND; + break; + + case 'both': + $fetch = PDO::FETCH_BOTH; + break; + + case 'assoc': + $fetch = PDO::FETCH_ASSOC; + break; + + default: + $fetch = PDO::FETCH_OBJ; + break; + } + + // excute + $excute = $this->connectClass->prepare($sql); + $excute->execute($parms); + + try { + $excute = $excute->fetchAll($fetch); + } catch (\Throwable $th) { + } + + return $excute; + } + + /** + * Get driver + * + */ + public function driver() + { + try { + $driver = $this->connectClass->getAttribute(PDO::ATTR_DRIVER_NAME); + } catch (\Throwable $th) { + $driver = null; + } + + return $driver; + } + + /** + * Get server info + * + */ + public function server() + { + return $this->connectClass->getAttribute(PDO::ATTR_SERVER_INFO); + } + + /** + * Get version + * + */ + public function ver() + { + return $this->connectClass->getAttribute(PDO::ATTR_SERVER_VERSION); + } + + /** + * Get the name of the currently used database + */ + public function database() + { + $sql = $this->sqlClass->generate('database'); + + return $this->execute($sql); + } + + /** + * Get the name of databases + */ + public function databases() + { + $sql = $this->sqlClass->generate('databases'); + + return $this->execute($sql); + } + + /** + * Create new database + */ + public function createDatabase($database) + { + $sql = $this->sqlClass->generate('createDatabase', $database); + + $this->execute($sql); + + return true; + } + + /** + * Delete database + */ + public function deleteDatabase($database) + { + $sql = $this->sqlClass->generate('deleteDatabase', $database); + + $this->execute($sql); + + return true; + } + + /** + * Get the list of the tables + * + */ + public function tables() + { + $sql = $this->sqlClass->generate('tables'); + + return $this->execute($sql); + } + + /** + * has table + * + */ + public function hasTable($table) + { + $sql = $this->sqlClass->generate('hasTable', $table); + + return count($this->execute($sql)) == 1; + } + + /** + * Delete Table + * + */ + public function deleteTable($table) { + $sql = $this->sqlClass->generate('deleteTable', $table); + + $this->execute($sql); + + return true; + } + + /** + * Rename Table + * + */ + public function renameTable($old, $new) { + $sql = $this->sqlClass->generate('renameTable', $old, $new); + + $this->execute($sql); + + return true; + } + + /** + * Clean Table + * + */ + public function cleanTable($table) { + $sql = $this->sqlClass->generate('cleanTable', $table); + + $this->execute($sql); + + return true; + } + + /** + * Create New Table + * + */ + public function createTable($table) { + return $this->sqlClass->generate('createTable', $this, $table); + } + + /** + * Foreign keys + * + */ + public function foreign($foreign_table, $primary_table=null) { + return $this->sqlClass->generate('foreign', $this, $foreign_table, $primary_table); + } + + /** + * Create column + * + */ + public function createColumn($table) { + return $this->sqlClass->generate('createColumn', $this, $table); + } + + /** + * Columns + * + */ + public function columns($table) + { + $sql = $this->sqlClass->generate('columns', $table); + + return $this->execute($sql); + } + + /** + * Has Column + * + */ + public function hasColumn($table, $columns) + { + $sql = $this->sqlClass->generate('hasColumn', $table, $columns); + + return count($this->execute($sql)) == 1; + } + + /** + * Delete column + * + */ + public function deleteColumn($table, $columns) { + $sql = $this->sqlClass->generate('deleteColumn', $table, $columns); + + $this->execute($sql); + + return true; + } + + /** + * Rename column + * + */ + public function renameColumn($table, $old, $new) { + $sql = $this->sqlClass->generate('renameColumn', $table, $old, $new); + + $this->execute($sql); + + return true; + } + +} \ No newline at end of file diff --git a/src/Database/Schema/Build/Generation/Migrate.txt b/src/Database/Schema/Build/Generation/Migrate.txt new file mode 100644 index 0000000..54857d7 --- /dev/null +++ b/src/Database/Schema/Build/Generation/Migrate.txt @@ -0,0 +1,45 @@ +id() + ->submit(); + } + + /** + * Rollback + * + **/ + public function :method_rollback:() + { + Schema::deleteTable('table'); + } + +} \ No newline at end of file diff --git a/src/Database/Schema/Build/MigrateBuild.php b/src/Database/Schema/Build/MigrateBuild.php new file mode 100644 index 0000000..dd1a1d8 --- /dev/null +++ b/src/Database/Schema/Build/MigrateBuild.php @@ -0,0 +1,24 @@ +schema = $schema; + $this->driver = $driver; + $this->table = $table; + } + + /* + * ID + * + */ + public function id($name='id'){ + return $this->addType("id", $name); + } + + /* + * Big integer + * + */ + public function bigInteger($name){ + return $this->addType("bigInteger", $name); + } + + /* + * Binary + * + */ + public function binary($name){ + return $this->addType("binary", $name); + } + + /* + * Boolean + * + */ + public function boolean($name){ + return $this->addType("boolean", $name); + } + + /* + * Char + * + */ + public function char($name, $length=30){ + return $this->addType("char", $name, array($length)); + } + + /* + * Date + * + */ + public function date($name){ + return $this->addType("date", $name); + } + + /* + * DateTime + * + */ + public function dateTime($name){ + return $this->addType("dateTime", $name); + } + + /* + * Decimal + * + */ + public function decimal($name, $precision=15, $scale=2){ + return $this->addType("decimal", $name, array($precision, $scale)); + } + + /* + * Double + * + */ + public function double($name, $digits=15, $decimal_point=8){ + return $this->addType("double", $name, array($digits, $decimal_point)); + } + + /* + * Enum + * + */ + public function enum($name, array $array){ + return $this->addType("enum", $name, array($array)); + } + + /* + * float + * + */ + public function float($name){ + return $this->addType("float", $name); + } + + /* + * Increments + * + */ + public function increments($name){ + return $this->addType("increments", $name); + } + + /* + * Integer + * + */ + public function integer($name){ + return $this->addType("integer", $name); + } + + /* + * LongText + * + */ + public function longText($name){ + return $this->addType("longText", $name); + } + + /* + * MediumInteger + * + */ + public function mediumInteger($name){ + return $this->addType("mediumInteger", $name); + } + + /* + * MediumText + * + */ + public function mediumText($name){ + return $this->addType("mediumText", $name); + } + + /* + * SmallInteger + * + */ + public function smallInteger($name){ + return $this->addType("smallInteger", $name); + } + + /* + * TinyInteger + * + */ + public function tinyInteger($name){ + return $this->addType("tinyInteger", $name); + } + + /* + * String + * + */ + public function string($name, $length=100){ + return $this->addType("string", $name, array($length)); + } + + /* + * Text + * + */ + public function text($name){ + return $this->addType("text", $name); + } + + /* + * Time + * + */ + public function time($name){ + return $this->addType("time", $name); + } + + /* + * Json + * + */ + public function json($name){ + return $this->addType("json", $name); + } + + /* + * Timestamp + * + */ + public function timestamp($name){ + return $this->addType("timestamp", $name); + } + + /* + * Add type + * + */ + protected function addType($type, $name, $parameters=array()){ + $this->columns[$name] = [ + 'type' => $type, + 'parameters' => $parameters, + 'attributes' => array(), + 'attributes_parameters' => array() + ]; + + $this->column = $name; + + return clone($this); + } + + /* + * Auto increment + * + */ + public function auto(){ + return $this->addAttribute("auto"); + } + + /* + * Primary key + * + */ + public function primary(){ + return $this->addAttribute("primary"); + } + + /* + * Unique + * + */ + public function unique(){ + return $this->addAttribute("unique"); + } + + /* + * Positive + * + */ + public function positive(){ + return $this->addAttribute("positive"); + } + + /* + * Update current + * + */ + public function updateCurrent(){ + return $this->addAttribute("updateCurrent"); + } + + /* + * Current + * + */ + public function current(){ + return $this->addAttribute("current"); + } + + /* + * Nullable + * + */ + public function nullable(){ + return $this->addAttribute("nullable"); + } + + /* + * Not nullable + * + */ + public function notNullable(){ + return $this->addAttribute("notNullable"); + } + + /* + * Default + * + */ + public function default($value){ + return $this->addAttribute("default", array($value)); + } + + /* + * After + * + */ + public function after($column){ + return $this->addAttribute("after", array($column)); + } + + /* + * Add attribute + * + */ + protected function addAttribute($name, array $parameters=array()){ + array_push($this->columns[$this->column]['attributes'], $name); + array_push($this->columns[$this->column]['attributes_parameters'], $parameters); + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Database/Schema/CreateColumn.php b/src/Database/Schema/CreateColumn.php new file mode 100644 index 0000000..da569b8 --- /dev/null +++ b/src/Database/Schema/CreateColumn.php @@ -0,0 +1,30 @@ +driver)->createColumnSql($this->table, $this->columns); + $this->schema->execute($sql); + + return true; + } +} \ No newline at end of file diff --git a/src/Database/Schema/CreateTable.php b/src/Database/Schema/CreateTable.php new file mode 100644 index 0000000..9ff23cb --- /dev/null +++ b/src/Database/Schema/CreateTable.php @@ -0,0 +1,31 @@ +driver)->createTableSql($this->table, $this->columns); + + $this->schema->execute($sql); + + return true; + } +} \ No newline at end of file diff --git a/src/Database/Schema/Foreign.php b/src/Database/Schema/Foreign.php new file mode 100644 index 0000000..954ea82 --- /dev/null +++ b/src/Database/Schema/Foreign.php @@ -0,0 +1,103 @@ +schema = $schema; + $this->driver = $driver; + $this->foreign_table = $foreign_table; + $this->primary_table = $primary_table; + } + + /* + * ON UPDATE CASCADE + * + */ + public function onUpdate(){ + $this->on_update = true; + + return clone($this); + } + + /* + * ON DELETE CASCADE + * + */ + public function onDelete(){ + $this->on_delete = true; + + return clone($this); + } + + /* + * Add foreign key + * + */ + public function add($foreign_column, $primary_column, $index=null){ + // Cascade + $on_update = ($this->on_update==true) ? "ON UPDATE CASCADE" : ''; + $on_delete = ($this->on_delete==true) ? " ON DELETE CASCADE" : ''; + $cascade = $on_update . $on_delete; + + $sql = ($this->driver)->addForeign($this->foreign_table, $foreign_column, $this->primary_table, $primary_column, $index, $cascade); + + $this->schema->execute($sql); + + return true; + } + + /* + * Update foreign key + * + */ + public function update($foreign_column, $primary_column, $index=null){ + // Cascade + $on_update = ($this->on_update==true) ? "ON UPDATE CASCADE" : ''; + $on_delete = ($this->on_delete==true) ? " ON DELETE CASCADE" : ''; + $cascade = $on_update . $on_delete; + + $sql = ($this->driver)->updateForeign($this->foreign_table, $foreign_column, $this->primary_table, $primary_column, $index, $cascade); + + $this->schema->execute($sql); + + return true; + } + + /* + * Delete foreign key + * + */ + public function delete($column, $index=false){ + $sql = ($this->driver)->deleteForeign($this->foreign_table, $column, $index); + + $this->schema->execute($sql); + + return true; + } + +} \ No newline at end of file diff --git a/src/Database/Schema/Sql.php b/src/Database/Schema/Sql.php new file mode 100644 index 0000000..80dbf87 --- /dev/null +++ b/src/Database/Schema/Sql.php @@ -0,0 +1,40 @@ +drive))->{$name}(...$args); + } + +} \ No newline at end of file diff --git a/src/Database/Schema/mysql.php b/src/Database/Schema/mysql.php new file mode 100644 index 0000000..b3f2d9e --- /dev/null +++ b/src/Database/Schema/mysql.php @@ -0,0 +1,391 @@ + "BIGINT(20) AUTO_INCREMENT PRIMARY KEY", + "bigInteger" => "BIGINT", + "binary" => "BLOB", + "boolean" => "BOOLEAN", + "char" => "CHAR(:0)", + "date" => "DATE", + "dateTime" => "DATETIME", + "decimal" => "DECIMAL(:0, :1)", + "double" => "DOUBLE(:0, :1)", + "enum" => "ENUM(:0)", + "float" => "FLOAT", + "increments" => "BIGINT(20) AUTO_INCREMENT PRIMARY KEY", + "integer" => "INTEGER", + "longText" => "LONGTEXT", + "mediumInteger" => "MEDIUMINT", + "mediumText" => "mediumText", + "smallInteger" => "SMALLINT", + "tinyInteger" => "TINYINT", + "string" => "VARCHAR(:0)", + "text" => "TEXT", + "json" => "JSON", + "time" => "TIME", + "timestamp" => "TIMESTAMP", + ]; + + /* + * Attributes type + * + */ + public $attributesType = [ + "primary" => "PRIMARY KEY", + "auto" => "AUTO_INCREMENT", + "unique" => "UNIQUE", + "positive" => "unsigned", + "updateCurrent" => "ON UPDATE CURRENT_TIMESTAMP", + "current" => "DEFAULT CURRENT_TIMESTAMP", + "nullable" => "NULL DEFAULT NULL", + "notNullable" => "NOT NULL", + "default" => "DEFAULT :0", + "after" => "AFTER :0", + ]; + + /** + * Get the name of the currently used database + * + */ + public function database() + { + // sql + $sql = 'SELECT database()'; + + return $sql; + } + + /** + * Get the name of databases + * + */ + public function databases() + { + $sql = "SHOW DATABASES"; + + return $sql; + } + + /** + * Create new databases + * + */ + public function createDatabase($database) + { + $sql = "CREATE DATABASE $database"; + + return $sql; + } + + /** + * Delete database + * + */ + public function deleteDatabase($databases) + { + // Databases + if(is_string($databases)){ + $databases = explode(",", $databases); + } + + // Sql + $sql = ""; + foreach($databases as $database){ + $database = trim($database); + + $sql .= "DROP DATABASE $database; "; + } + + return $sql; + } + + /** + * Get the list of the tables + * + */ + public function tables() + { + $sql = "SHOW TABLES"; + + return $sql; + } + + /** + * has table + * + */ + public function hasTable($table) + { + $sql = "SHOW TABLES LIKE '$table'"; + + return $sql; + } + + /** + * Delete Table + * + */ + public function deleteTable($tables) { + // Tables + if(is_string($tables)){ + $tables = explode(",", $tables); + } + + // Sql + $sql = ""; + foreach($tables as $table){ + $table = trim($table); + + $sql .= "DROP TABLE $table; "; + } + + // return + return $sql; + } + + /** + * Rename Table + * + */ + public function renameTable($old, $new) { + // sql + $sql = "RENAME TABLE `$old` TO `$new`;"; + + // return + return $sql; + } + + /** + * Clean Table + * + */ + public function cleanTable($table) { + // sql + $sql = "TRUNCATE TABLE $table;"; + + // return + return $sql; + } + + /** + * Create new table + * + */ + public function createTable($schema, $table) + { + return new CreateTable($schema, $this, $table); + } + + public function createTableSql($table, $columns) + { + // Sql start + $sql_start = "CREATE TABLE IF NOT EXISTS `$table` ("; + + // Sql end + $sql_end = ")"; + + // Sql + $sql = $this->columnSql($columns); + + $sql = $sql_start . $sql . $sql_end . ';'; + + return $sql; + } + + protected function columnSql($columns, $prefix='') + { + $sql = ''; + + foreach ($columns as $key => $column) { + // Column name + $column_name = $key; + + // Key + $sql .= ' ' . $prefix . "`$key`"; + + // Type + $sql .= ' ' . $this->columnsType[$column['type']]; + + // Parameters + foreach ($column['parameters'] as $key => $parameter) { + if(is_array($parameter)){ + $parameter = "'" . implode("', '", $parameter) . "'" ; + } + + $sql = str_replace(":$key", $parameter, $sql); + } + + // Attributes + foreach ($column['attributes'] as $key => $attribute) { + $sql .= ' ' . $this->attributesType[$attribute]; + + $parameter = implode("', '", $column['attributes_parameters'][$key]); + $sql = str_replace(":$key", $parameter, $sql); + $sql = str_replace(":column", $column_name, $sql); + } + + // End + $sql .= ','; + } + + return rtrim($sql, ','); + } + + /** + * Create foreign keys + * + */ + public function foreign($schema, $foreign_table, $primary_table) + { + return new Foreign($schema, $this, $foreign_table, $primary_table); + } + + /** + * Add foreign key + * + */ + public function addForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = "ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade"; + + return $sql; + } + + /** + * Update foreign key + * + */ + public function updateForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = " + ALTER TABLE $foreign_table DROP FOREIGN KEY $index; + ALTER TABLE $foreign_table DROP INDEX $index; + ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade, + "; + + return $sql; + } + + /** + * Delete foreign key + * + */ + public function deleteForeign($table, $column, $index=false) { + $index = ($this->on_update==true) ? "fk_$column" : $index; + + $sql = " + ALTER TABLE $table DROP FOREIGN KEY $index; + ALTER TABLE $table DROP INDEX $index; + "; + + return $sql; + } + + /** + * Create column + * + */ + public function createColumn($schema, $table) { + return new CreateColumn($schema, $this, $table); + } + + public function createColumnSql($table, $columns) + { + // Sql start + $sql_start = "ALTER TABLE $table "; + + // Sql + $sql = $this->columnSql($columns, 'ADD COLUMN '); + + $sql = $sql_start . $sql . ';'; + + return $sql; + } + + /** + * Get the list of the columns in tables + * + */ + public function columns($table) + { + $sql = "SHOW COLUMNS FROM `$table`"; + + return $sql; + } + + /** + * has column + * + */ + public function hasColumn($table, $column) + { + $sql = "SHOW COLUMNS FROM `$table` LIKE '$column'"; + + return $sql; + } + + /** + * Delete column + * + */ + public function deleteColumn($table, $columns) { + // Columns + if(is_string($columns)){ + $columns = explode(",", $columns); + } + + $columns = implode(', ', $columns); + + // Sql + $sql = "ALTER TABLE $table DROP Column $columns"; + + // return + return $sql; + } + + /** + * Rename column + * + */ + public function renameColumn($table, $old, $new) { + // sql + $sql = "ALTER TABLE $table RENAME COLUMN `$old` to `$new`;"; + + // return + return $sql; + } + +} \ No newline at end of file diff --git a/src/Database/Schema/pgsql.php b/src/Database/Schema/pgsql.php new file mode 100644 index 0000000..761d331 --- /dev/null +++ b/src/Database/Schema/pgsql.php @@ -0,0 +1,388 @@ + "bigserial NOT NULL PRIMARY KEY", + "bigInteger" => "BIGINT", + "binary" => "BLOB", + "boolean" => "BOOLEAN", + "char" => "CHAR(:0)", + "date" => "date", + "dateTime" => "TIMESTAMP", + "decimal" => "DECIMAL(:0, :1)", + "double" => "DOUBLE PRECISION", + "enum" => "", + "float" => "FLOAT", + "increments" => "bigserial NOT NULL PRIMARY KEY", + "integer" => "INTEGER", + "longText" => "TEXT", + "mediumInteger" => "INTEGER", + "mediumText" => "TEXT", + "smallInteger" => "INTEGER", + "tinyInteger" => "INTEGER", + "string" => "VARCHAR(:0)", + "text" => "TEXT", + "json" => "JSON", + "time" => "TIME", + "timestamp" => "TIMESTAMP", + ]; + + /* + * Attributes type + * + */ + public $attributesType = [ + "primary" => "PRIMARY KEY", + "auto" => "", + "unique" => "UNIQUE", + "positive" => "unsigned", + "updateCurrent" => "", + "current" => "DEFAULT CURRENT_TIMESTAMP", + "nullable" => "NULL DEFAULT NULL", + "notNullable" => "NOT NULL", + "default" => "DEFAULT :0", + "after" => "", + ]; + + /** + * Get the name of the currently used database + * + */ + public function database() + { + // sql + $sql = 'SELECT database()'; + + return $sql; + } + + /** + * Get the name of databases + * + */ + public function databases() + { + $sql = "SELECT datname FROM pg_catalog.pg_database"; + + return $sql; + } + + /** + * Create new databases + * + */ + public function createDatabase($database) + { + $sql = "CREATE DATABASE $database"; + + return $sql; + } + + /** + * Delete database + * + */ + public function deleteDatabase($databases) + { + // Databases + if(is_string($databases)){ + $databases = explode(",", $databases); + } + + // Sql + $sql = ""; + foreach($databases as $database){ + $database = trim($database); + + $sql .= "DROP DATABASE $database; "; + } + + return $sql; + } + + /** + * Get the list of the tables + * + */ + public function tables() + { + $sql = "select table_name from information_schema.tables where table_schema = 'public'"; + + return $sql; + } + + /** + * has table + * + */ + public function hasTable($table) + { + $sql = "select table_name from information_schema.tables WHERE table_schema = 'public' and table_name='$table'"; + + return $sql; + } + + /** + * Delete Table + * + */ + public function deleteTable($tables) { + // Tables + if(is_string($tables)){ + $tables = explode(",", $tables); + } + + // Sql + $sql = ""; + foreach($tables as $table){ + $table = trim($table); + + $sql .= "DROP TABLE $table; "; + } + + // return + return $sql; + } + + /** + * Rename Table + * + */ + public function renameTable($old, $new) { + // sql + $sql = "RENAME TABLE '$old' TO '$new';"; + + // return + return $sql; + } + + /** + * Clean Table + * + */ + public function cleanTable($table) { + // sql + $sql = "TRUNCATE TABLE $table;"; + + // return + return $sql; + } + + /** + * Create new table + * + */ + public function createTable($schema, $table) + { + return new CreateTable($schema, $this, $table); + } + + public function createTableSql($table, $columns) + { + // Sql start + $sql_start = "CREATE TABLE IF NOT EXISTS $table ("; + + // Sql end + $sql_end = ")"; + + // Sql + $sql = $this->columnSql($columns); + + $sql = $sql_start . $sql . $sql_end . ';'; + + return $sql; + } + + protected function columnSql($columns, $prefix='') + { + $sql = ''; + + foreach ($columns as $key => $column) { + // Column name + $column_name = $key; + + // Key + $sql .= ' ' . $prefix . "$key"; + + // Type + $sql .= ' ' . $this->columnsType[$column['type']]; + + // Parameters + foreach ($column['parameters'] as $key => $parameter) { + if(is_array($parameter)){ + $parameter = "'" . implode("', '", $parameter) . "'" ; + } + + $sql = str_replace(":$key", $parameter, $sql); + } + + // Attributes + foreach ($column['attributes'] as $key => $attribute) { + $sql .= ' ' . $this->attributesType[$attribute]; + + $parameter = implode("', '", $column['attributes_parameters'][$key]); + $sql = str_replace(":$key", $parameter, $sql); + $sql = str_replace(":column", $column_name, $sql); + } + + // End + $sql .= ','; + } + + return rtrim($sql, ','); + } + + /** + * Create foreign keys + * + */ + public function foreign($schema, $foreign_table, $primary_table) + { + return new Foreign($schema, $this, $foreign_table, $primary_table); + } + + /** + * Add foreign key + * + */ + public function addForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = "ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade"; + + return $sql; + } + + /** + * Update foreign key + * + */ + public function updateForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = " + ALTER TABLE $foreign_table DROP FOREIGN KEY $index; + ALTER TABLE $foreign_table DROP INDEX $index; + ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade, + "; + + return $sql; + } + + /** + * Delete foreign key + * + */ + public function deleteForeign($table, $column, $index=false) { + $index = ($this->on_update==true) ? "fk_$column" : $index; + + $sql = " + ALTER TABLE $table DROP CONSTRAINT $index; + ALTER TABLE $table DROP INDEX $index; + "; + + return $sql; + } + + /** + * Create column + * + */ + public function createColumn($schema, $table) { + return new CreateColumn($schema, $this, $table); + } + + public function createColumnSql($table, $columns) + { + // Sql start + $sql_start = "ALTER TABLE $table "; + + // Sql + $sql = $this->columnSql($columns, 'ADD COLUMN '); + + $sql = $sql_start . $sql . ';'; + + return $sql; + } + + /** + * Get the list of the columns in tables + * + */ + public function columns($table) + { + $sql = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table'"; + + return $sql; + } + + /** + * has column + * + */ + public function hasColumn($table, $column) + { + $sql = "SELECT $column FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' ORDER BY ORDINAL_POSITION"; + + return $sql; + } + + /** + * Delete column + * + */ + public function deleteColumn($table, $columns) { + // Columns + if(is_string($columns)){ + $columns = explode(",", $columns); + } + + // Sql + $sql = "ALTER TABLE $table"; + foreach($columns as $column){ + $column = trim($column); + + $sql .= " DROP COLUMN $column,"; + } + + $sql = rtrim($sql, ','); + + // return + return $sql; + } + + /** + * Rename column + * + */ + public function renameColumn($table, $old, $new) { + // sql + $sql = "ALTER TABLE $table RENAME COLUMN $old to $new;"; + + // return + return $sql; + } + +} \ No newline at end of file diff --git a/src/Database/Schema/sqlite.php b/src/Database/Schema/sqlite.php new file mode 100644 index 0000000..01afc81 --- /dev/null +++ b/src/Database/Schema/sqlite.php @@ -0,0 +1,384 @@ + "BIGINT(20) PRIMARY KEY", + "bigInteger" => "BIGINT", + "binary" => "BLOB", + "boolean" => "BOOLEAN", + "char" => "CHAR(:0)", + "date" => "DATE", + "dateTime" => "DATETIME", + "decimal" => "DECIMAL(:0, :1)", + "double" => "DOUBLE(:0, :1)", + "enum" => "", + "float" => "FLOAT", + "increments" => "BIGINT(20) PRIMARY KEY", + "integer" => "INTEGER", + "longText" => "LONGTEXT", + "mediumInteger" => "MEDIUMINT", + "mediumText" => "mediumText", + "smallInteger" => "SMALLINT", + "tinyInteger" => "TINYINT", + "string" => "VARCHAR(:0)", + "text" => "TEXT", + "json" => "JSON", + "time" => "TIME", + "timestamp" => "TIMESTAMP", + ]; + + /* + * Attributes type + * + */ + public $attributesType = [ + "primary" => "PRIMARY KEY", + "auto" => "", + "unique" => "UNIQUE", + "positive" => "unsigned", + "updateCurrent" => "", + "current" => "DEFAULT CURRENT_TIMESTAMP", + "nullable" => "NULL DEFAULT NULL", + "notNullable" => "NOT NULL", + "default" => "DEFAULT :0", + "after" => "AFTER :0", + ]; + + /** + * Get the name of the currently used database + * + */ + public function database() + { + // sql + $sql = 'SELECT database()'; + + return $sql; + } + + /** + * Get the name of databases + * + */ + public function databases() + { + $sql = ""; + + return $sql; + } + + /** + * Create new databases + * + */ + public function createDatabase($database) + { + return false; + } + + /** + * Delete database + * + */ + public function deleteDatabase($databases) + { + // Databases + if(is_string($databases)){ + $databases = explode(",", $databases); + } + + // Sql + $sql = ""; + foreach($databases as $database){ + $database = trim($database); + + $sql .= "DROP DATABASE $database; "; + } + + return $sql; + } + + /** + * Get the list of the tables + * + */ + public function tables() + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'"; + + return $sql; + } + + /** + * Delete Table + * + */ + public function deleteTable($tables) { + // Tables + if(is_string($tables)){ + $tables = explode(",", $tables); + } + + // Sql + $sql = ""; + foreach($tables as $table){ + $table = trim($table); + + $sql .= "DROP TABLE $table; "; + } + + // return + return $sql; + } + + /** + * Rename Table + * + */ + public function renameTable($old, $new) { + // sql + $sql = "RENAME TABLE `$old` TO `$new`;"; + + // return + return $sql; + } + + /** + * Clean Table + * + */ + public function cleanTable($args) { + // table + $table = $args; + + // sql + $sql = "TRUNCATE TABLE $table;"; + + // return + return $sql; + } + + /** + * has table + * + */ + public function hasTable($table) + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' AND name='$table'"; + + return $sql; + } + + + /** + * Create new table + * + */ + public function createTable($schema, $table) + { + return new CreateTable($schema, $this, $table); + } + + public function createTableSql($table, $columns) + { + // Sql start + $sql_start = "CREATE TABLE IF NOT EXISTS `$table` ("; + + // Sql end + $sql_end = ")"; + + // Sql + $sql = $this->columnSql($columns); + + $sql = $sql_start . $sql . $sql_end . ';'; + + return $sql; + } + + protected function columnSql($columns, $prefix='', $symbol=',') + { + $sql = ''; + + foreach ($columns as $key => $column) { + // Column name + $column_name = $key; + + // Key + $sql .= ' ' . $prefix . "`$key`"; + + // Type + $sql .= ' ' . $this->columnsType[$column['type']]; + + // Parameters + foreach ($column['parameters'] as $key => $parameter) { + if(is_array($parameter)){ + $parameter = "'" . implode("', '", $parameter) . "'" ; + } + + $sql = str_replace(":$key", $parameter, $sql); + } + + // Attributes + foreach ($column['attributes'] as $key => $attribute) { + $sql .= ' ' . $this->attributesType[$attribute]; + + $parameter = implode("', '", $column['attributes_parameters'][$key]); + $sql = str_replace(":$key", $parameter, $sql); + $sql = str_replace(":column", $column_name, $sql); + } + + // End + $sql .= $symbol; + } + + return rtrim($sql, ','); + } + + /** + * Create foreign keys + * + */ + public function foreign($schema, $foreign_table, $primary_table) + { + return new Foreign($schema, $this, $foreign_table, $primary_table); + } + + /** + * Add foreign key + * + */ + public function addForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = "ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade"; + + return $sql; + } + + /** + * Update foreign key + * + */ + public function updateForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = " + ALTER TABLE $foreign_table DROP FOREIGN KEY $index; + ALTER TABLE $foreign_table DROP INDEX $index; + ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade, + "; + + return $sql; + } + + /** + * Delete foreign key + * + */ + public function deleteForeign($table, $column, $index=false) { + $index = ($this->on_update==true) ? "fk_$column" : $index; + + $sql = " + ALTER TABLE $table DROP CONSTRAINT $index; + ALTER TABLE $table DROP INDEX $index; + "; + + return $sql; + } + + /** + * Create column + * + */ + public function createColumn($schema, $table) { + return new CreateColumn($schema, $this, $table); + } + + public function createColumnSql($table, $columns) + { + // Sql + $sql = $this->columnSql($columns, "ALTER TABLE $table ADD COLUMN ", ';'); + + return $sql; + } + + /** + * Get the list of the columns in tables + * + */ + public function columns($table) + { + $sql = "SELECT sql FROM sqlite_master WHERE tbl_name = 'supportContacts' AND type = '$table'"; + + return $sql; + } + + /** + * has column + * + */ + public function hasColumn($table, $column) + { + $sql = "SELECT sql FROM sqlite_master WHERE tbl_name = 'supportContacts' AND type = '$table'"; + + return $sql; + } + + /** + * Delete column + * + */ + public function deleteColumn($table, $columns) { + // Columns + if(is_string($columns)){ + $columns = explode(",", $columns); + } + + // Sql + $sql = ""; + foreach($columns as $column){ + $column = trim($column); + + $sql .= "ALTER TABLE $table DROP COLUMN $column;"; + } + + // return + return $sql; + } + + /** + * Rename column + * + */ + public function renameColumn($table, $old, $new) { + // sql + $sql = "ALTER TABLE $table RENAME COLUMN $old to $new;"; + + // return + return $sql; + } + +} \ No newline at end of file diff --git a/src/Database/Schema/sqlsrv.php b/src/Database/Schema/sqlsrv.php new file mode 100644 index 0000000..be7cab3 --- /dev/null +++ b/src/Database/Schema/sqlsrv.php @@ -0,0 +1,382 @@ + "BIGINT(20) IDENTITY(1,1) PRIMARY KEY", + "bigInteger" => "BIGINT", + "binary" => "VARBINARY(max)", + "boolean" => "BIT", + "char" => "CHAR(:0)", + "date" => "DATE", + "dateTime" => "DATETIME", + "decimal" => "DECIMAL(:0, :1)", + "double" => "float", + "enum" => "VARCHAR(10) CHECK (:column IN(:0))", + "float" => "FLOAT", + "increments" => "BIGINT(20) IDENTITY(1,1) PRIMARY KEY", + "integer" => "INTEGER", + "longText" => "NVARCHAR(max)", + "mediumInteger" => "bigint", + "mediumText" => "nvarchar", + "smallInteger" => "smallint", + "tinyInteger" => "tinyint", + "string" => "nvarchar(:0)", + "text" => "TEXT", + "json" => "nvarchar(max)", + "time" => "TIME", + "timestamp" => "TIMESTAMP", + ]; + + /* + * Attributes type + * + */ + public $attributesType = [ + "primary" => "PRIMARY KEY", + "auto" => "AUTO_INCREMENT", + "unique" => "UNIQUE", + "positive" => "unsigned", + "updateCurrent" => "", + "current" => "DEFAULT getdate()", + "nullable" => "NULL DEFAULT NULL", + "notNullable" => " NOT NULL", + "default" => "DEFAULT :0", + "after" => "", + ]; + + /** + * Get the name of the currently used database + * + */ + public function database() + { + // sql + $sql = 'SELECT database()'; + + return $sql; + } + + /** + * Get the name of databases + * + */ + public function databases() + { + $sql = "SELECT name FROM master.dbo.sysdatabases"; + + return $sql; + } + + /** + * Create new databases + * + */ + public function createDatabase($database) + { + $sql = "CREATE DATABASE $database"; + + return $sql; + } + + /** + * Delete database + * + */ + public function deleteDatabase($databases) + { + // Databases + if(is_string($databases)){ + $databases = explode(",", $databases); + } + + // Sql + $sql = ""; + foreach($databases as $database){ + $database = trim($database); + + $sql .= "DROP DATABASE $database; "; + } + + return $sql; + } + + /** + * Get the list of the tables + * + */ + public function tables() + { + $sql = "select table_name from information_schema.tables"; + + return $sql; + } + + /** + * has table + * + */ + public function hasTable($table) + { + $sql = "select TABLE_NAME from information_schema.tables WHERE table_name='$table'"; + + return $sql; + } + + /** + * Delete Table + * + */ + public function deleteTable($tables) { + // Tables + if(is_string($tables)){ + $tables = explode(",", $tables); + } + + // Sql + $sql = ""; + foreach($tables as $table){ + $table = trim($table); + + $sql .= "DROP TABLE $table; "; + } + + // return + return $sql; + } + + /** + * Rename Table + * + */ + public function renameTable($old, $new) { + // sql + $sql = "sp_rename '$old', '$new'"; + + // return + return $sql; + } + + /** + * Clean Table + * + */ + public function cleanTable($table) { + // sql + $sql = "TRUNCATE TABLE $table;"; + + // return + return $sql; + } + + /** + * Create new table + * + */ + public function createTable($schema, $table) + { + return new CreateTable($schema, $this, $table); + } + + public function createTableSql($table, $columns) + { + // Sql start + $sql_start = "CREATE TABLE $table ("; + + // Sql end + $sql_end = ")"; + + // Sql + $sql = $this->columnSql($columns); + + $sql = $sql_start . $sql . $sql_end . ';'; + + return $sql; + } + + protected function columnSql($columns, $prefix='', $symbol=',') + { + $sql = ''; + + foreach ($columns as $key => $column) { + // Column name + $column_name = $key; + + // Key + $sql .= ' ' . $prefix . "$key"; + + // Type + $sql .= ' ' . $this->columnsType[$column['type']]; + + // Parameters + foreach ($column['parameters'] as $key => $parameter) { + if(is_array($parameter)){ + $parameter = "'" . implode("', '", $parameter) . "'" ; + } + + $sql = str_replace(":$key", $parameter, $sql); + } + + + $sql = str_replace(":column", $column_name, $sql); + + // Attributes + foreach ($column['attributes'] as $key => $attribute) { + + $sql .= ' ' . $this->attributesType[$attribute]; + + $parameter = implode("', '", $column['attributes_parameters'][$key]); + $sql = str_replace(":$key", $parameter, $sql); + } + + // End + $sql .= $symbol; + } + + return rtrim($sql, ','); + } + + /** + * Create foreign keys + * + */ + public function foreign($schema, $foreign_table, $primary_table) + { + return new Foreign($schema, $this, $foreign_table, $primary_table); + } + + /** + * Add foreign key + * + */ + public function addForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = "ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade"; + + return $sql; + } + + /** + * Update foreign key + * + */ + public function updateForeign($foreign_table, $foreign_column, $primary_table, $primary_column, $index=null, $cascade='') { + $index = (is_null($index)) ? "fk_$foreign_column" : $index; + + $sql = " + ALTER TABLE $foreign_table DROP FOREIGN KEY $index; + ALTER TABLE $foreign_table DROP INDEX $index; + ALTER TABLE $foreign_table ADD CONSTRAINT $index FOREIGN KEY ($foreign_column) REFERENCES $primary_table($primary_column) $cascade, + "; + + return $sql; + } + + /** + * Delete foreign key + * + */ + public function deleteForeign($table, $column, $index=false) { + $index = ($this->on_update==true) ? "fk_$column" : $index; + + $sql = " + ALTER TABLE $table DROP CONSTRAINT $index; + ALTER TABLE $table DROP INDEX $index; + "; + + return $sql; + } + + /** + * Create column + * + */ + public function createColumn($schema, $table) { + return new CreateColumn($schema, $this, $table); + } + + public function createColumnSql($table, $columns) + { + // Sql + $sql = "ALTER TABLE posts Add " . $this->columnSql($columns, "",) . ';'; + + return $sql; + } + + /** + * Get the list of the columns in tables + * + */ + public function columns($table) + { + $sql = "select * from information_schema.columns where table_name = '$table'"; + + return $sql; + } + + /** + * has column + * + */ + public function hasColumn($table, $column) + { + $sql = "SELECT $column FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '$table' ORDER BY ORDINAL_POSITION"; + + return $sql; + } + + /** + * Delete column + * + */ + public function deleteColumn($table, $columns) { + // Columns + if(is_string($columns)){ + $columns = explode(",", $columns); + } + + $columns = implode(', ', $columns); + + // Sql + $sql = "ALTER TABLE $table DROP Column $columns"; + + // return + return $sql; + } + + /** + * Rename column + * + */ + public function renameColumn($table, $old, $new) { + // sql + $sql = "EXEC sp_rename '$table.$old', '$new', 'COLUMN'"; + + // return + return $sql; + } + +} \ No newline at end of file diff --git a/src/Database/pdoDB.php b/src/Database/pdoDB.php new file mode 100644 index 0000000..e211841 --- /dev/null +++ b/src/Database/pdoDB.php @@ -0,0 +1,184 @@ + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ, + ]; + + // Generate connection string + switch ($driver) { + case 'mysql': + $string = "mysql:host=$host;dbname=$dbname;charset=utf8mb4;port=$port"; + break; + + case 'sqlite': + $string = "sqlite:$host"; + break; + + case 'pgsql': + $string = "pgsql:host=$host;dbname=$dbname;port=$port"; + break; + + case 'sqlsrv': + $string = "sqlsrv:Server=$host;Database=$dbname"; + break; + + default: + $string = "mysql:host=$host;dbname=$dbname;charset=utf8mb4;port=$port"; + } + + try { + $pdo = new \PDO($string, $username, $password, $option); + } catch (\PDOException $e) { + if($getError){ + throw new \PDOException($e->getMessage(), (int)$e->getCode()); + } + $pdo = null; + } + + // Set connection + $this->pdo = $pdo; + + // Return connection + return $pdo; + } + + /** + * Connection + * + * Get a connection. + */ + public function connection() { + return $this->pdo; + } + + /** + * Get connect + */ + public function getConnect() + { + return $this->pdo; + } + + /** + * Set connect + */ + public function setConnect($pdo) + { + // PDO class + return $this->pdo = $pdo; + } + + /** + * execute Query + * + */ + public function execute($sql, $parms=[], $fetch="obj") + { + // fetch type + switch ($fetch) { + case 'obj': + $fetch = PDO::FETCH_OBJ; + break; + + case 'num': + $fetch = PDO::FETCH_NUM; + break; + + case 'name': + $fetch = PDO::FETCH_NAMED; + break; + + case 'lazy': + $fetch = PDO::FETCH_LAZY; + break; + + case 'into': + $fetch = PDO::FETCH_INTO; + break; + + case 'class': + $fetch = PDO::FETCH_CLASS; + break; + + case 'bound': + $fetch = PDO::FETCH_BOUND; + break; + + case 'both': + $fetch = PDO::FETCH_BOTH; + break; + + case 'assoc': + $fetch = PDO::FETCH_ASSOC; + break; + + default: + $fetch = PDO::FETCH_OBJ; + break; + } + + // excute + $excute = $this->pdo->prepare($sql); + $excute->execute($parms); + + try { + $excute = $excute->fetchAll($fetch); + } catch (\Throwable $th) { + } + + return $excute; + } + +} \ No newline at end of file diff --git a/src/Debugger.php b/src/Debugger.php new file mode 100644 index 0000000..ed65461 --- /dev/null +++ b/src/Debugger.php @@ -0,0 +1,41 @@ +getConstructor(); + + return $res; + } + + /** + * Properties + * + * Gets properties + * + * Filter (null| static | public | protected | private) + */ + public function properties($class_name, $filter=null) + { + // Filter + $filter = str_replace(['static'], \ReflectionProperty::IS_STATIC, $filter); + $filter = str_replace(['public'], \ReflectionProperty::IS_PUBLIC, $filter); + $filter = str_replace(['protected'], \ReflectionProperty::IS_PROTECTED, $filter); + $filter = str_replace(['private'], \ReflectionProperty::IS_PRIVATE, $filter); + + $class = new \ReflectionClass($class_name); + + if(empty($filter)){ + $res = $class->getProperties(null); + }else { + $res = $class->getProperties($filter); + } + + return $res; + } + + /** + * Default properties + * + * Gets default properties + */ + public function defaultProperties($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getDefaultProperties(); + + return $res; + } + + /** + * File + * + * Gets the filename of the file in which the class has been defined + */ + public function file($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getFileName(); + + return $res; + } + + /** + * Interface + * + * Gets the interface names + */ + public function interface($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getInterfaceNames(); + + return $res; + } + + /** + * Methods + * + * Gets an array of methods + * Filter (static | public | protected | private) + * + * Example : (new Classes)->methods('class_name', 'protected | public') + */ + public function methods($class_name, $filter=null) + { + // Filter + $filter = str_replace(['static'], \ReflectionProperty::IS_STATIC, $filter); + $filter = str_replace(['public'], \ReflectionProperty::IS_PUBLIC, $filter); + $filter = str_replace(['protected'], \ReflectionProperty::IS_PROTECTED, $filter); + $filter = str_replace(['private'], \ReflectionProperty::IS_PRIVATE, $filter); + + $class = new \ReflectionClass($class_name); + + if(empty($filter)){ + $res = $class->getMethods(null); + }else { + $res = $class->getMethods($filter); + } + + return $res; + } + + /** + * Constants + * Gets class constants + */ + public function constants($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getReflectionConstants (); + + return $res; + } + + /** + * Get + * + * Gets name of class with namespace + */ + public function get($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getName(); + + return $res; + } + + /** + * Name + * + * Gets short name + */ + public function name($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getShortName(); + + return $res; + } + + /** + * Namespace + * + * Gets namespace name + */ + public function namespace($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getNamespaceName(); + + return $res; + } + + /** + * Traits + * Returns an array of traits used by this class + */ + public function traits($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->getTraits(); + + return $res; + } + + /** + * Has constant + * Checks if constant is defined + */ + public function hasConstant($class_name, $name) + { + $class = new \ReflectionClass($class_name); + $res = $class->hasConstant($name); + + return $res; + } + + /** + * Has method + * Checks if method is defined + */ + public function hasMethod($class_name, $name) + { + $class = new \ReflectionClass($class_name); + $res = $class->hasMethod($name); + + return $res; + } + + /** + * Has property + * Checks if property is defined + */ + public function hasProperty($class_name, $name) + { + $class = new \ReflectionClass($class_name); + $res = $class->hasProperty($name); + + return $res; + } + + /** + * Is abstract + * Checks if class is abstract + */ + public function isAbstract($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isAbstract(); + + return $res; + } + + /** + * Is anonymous + * Checks if class is anonymous + */ + public function isAnonymous($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isAnonymous(); + + return $res; + } + + /** + * Is cloneable + * Returns whether this class is cloneable + */ + public function isCloneable($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isCloneable(); + + return $res; + } + + /** + * Is final + * Checks if class is final + */ + public function isFinal($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isFinal(); + + return $res; + } + + /** + * Is instantiable + * Checks if the class is instantiable + */ + public function isInstantiable($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isInstantiable(); + + return $res; + } + + /** + * Is nterface + * Checks if the class is an interface + */ + public function isInterface($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isInterface(); + + return $res; + } + + /** + * Is internal + * Checks if class is defined internally by an extension, or the core + */ + public function isInternal($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isInternal(); + + return $res; + } + + /** + * Is iterable + * Check whether this class is iterable + */ + public function isIterable($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isIterable(); + + return $res; + } + + /** + * Is sub-class + * Checks if a subclass + */ + public function isSubClass($class_name, $class) + { + $class = new \ReflectionClass($class_name); + $res = $class->isSubclassOf($class); + + return $res; + } + + /** + * Is sub-class + * Returns whether this is a trait + */ + public function isTrait($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->isTrait(); + + return $res; + } + + /** + * New + * Creates a new class instance from given arguments + */ + public function new($class_name, $arguments=[]) + { + $class = new \ReflectionClass($class_name); + $res = $class->newInstance(...$arguments); + + return $res; + } + + /** + * Instance + * Creates a new class instance without invoking the constructor + */ + public function instance($class_name) + { + $class = new \ReflectionClass($class_name); + $res = $class->newInstanceWithoutConstructor(); + + return $res; + } + +} \ No newline at end of file diff --git a/src/Dev/Cli.php b/src/Dev/Cli.php new file mode 100644 index 0000000..f8ea49a --- /dev/null +++ b/src/Dev/Cli.php @@ -0,0 +1,371 @@ + '0;30', + 'dark_gray' => '1;30', + 'blue' => '0;34', + 'light_blue' => '1;34', + 'green' => '0;32', + 'light_green' => '1;32', + 'cyan' => '0;36', + 'light_cyan' => '1;36', + 'red' => '0;31', + 'light_red' => '1;31', + 'purple' => '0;35', + 'light_purple' => '1;35', + 'brown' => '0;33', + 'yellow' => '1;33', + 'light_gray' => '0;37', + 'white' => '1;37', + ]; + + /** + * background colors + * + */ + private $backgroundColors = [ + 'black' => '40', + 'red' => '41', + 'green' => '42', + 'yellow' => '43', + 'blue' => '44', + 'magenta' => '45', + 'cyan' => '46', + 'light_gray' => '47', + ]; + + /** + * Construct + * + */ + public function __construct(){} + + /** + * Get namespace + * + */ + public function getNamespace() { + return $this->namespace; + } + + /** + * Set namespace + * + */ + public function setNamespace($value) { + $this->namespace = $value; + + return clone($this); + } + + /** + * Get path + * + */ + public function getPath() { + return $this->path; + } + + /** + * Get path + * + */ + public function setPath($path) { + return $this->path = $path; + } + + /** + * Get method + * + */ + public function getMethod() { + return $this->method; + } + + /** + * Get commands + * + **/ + public function getCommands(){ + return $this->commands; + } + + /** + * Set commands + * + **/ + public function setCommands(array $list){ + $list = array_map('trim', $list); + return $this->commands = $list; + + } + + /** + * Commands + * + **/ + public function commands(array $list){ + $list = array_map('trim', $list); + return $this->commands = array_merge($this->commands, $list); + } + + /** + * output. + * + * @param $msg + */ + public function output($msg){ + fwrite(STDOUT, $this->initColoredString($msg, null,'red').PHP_EOL); + } + + /** + * notice. + * + * @param $msg + */ + public function notice($msg, $bg='') + { + fwrite(STDOUT, $this->initColoredString($msg, 'light_gray', $bg).PHP_EOL); + } + + /** + * error. + * + * @param $msg + */ + public function error($msg, $bg='') + { + fwrite(STDERR, $this->initColoredString($msg, 'red', $bg).PHP_EOL); + } + + /** + * warn. + * + * @param $msg + */ + public function warn($msg, $bg='') + { + fwrite(STDOUT, $this->initColoredString($msg, 'yellow', $bg).PHP_EOL); + } + + /** + * success. + * + * @param $msg + */ + public function success($msg, $bg='') + { + fwrite(STDOUT, $this->initColoredString($msg, 'green', $bg).PHP_EOL); + } + + /** + * Menu. + * + */ + public function menu($array) + { + // Index + $index = 1; + + // Colors + $colors = [ + "red", + "yellow", + "green", + "light_gray", + "blue", + "light_purple", + "white", + "dark_gray", + "bink", + "gray", + "light_blue", + "light_green", + "purple", + "cyan", + "brown", + "light_red", + "light_cyan", + ]; + + // Menu + foreach($array as $key => $item) { + if(is_numeric($key)){$key=$item;} + $str = $index . '. ' . $key; + + //$this->warn($str); + fwrite(STDOUT, $this->initColoredString($str, "black",$colors[($index - 1)]).PHP_EOL); + + $index++; + } + + // Exit + exit(); + } + + /** + * Handle. + * + */ + public function handle() + { + // CLI mode + if(PHP_SAPI == 'cli'){ + // Args + array_shift($_SERVER['argv']); + + // Command + $command = $this->command(); + + if(empty($command)){ + $this->menu($this->commands); + exit(); + } + + // Function + $function = $this->function(); + + // Excute + $object = new $command; + + try { + // Access to function + $handle_func = $object->{$this->method}(); + + if(array_key_exists($function, $handle_func)){ + // Acess to target function + $result = call_user_func_array([$object, $handle_func[$function]], $this->parms()); + if(is_string($result)){ echo $result; } + }else{ + $this->error((" Oops, command not found. "), 'light_gray'); + } + + } catch (\Throwable $th) { + $this->error((" Oops, something went wrong. "), 'light_gray'); + $this->error(($th)); + } + + } + } + + /** + * Call. + * + */ + public function call() + { + // Args + $args = func_get_args(); + + // Command + $command = $args[0] ?? null; + $command = ($this->commands)[$command] ?? null; + + if(!is_null($command)){ + if (!class_exists($command)) { + $command = $this->getNamespace() . '/' . $command; + $command = $command; + } + }else{ + throw new \Exception("Oops, command not found."); + } + + $object = new $command; + + // Function + $function = $args[1] ?? null; + + if(is_null($function)){ + throw new \Exception("Oops, something went wrong."); + } + + // Parms + array_shift($args); + array_shift($args); + + // Excute + try { + // Access to function + $handle_func = call_user_func_array([$object, $this->method], []); + if(array_key_exists($function, $handle_func)){ + // Acess to target function + call_user_func_array([$object, $handle_func[$function]], $args); + }else{ + throw new \Exception("Oops, command not found."); + } + + } catch (\Throwable $th) { + throw new \Exception("Oops, something went wrong."); + } + } + + /** + * Exec. + * + */ + public function exec($command) + { + return shell_exec($command); + } + +} \ No newline at end of file diff --git a/src/Dev/Cli/Build/CliBuild.php b/src/Dev/Cli/Build/CliBuild.php new file mode 100644 index 0000000..309e3ed --- /dev/null +++ b/src/Dev/Cli/Build/CliBuild.php @@ -0,0 +1,24 @@ +foregroundColors[$foregroundColor])) { + $coloredString .= "\033[".$this->foregroundColors[$foregroundColor].'m'; + } + if (isset($this->backgroundColors[$backgroundColor])) { + $coloredString .= "\033[".$this->backgroundColors[$backgroundColor].'m'; + } + + $coloredString .= $string."\033[0m"; + + return $coloredString; + } + + /** + * function. + * + */ + protected function function() + { + // Args + $argv = $_SERVER['argv'][0] ?? null; + $argv = explode(':',$argv); + + // Function + $function = $argv[1] ?? $this->defaultMethod(); + + return $function; + } + + /** + * Parameters. + * + */ + protected function parms() + { + unset($_SERVER['argv'][0]); + $parms = $_SERVER['argv']; + + return $parms; + } + + /** + * Default method + * + */ + public function defaultMethod() + { + // Args + $argv = $_SERVER['argv'][0] ?? null; + $argv = explode(':',$argv); + + // Command + $command = $argv[0] ?? null; + $command = ($this->commands)[$command] ?? null; + $command = explode(',', $command)[1] ?? $this->getDefaultMethod(); + $command = trim($command); + + return $command; + } + + /** + * command. + * + */ + public function command() + { + // Args + $argv = $_SERVER['argv'][0] ?? null; + $argv = explode(':',$argv); + + // Command + $command = $argv[0] ?? null; + $command = ($this->commands)[$command] ?? null; + + if(!is_null($command)){ + $command = trim(explode(',', $command)[0]); + $command = $command ?? null; + }else{ + $this->error((" Command not found. "), 'light_gray'); + + // Exit + exit(); + } + + if (!class_exists($command)) { + $command = $this->getNamespace() . '\\' . $command; + $command = $command; + } + + return $command; + } + +} \ No newline at end of file diff --git a/src/Dev/Controller.php b/src/Dev/Controller.php new file mode 100644 index 0000000..e153788 --- /dev/null +++ b/src/Dev/Controller.php @@ -0,0 +1,139 @@ +namespace; + } + + /** + * Set controller namespace + * + */ + public function setNamespace($value) { + $this->namespace = $value; + + return clone($this); + } + + /** + * Get controller path + * + */ + public function getPath() { + return $this->path; + } + + /** + * Get controller path + * + */ + public function setPath($path) { + return $this->path = $path; + } + + /** + * Get default method + * + */ + public function getDefaultMethod() { + return $this->defaultMethod; + } + + /** + * Set default method + * + */ + public function setDefaultMethod($value) { + $this->defaultMethod = $value; + + return clone($this); + } + + /** + * Call + * + */ + public function call($callback, $params=[]) { + // Callback + if(is_array($callback)){ + $callback = $callback; + }else{ + $callback = explode(',', $callback); + $callback = array_map('trim', $callback); + } + + // Class + $class = $callback[0]; + + if (!class_exists($callback[0])) { + $class = trim($this->namespace . '\\' . $callback[0], '\\'); + $class = str_replace(['.', '/', '//', '\\\\'],"\\" ,$class); + + $class = trim($this->namespace . '\\' . $class); + } + + // Method + $method = $callback[1] ?? $this->defaultMethod; + + // Call callback + if (class_exists($class)) { + $object = new $class; + if (method_exists($object, $method)) { + return call_user_func_array([$object, $method], $params); + } else { + throw new \Exception("The method " . $method . " is not exists at " . $class); + } + } else { + throw new \Exception("Class " . $class . " is not found"); + } + + } + +} diff --git a/src/Dev/Controller/Build/ControllerBuild.php b/src/Dev/Controller/Build/ControllerBuild.php new file mode 100644 index 0000000..58b4ea5 --- /dev/null +++ b/src/Dev/Controller/Build/ControllerBuild.php @@ -0,0 +1,23 @@ + 'Invalid callback', + 'array' => 'Array required', + 'listener' => 'Listener not defined', + ]; + + /* + * Namespace + */ + protected $namespace; + + /* + * path + */ + protected $path; + + + /** + * Get namespace class + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * Set namespace class + */ + public function setNamespace(string $namespace) + { + $this->namespace = $namespace; + } + + /** + * Get path + */ + public function getPath() + { + return $this->path; + } + + /** + * Set path + */ + public function setPath(string $path) + { + $this->path = $path; + } + + /** + * + */ + public function listen($name, $callback) + { + if(is_array($callback)){ + $class = $callback[0] ?? ""; + $method = $callback[1] ?? ""; + } + + if(is_string($callback)){ + $callback = explode(",", trim($callback)); + $class = $callback[0] ?? ""; + $method = $callback[1] ?? ""; + } + + $class = trim($class); + $method = trim($method); + + if(!class_exists($class)){ + $class = trim($this->namespace, '\\') . '\\' . $class; + } + + $callback = array(new $class, $method); + + if (!is_callable($callback)) { + throw new \Exception($this->errorMessage['callback']); + } + + $this->events[$name][] = $callback; + + return true; + } + + /** + * + */ + public function listeners($names, $callback) + { + $result = true; + + if (!is_array($names)) { + throw new \Exception($this->errorMessage['array']); + } + + foreach ($names as $name) { + $result = $this->listen($name, $callback); + } + + return $result; + } + + /** + * + */ + public function trigger($name, $argument = null) + { + $name = trim($name); + + if (!isset($this->events[$name])) { + throw new \Exception($this->errorMessage['listener']); + } + + foreach ($this->events[$name] as $event => $callback) { + if($argument && is_array($argument)) { + call_user_func_array($callback, $argument); + } + elseif ($argument && !is_array($argument)) { + call_user_func($callback, $argument); + } + else { + call_user_func($callback); + } + } + + return true; + } + + /** + * + */ + public function remove($name) + { + if (is_array($name)) { + foreach ($name as $n) { + $result = $this->remove($n); + + if (!$result) { + return $result; + } + } + + return true; + } else { + if (isset($this->events[$name])) { + unset($this->events[$name]); + + return true; + } + } + + return false; + } + + /** + * Reset all events + * + * @return bool + */ + public function reset() + { + $this->events = []; + + return true; + } +} \ No newline at end of file diff --git a/src/Dev/Event/Build/EventBuild.php b/src/Dev/Event/Build/EventBuild.php new file mode 100644 index 0000000..4ab018d --- /dev/null +++ b/src/Dev/Event/Build/EventBuild.php @@ -0,0 +1,23 @@ +namespace; + } + + /** + * Set middleware namespace + * + */ + public function setNamespace($value) { + $this->namespace = $value; + + return clone($this); + } + + /** + * Get middleware path + * + */ + public function getPath() { + return $this->path; + } + + /** + * Get middleware path + * + */ + public function setPath($path) { + return $this->path = $path; + } + + /** + * Get method + * + */ + public function getMethod() { + return $this->method; + } + + /** + * Call + * + */ + public function call($callback, $params=[]) { + // Callback + if(is_array($callback)){ + $callback = $callback; + }else{ + $callback = explode(',', $callback); + $callback = array_map('trim', $callback); + } + + // Class + $class = $callback[0]; + + if (!class_exists($callback[0])) { + $class = trim($this->MiddlewareNamespace . '\\' . $callback[0], '\\'); + $class = str_replace(['.', '/', '//', '\\\\'],"\\" ,$class); + + $class = trim($this->MiddlewareNamespace . '\\' . $class); + } + + // Method + $method = $callback[1] ?? $this->defaultMethod; + + // Call callback + if (class_exists($class)) { + $object = new $class; + if (method_exists($object, $method)) { + return call_user_func_array([$object, $method], $params); + } else { + throw new \Exception("The method " . $method . " is not exists at " . $class); + } + } else { + throw new \Exception("Class " . $class . " is not found"); + } + + } + +} diff --git a/src/Dev/Middleware/Build/MiddlewareBuild.php b/src/Dev/Middleware/Build/MiddlewareBuild.php new file mode 100644 index 0000000..cdce935 --- /dev/null +++ b/src/Dev/Middleware/Build/MiddlewareBuild.php @@ -0,0 +1,23 @@ + "framework-services-class", + "servicesEnable" => "framework-services-enable", + "servicesMethod" => "framework-services-method", + ]; + + /** + * Construct + * + */ + public function __construct(){ + // Temp discovery file + $this->tempDiscoveryFile = __DIR__.DIRECTORY_SEPARATOR.'Plugin'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'TempDiscovery.php'; + + // Temp discovery file + $this->tempDiscoveryServices = __DIR__.DIRECTORY_SEPARATOR.'Plugin'.DIRECTORY_SEPARATOR.'Resources'.DIRECTORY_SEPARATOR.'TempDiscoveryServices.php'; + } + + /** + * Discovery + * + **/ + public function discovery() + { + $plugins = array(); + + foreach($this->list() as $plugin){ + $plugin = $plugin[0]; + + $composer = dirname($plugin).DIRECTORY_SEPARATOR.'composer.json'; + + if(file_exists($composer)){ + $data = json_decode(file_get_contents($composer)); + $check = $data->extra->{$this->extra['servicesEnable']} ?? false; + + if($check){ + $class_check = ($data->extra->{$this->extra['services']}) ?? false; + $function_check = ($data->extra->{$this->extra['servicesMethod']}) ?? false; + + $plugins[$data->name] = [ + "path" => $plugin, + "class" => ($class_check) ? $data->extra->{$this->extra['services']} : false, + "function" => ($function_check) ? $data->extra->{$this->extra['servicesMethod']} : false + ]; + } + } + } + + // Save to temp discovery file + file_put_contents($this->tempDiscoveryFile, "discoveryServices(); + + return $plugins; + } + + /** + * Services run + * + **/ + public function servicesRun() + { + return include $this->tempDiscoveryServices; + } + + /** + * Discovery clean + * + **/ + public function discoveryClean() + { + file_put_contents($this->tempDiscoveryFile, ""); + file_put_contents($this->tempDiscoveryServices, ""); + + return true; + } + + /** + * Discovery list + * + **/ + public function DiscoveryList() + { + return include($this->tempDiscoveryFile); + } + + /** + * info + */ + public function info($key){ + // Key + $key = trim($key, "\\"); + $key = str_replace([".", "/"], "\\", $key); + $key .= "\\"; + + // Info + $list = $this->list(); + $plugin = $list[$key][0]; + $composer = dirname($plugin).DIRECTORY_SEPARATOR.'composer.json'; + if(file_exists($composer)){ + $data = json_decode(file_get_contents($composer)); + return $data; + } + + return false; + } + + /** + * Set handle + */ + public function setHandle($value){ + $this->handle = $value; + + return clone($this); + } + + /** + * Get handle + */ + public function getHandle(){ + return $this->handle; + } + + /** + * Set extra + */ + public function setExtra($value){ + $this->extra = $value; + + return clone($this); + } + + /** + * Get extra + */ + public function getExtra(){ + return $this->extra; + } + + /** + * Command + */ + public function command($class, $func=false){ + // Class + $class = str_replace([".", "/"], "\\", $this->info($class)->extra->{$this->extra['services']}); + $class = new $class; + + if(!$func){ + $handle = $this->getHandle(); + }else{ + $handle = $this->getHandle(); + $handle = $class->$handle()[$func]; + } + + // Run submit + return $class->$handle(); + } + +} \ No newline at end of file diff --git a/src/Dev/Plugin/HelpersTrait.php b/src/Dev/Plugin/HelpersTrait.php new file mode 100644 index 0000000..c91d79e --- /dev/null +++ b/src/Dev/Plugin/HelpersTrait.php @@ -0,0 +1,75 @@ +discoveryList()) && count($this->discoveryList())!=0){ + foreach($this->discoveryList() as $plugin){ + $plugin_class = str_replace(['/', '.'], "\\", $plugin['class']); + $plugin_function = $plugin['function']; + + $content .= "(new \\$plugin_class)->{$plugin_function}(); \n"; + } + } + + file_put_contents($this->tempDiscoveryServices, $content); + } + + /** + * List + */ + protected function list(){ + $autoload_psr4 = $this->root("vendor".DIRECTORY_SEPARATOR."composer".DIRECTORY_SEPARATOR."autoload_psr4.php"); + return (include $autoload_psr4); + } + +} \ No newline at end of file diff --git a/src/Dev/Plugin/Resources/TempDiscovery.php b/src/Dev/Plugin/Resources/TempDiscovery.php new file mode 100644 index 0000000..5930fbc --- /dev/null +++ b/src/Dev/Plugin/Resources/TempDiscovery.php @@ -0,0 +1,4 @@ +'; + + echo $html; + + if (is_string($data)) { + echo $data; + } else { + print_r($data); + } + echo ""; + } + + /** + * Dump and die + */ + public function dd($data){ + $this->dump($data); + die(); + } + +} diff --git a/src/Directories/File.php b/src/Directories/File.php new file mode 100644 index 0000000..821563a --- /dev/null +++ b/src/Directories/File.php @@ -0,0 +1,422 @@ +filesystemRoot() . $path; + + return file_exists($path); + } + + /** + * Delete file + * + * @var string $path + * @return mixed + */ + public function delete($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return unlink($path); + } + } + + /** + * Copy file + * + * @var string $path + * @var string $second_path + * @return mixed + */ + public function copy($path, $second_path) { + $path = $this->filesystemRoot() . $path; + $second_path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return copy($path, $second_path); + } + } + + /** + * Move file + * + * @var string $path + * @var string $second_path + * @return mixed + */ + public function move($path, $second_path) { + $path = $this->filesystemRoot() . $path; + $second_path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + $new_path = $second_path.'\\'.$this->fullName($path); + + //Move the file using PHP's rename function. + return rename($path, $new_path); + } + } + + /** + * Full name of file + * + * @var string $path + * @return mixed + */ + public function fullName($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return pathinfo($path, PATHINFO_BASENAME); + } + } + + /** + * Name of file + * + * @var string $path + * @return mixed + */ + public function name($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + $file = pathinfo($path, PATHINFO_FILENAME); + return preg_replace('/\.[^.]+$/', '', $file); + } + } + + + /** + * Folder of file + * + * @var string $path + * @return mixed + */ + public function folder($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return pathinfo($path, PATHINFO_DIRNAME); + } + } + + /** + * Rename of file + * @var string $path + * @var string $name + * + * @return mixed + */ + public function rename($path, $name) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + $new_path = $this->folder($path).'\\'.$name.'.'.$this->extension($path); + return rename($path , $new_path); + } + } + + /** + * Extension of file + * + * @var string $path + * @return mixed + */ + public function extension($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return pathinfo($path, PATHINFO_EXTENSION); + } + } + + /** + * Get extension of file + * + * @var string $path + * @var string $extension + * @return mixed + */ + public function getExtension($path, $extension) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + $file = pathinfo($path, PATHINFO_FILENAME); + return preg_replace('/\.[^.]+$/', '.', $file) . '.' . $extension; + } + } + + /** + * Set extension of file + * + * @var string $path + * @var string $extension + * @return mixed + */ + public function setExtension($path, $extension) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + $new_path = $this->folder($path).'\\' . $this->getExtension($path, $extension); + return rename($path , $new_path); + } + } + + /** + * Is extension of file + * + * @var string $path + * @var string $extension + * @return mixed + */ + public function isExtension($path, $extension) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return (($this->extension($path, $extension)==$extension) ? true : false); + } + } + + /** + * Is writable of file + * + * @var string $path + * @return mixed + */ + public function writable($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return is_writable($path); + } + } + + /** + * Is readable of file + * + * @var string $path + * @return mixed + */ + public function readable($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return is_readable($path); + } + } + + /** + * Get contents of file + * + * @var string $path + * @return mixed + */ + public function get($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return file_get_contents($path); + } + } + + /** + * Set contents of file + * + * @var string $path + * @var string $content + * @return mixed + */ + public function set($path, $content) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return file_put_contents($path, $content); + } + } + + /** + * Read from file + * + * @var string $path + * @return mixed + */ + public function read($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return readfile($path); + } + } + + /** + * Write to file + * + * @var string $path + * @var string $content + * @return mixed + */ + public function write($path, $content) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + $file = fopen($path,"w"); + $file_content = fwrite($file, $content); + fclose($file); + return $file_content; + } + } + + /** + * File create + * + * @var string $path + * @return mixed + */ + public function create($path) { + $path = $this->filesystemRoot() . $path; + + if (!(file_exists($path))) { + $file = fopen($path,"w"); + fwrite($file,''); + fclose($file); + return true; + } + return false; + } + + /** + * File size + * + * @var string $path + * @return mixed + */ + public function size($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return filesize($path); + } + } + + /** + * Mime of file + * + * @var string $path + * @return mixed + */ + public function mime($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return mime_content_type($path); + } + } + + /** + * Bloob + * + * @return string + */ + public function blob($path) { + $path = $this->filesystemRoot() . $path; + + if (file_exists($path)) { + return file_get_contents(addslashes($path)); + } + } + + /** + * History of file + * + * @var string $path + * @var string $action ['update', 'change'] + * @return mixed + */ + public function history($path, $action='update') { + $path = $this->filesystemRoot() . $path; + + // Action + $action_list = array('update', 'change'); + if (!in_array($action, $action_list)){$action="update";} + + if (file_exists($path)) { + if ($action=='update') { // update + return filectime($path); + }else{ // change + return filemtime($path); + } + } + } + + /** + * Namespace extract form file + * + */ + public function namespace($file) + { + return $this->extract($file, 'namespace'); + } + + /** + * Class name extract form file + * + */ + public function className($file) + { + return $this->extract($file, 'class'); + } + + /** + * Class extract form file + * + */ + public function class($file) + { + return $this->namespace($file).'\\'.$this->className($file); + } + +} \ No newline at end of file diff --git a/src/Directories/File/Helpers.php b/src/Directories/File/Helpers.php new file mode 100644 index 0000000..0468f88 --- /dev/null +++ b/src/Directories/File/Helpers.php @@ -0,0 +1,46 @@ +preparePathFileSystem($file); + + $ns = false; + $handle = fopen($file, 'r'); + + if($handle){ + while(($line=fgets($handle)) !== false){ + if(strpos($line, $type) === 0){ + $parts = explode(' ', $line); + $ns = rtrim(trim($parts[1]), ';'); + break; + } + } + fclose($handle); + } + return $ns; + } + +} \ No newline at end of file diff --git a/src/Directories/Folder.php b/src/Directories/Folder.php new file mode 100644 index 0000000..94f7906 --- /dev/null +++ b/src/Directories/Folder.php @@ -0,0 +1,307 @@ +filesystemRoot() . $path; + + return is_dir($path); + } + + /** + * Get all files and folders in folder + * + * @param string $path + * @return mixed + */ + public function get($path) { + $path = $this->filesystemRoot() . $path; + + $ListFiles = array(); + $Files = array(); + + $scannedFiles = array_diff(scandir($path), ['.', '..']); + $Files = array_merge($scannedFiles, $ListFiles); + + return $Files; + } + + /** + * Get all files in folder + * + * @param string $path + * @return mixed + */ + public function files($path) { + $path = $this->filesystemRoot() . $path; + + return $this->list_directory($path, 'files'); + } + + /** + * Get all files in dirs + * + * @param string $path + * @return mixed + */ + public function filesDirs($path) { + $path = $this->filesystemRoot() . $path; + + return $this->listdirsfiles($path); + } + + /** + * get all folders in folder + * + * @param string $path + * @return mixed + */ + public function folders($path) { + $path = $this->filesystemRoot() . $path; + + return $this->list_directory($path, 'folders'); + } + + /** + * get all dirs in path + * + * @param string $path + * @return mixed + */ + public function dirs($path) { + $path = $this->filesystemRoot() . $path; + + return $this->listdirs($path); + } + + /** + * Cleanup folder + * + * @param string $path + * @return mixed + */ + public function clean($path) { + $path = $this->filesystemRoot() . $path; + + // destroy + if (is_dir($path)){ + $this->unlinkr($path); + return true; + }else{ + return false; + } + + } + + /** + * Delete folder + * + * @param string $path + * @return mixed + */ + public function delete($path) { + $path = $this->filesystemRoot() . $path; + + if (is_dir($path)){ + $this->unlinkr($path); + if(rmdir($path)) { + return true; + }else{ + return false; + } + }else{ + return false; + } + } + + /** + * Create folder + * + * @param string $path + * @return mixed + */ + public function create($path, $permissions="0777") { + $path = $this->filesystemRoot() . $path; + + if(!is_dir($path)){ + mkdir($path, $permissions, true); + return true; + }else{ + return false; + } + } + + /** + * Copy folder + * + * @var string $path + * @var string $second_path + * @return mixed + */ + public function copy($path, $second_path) { + $path = $this->filesystemRoot() . $path; + $second_path = $this->preparePathFileSystem($second_path); + + if(is_dir($path)){ + return $this->copy_directory($path, $second_path); + } + } + + /** + * Name of folder + * + * @var string $path + * @return mixed + */ + public function name($path) { + $path = $this->filesystemRoot() . $path; + + if (is_dir($path)) { + $file = pathinfo($path, PATHINFO_FILENAME); + return preg_replace('/\.[^.]+$/', '', $file); + } + } + + /** + * Move folder + * + * @var string $path + * @var string $second_path + * @return mixed + */ + public function move($path, $second_path) { + $path = $this->filesystemRoot() . $path; + $second_path = $this->preparePathFileSystem($second_path); + + if (is_dir($path)) { + $new_path = $second_path.'\\'.$this->name($path); + //Move the file using PHP's rename function. + return $folderMoved = rename($path, $new_path); + } + } + + /** + * Rename of folder + * @var string $path + * @var string $name + * + * @return mixed + */ + public function rename($path, $name) { + $path = $this->filesystemRoot() . $path; + + if (is_dir($path)) { + $new_path = pathinfo($path, PATHINFO_DIRNAME).'\\'.$name; + return rename($path , $new_path); + } + } + + /** + * Is writable of folder + * + * @var string $path + * @return mixed + */ + public function writable($path) { + $path = $this->filesystemRoot() . $path; + + if (is_dir($path)) { + return is_writable($path); + } + } + + /** + * Is readable of folder + * + * @var string $path + * @return mixed + */ + public function readable($path) { + $path = $this->filesystemRoot() . $path; + + if (is_dir($path)) { + return is_readable($path); + } + } + + /** + * Folder size + * + * @var string $path + * @return mixed + */ + public function size($path) { + $path = $this->filesystemRoot() . $path; + + if (is_dir($path)) { + return $this->dirSize($path); + } + } + + /** + * History of folder + * + * @var string $path + * @var string $action ['update', 'change'] + * @return mixed + */ + public function history($path, $action='update') { + $path = $this->filesystemRoot() . $path; + + // Action + $action_list = array('update', 'change'); + if (!in_array($action, $action_list)){$action="update";} + + if (is_dir($path)) { + if ($action=='update') { // update + return filectime($path); + }else{ // change + return filemtime($path); + } + } + } + +} \ No newline at end of file diff --git a/src/Directories/Folder/Helpers.php b/src/Directories/Folder/Helpers.php new file mode 100644 index 0000000..2bfb8cb --- /dev/null +++ b/src/Directories/Folder/Helpers.php @@ -0,0 +1,181 @@ +list_directory($path, 'files'); + + // files main dir + foreach ($files_main_dir as $file_main_dir) { + array_push($files_list, $path.DIRECTORY_SEPARATOR.$file_main_dir); + } + + // files sub dirs + $folders = $this->dirs($path); + + foreach ($folders as $keyFolders => $folder) { + $files = $this->files($folder); + + foreach ($files as $keyFile => $file) { + $existing_array = array('a'=>'b', 'b'=>'c'); + array_push($files_list,$folder.DIRECTORY_SEPARATOR.$file,); + } + } + + // files list + return $files_list; + } + + /** + * List dirs + * + */ + protected function listdirs($dir) { + static $alldirs = array(); + $dirs = glob($dir . '/*', GLOB_ONLYDIR); + if (count($dirs) > 0) { + foreach ($dirs as $d) $alldirs[] = $d; + } + foreach ($dirs as $dir) $this->listdirs($dir); + return $alldirs; + } + + /** + * Unlinkr + * + */ + protected function list_directory($path, $action='files') { + $ListFiles = array(); + $Files = array(); + $Folders = array(); + + $scannedFiles = array_diff(scandir($path), ['.', '..']); + $ListFiles = array_merge($scannedFiles, $ListFiles); + + foreach($scannedFiles as $file) + { + if( is_file($path.DIRECTORY_SEPARATOR.$file) ) + { + array_push($Files, $file); + }else{ + array_push($Folders, $file); + } + } + + return (($action == 'files') ? $Files : $Folders); + } + + /** + * Unlinkr + * + * @param string $path + * @return mixed + */ + protected function unlinkr($dir, $pattern = "*") { + // find all files and folders matching pattern + $files = glob($dir . "/$pattern"); + + //interate thorugh the files and folders + foreach($files as $file){ + //if it is a directory then re-call unlinkr function to delete files inside this directory + if (is_dir($file) and !in_array($file, array('..', '.'))) { + //echo "

    opening directory $file

    "; + $this->unlinkr($file, $pattern); + //remove the directory itself + //echo "

    deleting directory $file

    "; + rmdir($file); + } else if(is_file($file) and ($file != __FILE__)) { + // make sure you don't delete the current script + //echo "

    deleting file $file

    "; + unlink($file); + } + } + } + + /** + * Copy directory + * + */ + protected function copy_directory($src,$dst) { + $dir = opendir($src); + if(!is_dir($dst) || !file_exists($dst)){ @mkdir($dst); } + while(false !== ( $file = readdir($dir)) ) { + if (( $file != '.' ) && ( $file != '..' )) { + if ( is_dir($src . '/' . $file) ) { + $this->copy_directory($src . '/' . $file,$dst . '/' . $file); + } + else { + copy($src . '/' . $file,$dst . '/' . $file); + } + } + } + closedir($dir); + } + + /** + * Get the directory size + * @param directory $dir + * @return integer + */ + protected function dirSize($dir_path) { + $dir = rtrim(str_replace('\\', '/', $dir_path), '/'); + + if (is_dir($dir) === true) { + $totalSize = 0; + $os = strtoupper(substr(PHP_OS, 0, 3)); + // If on a Unix Host (Linux, Mac OS) + if ($os !== 'WIN') { + $io = popen('/usr/bin/du -sb ' . $dir, 'r'); + if ($io !== false) { + $totalSize = intval(fgets($io, 80)); + pclose($io); + return $totalSize; + } + } + // If on a Windows Host (WIN32, WINNT, Windows) + if ($os === 'WIN' && extension_loaded('com_dotnet')) { + $obj = new \COM('scripting.filesystemobject'); + if (is_object($obj)) { + $ref = $obj->getfolder($dir); + $totalSize = $ref->size; + $obj = null; + return $totalSize; + } + } + // If System calls did't work, use slower PHP 5 + $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($dir)); + foreach ($files as $file) { + $totalSize += $file->getSize(); + } + return $totalSize; + } else if (is_file($dir) === true) { + return filesize($dir); + } + } + +} \ No newline at end of file diff --git a/src/Env.php b/src/Env.php new file mode 100644 index 0000000..dc64a09 --- /dev/null +++ b/src/Env.php @@ -0,0 +1,41 @@ + 'Debugger\\resources\\404.php', + '500' => 'Debugger\\resources\\500.php' + ]; + + /** + * Set error page + * + **/ + public function view($path, $code) { + if(!is_null($path)){ + $path = $this->filesystemRoot() . $path; + } + + $path = (is_null($path)) ? "Debugger\\resources\\$code.php" : $path; + + $this->errors_view[$code] = $path; + + return true; + } + + /** + * Set error page by source code + * + **/ + public function viewCode(string $source_code, $code) { + $path = "Debugger\\resources\\customize_$code.php"; + + $this->errors_view[$code] = $path; + + file_put_contents(__DIR__.DIRECTORY_SEPARATOR.$path, $source_code); + + return true; + } + + /* + * Run + * + */ + public function run() { + // Reporting + ini_set('display_errors', 'Off'); + ini_set('display_startup_errors', 'Off'); + error_reporting(-1); + + // Shutdown function + register_shutdown_function([$this, 'shutDown']); + } + + /** + * ShutDown + * + **/ + public function shutDown() { + // Get Error + $error = error_get_last(); + $response = http_response_code(); + + // Response + switch($response) { + case 404: + if(PHP_SAPI != 'cli'){ + ob_end_clean(); + include_once $this->errors_view['404']; + exit(); + }else{ + $msg = "Sorry, Whoops looks like something went wrong."; + fwrite(STDERR, "\033[0;31m $msg \033[0m".PHP_EOL); + } + } + + // Run + if($error){ + if($this->enable){ + // HTTP response code: 500 + http_response_code(500); + + // Clean content + ob_end_clean(); + + // Error + $error = (object) [ + 'number' => $error['type'], + 'file' => $error['file'], + 'line' => $error['line'], + 'type' => $this->error_type($error['type']), + 'message' => nl2br(htmlspecialchars($error['message'])), + 'content' => explode("
    ", highlight_file($error['file'], true)), + ]; + + // View + if(PHP_SAPI != 'cli'){ + include_once "Debugger\\resources\\debugger.php"; + }else{ + $msg = "Sorry, Whoops looks like something went wrong."; + fwrite(STDERR, "\033[0;31m $msg \033[0m".PHP_EOL); + } + // Exit + exit(); + }else{ + // 500 page + if(PHP_SAPI != 'cli'){ + include_once $this->errors_view['500']; + }else{ + $msg = "Sorry, Whoops looks like something went wrong."; + fwrite(STDERR, "\033[0;31m $msg \033[0m".PHP_EOL); + } + } + } + + } + + /** + * Error + * + **/ + public function error($value, $code=500) { + if(PHP_SAPI != 'cli'){ + http_response_code($code); + + throw new \Exception($value); + }else{ + fwrite(STDERR, "\033[0;31m $value \033[0m".PHP_EOL); + } + } + +} \ No newline at end of file diff --git a/src/Exceptions/Debugger/Helpers.php b/src/Exceptions/Debugger/Helpers.php new file mode 100644 index 0000000..9559a30 --- /dev/null +++ b/src/Exceptions/Debugger/Helpers.php @@ -0,0 +1,64 @@ + + + + + Not Found + + + +
    +
    +

    Not Found | 404

    +
    +
    + + \ No newline at end of file diff --git a/src/Exceptions/Debugger/Resources/500.php b/src/Exceptions/Debugger/Resources/500.php new file mode 100644 index 0000000..bc00615 --- /dev/null +++ b/src/Exceptions/Debugger/Resources/500.php @@ -0,0 +1,18 @@ + + + + + + Internal Server Error + + + +
    +
    +

    Sorry, Whoops looks like something went wrong | 505

    +
    +
    + + \ No newline at end of file diff --git a/src/Exceptions/Debugger/Resources/customize_404.php b/src/Exceptions/Debugger/Resources/customize_404.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Exceptions/Debugger/Resources/customize_500.php b/src/Exceptions/Debugger/Resources/customize_500.php new file mode 100644 index 0000000..e69de29 diff --git a/src/Exceptions/Debugger/Resources/debugger.php b/src/Exceptions/Debugger/Resources/debugger.php new file mode 100644 index 0000000..50ddba6 --- /dev/null +++ b/src/Exceptions/Debugger/Resources/debugger.php @@ -0,0 +1,1666 @@ + + + + + + + + Kiaan! have found errors + + + + + + + + + + + + +
    + + +
    +
    +

    + type
    $error->message"; ?>

    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + +
    + +
    + + +

    + ▶️ Go to + file; ?> + (line: line; ?>) +

    +
    + +
    +

    + File +

    + +
    +
    +
    +
    + +

    + + + + + File

    + +
    +
    + +
    +
    +
    + + + + + + File: + + file; ?> + + (line: line; ?>) + +
    +
    +
      + + + content as $key=>$conten) { + ?> + +
    1. + + +
    2. + + + + +
    +
    +
    +
    +
    +
    +
    +
    + + +
    +

    + Information +

    + +
    + + + + + + + + + + + + +
    +

    + Information + + +

    +
    +

    Framework :

    +

    Framework version :

    +

    Current PHP version :

    +

    Error Type : type ;?>

    +

    Error Number :

    +

    Error message : message ;?>

    +

    Error file : file ;?>

    +

    Error line : line ;?>

    +
    +
    +
    + +

    + wish you good luck and happy coding without bugs 😀 +

    + +
    +
    + + + + + \ No newline at end of file diff --git a/src/Exceptions/Debugger/Resources/live.php b/src/Exceptions/Debugger/Resources/live.php new file mode 100644 index 0000000..abf8561 --- /dev/null +++ b/src/Exceptions/Debugger/Resources/live.php @@ -0,0 +1,500 @@ + + + + Not available + + + + + + + + + + + + +
    +
    +
    +
    + Not available +
    + +
    + +

    + Sorry, the service is not available right now. +

    + + +
    +
    + + +
    + + diff --git a/src/Facade.php b/src/Facade.php new file mode 100644 index 0000000..40ba1db --- /dev/null +++ b/src/Facade.php @@ -0,0 +1,119 @@ +__facade(); + $classInstance = new $classInstance(...$arguments); + + // Instance (non-static) + $this->__non_static_facade_instance = $classInstance; + + // Check if instance is already exists (static) + if(self::$__static_facade_instance == null) { + return self::$__static_facade_instance = $classInstance; + } + + // return instance + return $classInstance; + } + + /** + * Return Class + * + * @return mixed + */ + abstract function __facade(); + + /** + * Calls static + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public static function __callStatic(string $name, array $arguments) + { + // Check if instance is already exists + if(self::$__static_facade_instance == null) { + try { + self::$__static_facade_instance = new static; + } catch (\Throwable $th) {} + } + + return self::$__static_facade_instance->$name(...$arguments); + } + + /** + * Calls non-static + * @param string $name + * @param array $arguments + * + * @return mixed + */ + public function __call(string $name, array $arguments) + { + return $this->__non_static_facade_instance->$name(...$arguments); + } + + /* + * Get + */ + function __get($name){ + return $this->__non_static_facade_instance->$name; + } + + /* + * Set + */ + public function __set($name, $value) { + return $this->__non_static_facade_instance->$name = $value; + } + + /* + * Clone + */ + public function __clone(){ + return self::$__static_facade_instance; + } + +} \ No newline at end of file diff --git a/src/File.php b/src/File.php new file mode 100644 index 0000000..d406f2a --- /dev/null +++ b/src/File.php @@ -0,0 +1,41 @@ +callHttp('GET', $url, array(), $header); + } + + /* + * Post + * + */ + public function post($url, $data=[], $header=[]){ + // Call + return $this->callHttp('POST', $url, $data, $header); + } + + /* + * Method + * + * Method custom + */ + public function method($method, $url, $data=[], $header=[]){ + // Call + return $this->callHttp($method, $url, $data, $header); + } + +} \ No newline at end of file diff --git a/src/Http/Input.php b/src/Http/Input.php new file mode 100644 index 0000000..7c43c46 --- /dev/null +++ b/src/Http/Input.php @@ -0,0 +1,246 @@ +get = $_GET; + + // Post + $raw = json_decode(file_get_contents('php://input'), true) ?? array(); + $this->post = array_merge($_POST, $raw); + + // Files + $this->files = $_FILES; + + // Request + $this->request = array_merge($_REQUEST, $_FILES); + } + + /* + * Magic functions + * + */ + public function __set($key, $value){ + return $this->set($key, $value); + } + + public function __get($key){ + return $this->value($key); + } + + /** + * Returns an iterator for the elements. + * + * @return \Iterator Over map elements + */ + public function getIterator() : \Iterator + { + return new \ArrayIterator( $this->request ); + } + + /** + * Determines if an element exists at an offset. + * + * @param mixed $key Key to check for + * @return bool TRUE if key exists, FALSE if not + */ + public function offsetExists( $key ) + { + return isset( $this->request[$key] ); + } + + /** + * Returns an element at a given offset. + * + * @param mixed $key Key to return the element for + * @return mixed Value associated to the given key + */ + public function offsetGet( $key ) + { + return $this->request[$key] ?? null; + } + + /** + * Sets the element at a given offset. + * + * @param mixed $key Key to set the element for + * @param mixed $value New value set for the key + */ + public function offsetSet( $key, $value ) + { + if( $key !== null ) { + $this->request[$key] = $value; + } else { + $this->request[] = $value; + } + } + + /** + * Unsets the element at a given offset. + * + * @param string $key Key for unsetting the item + */ + public function offsetUnset( $key ) + { + unset( $this->request[$key] ); + } + + /** + * Get all request + * + */ + public function all() { + return $this->request; + } + + /** + * Check that the request has the key + * + */ + public function has($key, array $request=null) { + $request = (!is_array($request)) ? $this->request : $request; + + return array_key_exists($key, $request); + } + + /** + * Check that the $_GET request has the key + * + */ + public function hasGet($key) { + return $this->has($key, $this->get); + } + + /** + * Check that the $_POST request has the key + * + */ + public function hasPost($key) { + return $this->has($key, $this->post); + } + + /** + * Check that the $_FILES request has the key + * + */ + public function hasFile($key) { + return $this->has($key, $this->files); + } + + /** + * Get the value from the request + * + */ + public function value($key, array $request=null) { + $request = (!is_array($request)) ? $this->request : $request; + + return ($this->has($key)) ? $request[$key] : false; + } + + /** + * Get the value from the $_GET request + * + */ + public function get($key) { + return $this->value($key, $this->get); + } + + /** + * Get the value from the $_POST request + * + */ + public function post($key) { + return $this->value($key, $this->post); + } + + /** + * Set value for request by the given key + * + */ + public function set($key, $value) { + if($this->has($key, $this->get)){ $this->get[$key] = $value; } + if($this->has($key, $this->post)){ $this->post[$key] = $value; } + if($this->has($key, $this->files)){ $this->files[$key]['name'] = $value; } + if($this->has($key, $this->request)){ $this->request[$key] = $value; } + + return ($this->has($key)) ? $this->request[$key] : false; + } + + /** + * Select file + * + * @param string $key + * @return string $value + */ + public function file($key) { + if($this->has($key, $this->files)){ + return new File($this, $this->files, $key); + } + + return false; + } + +} \ No newline at end of file diff --git a/src/Http/Input/File.php b/src/Http/Input/File.php new file mode 100644 index 0000000..8529d75 --- /dev/null +++ b/src/Http/Input/File.php @@ -0,0 +1,106 @@ +files = $files; + $this->files = $files; + $this->selectedFile = $selectedFile; + } + + /** + * Get file info + * + */ + public function info() { + $key = $this->selectedFile; + $this->selectedFile = ''; + + return $this->files[$key]; + } + + /** + * Save file + * + * @param string $key + * @return string $value + */ + public function save($path) { + $path = $this->class->filesystemRoot() . $path; + + // tmp path + $tmp = $this->files[$this->selectedFile]["tmp_name"]; + $this->selectedFile = ''; + + // upload + if(move_uploaded_file($tmp, $path)) + { + // true + return true; + } + else + { + if(file_exists($tmp)){ + // upload + rename($tmp, $path); + + // true + return true; + }else{ + // Throwable + return $this->files[$this->selectedFile]["error"]; + } + } + } + + /** + * Bloob + * + * @return string + */ + public function blob($file) { + return file_get_contents(addslashes($this->files[$file]['tmp_name'])); + } + +} \ No newline at end of file diff --git a/src/Http/Request.php b/src/Http/Request.php new file mode 100644 index 0000000..effb505 --- /dev/null +++ b/src/Http/Request.php @@ -0,0 +1,161 @@ +uri(), '/') . $this->query(); + + return trim($protocol . $host . $script_name, '/'); + } + +} \ No newline at end of file diff --git a/src/Http/Request/MultipartFormDataParser.php b/src/Http/Request/MultipartFormDataParser.php new file mode 100644 index 0000000..e10252f --- /dev/null +++ b/src/Http/Request/MultipartFormDataParser.php @@ -0,0 +1,193 @@ +files = $_FILES; + $dataset->params = $_POST; + return $dataset; + } + + if ($method == "GET") { + return $dataset; + } + + $GLOBALS["_".$method] = []; + + //get raw input data + $rawRequestData = file_get_contents("php://input"); + if (empty($rawRequestData)) { + return null; + } + + $contentType = $_SERVER["CONTENT_TYPE"]; + + if (!preg_match('/boundary=(.*)$/is', $contentType, $matches)) { + return null; + } + + $boundary = $matches[1]; + $bodyParts = preg_split('/\\R?-+' . preg_quote($boundary, '/') . '/s', $rawRequestData); + array_pop($bodyParts); + + foreach ($bodyParts as $bodyPart) { + if (empty($bodyPart)) { + continue; + } + list($headers, $value) = preg_split('/\\R\\R/', $bodyPart, 2); + $headers =self::parseHeaders($headers); + if (!isset($headers['content-disposition']['name'])) { + continue; + } + if (isset($headers['content-disposition']['filename'])) { + $file = [ + 'name' => $headers['content-disposition']['filename'], + 'type' => array_key_exists('content-type', $headers) ? $headers['content-type'] : 'application/octet-stream', + 'size' => mb_strlen($value, '8bit'), + 'error' => UPLOAD_ERR_OK, + 'tmp_name' => null, + ]; + + if ($file['size'] > self::toBytes(ini_get('upload_max_filesize'))) { + $file['error'] = UPLOAD_ERR_INI_SIZE; + } else { + $tmpResource = tmpfile(); + + if ($tmpResource === false) { + $file['error'] = UPLOAD_ERR_CANT_WRITE; + } else { + $tmpResourceMetaData = stream_get_meta_data($tmpResource); + $tmpFileName = $tmpResourceMetaData['uri']; + if (empty($tmpFileName)) { + $file['error'] = UPLOAD_ERR_CANT_WRITE; + @fclose($tmpResource); + } else { + fwrite($tmpResource, $value); + $file['tmp_name'] = $tmpFileName; + $file['tmp_resource'] = $tmpResource; + } + } + } + $file["size"] = self::toFormattedBytes($file["size"]); + $_FILES[$headers['content-disposition']['name']] = $file; + $dataset->files[$headers['content-disposition']['name']] = $file; + } else { + //parameters + $dataset->params[$headers['content-disposition']['name']] = $value; + + if ($method !="POST" && $method != "GET") { + $GLOBALS["_".$method][$headers['content-disposition']['name']] = $value; + } + } + } + return $dataset; + } + + + /** + * Parses body param headers + * @param $headerContent + * + * @return array + */ + private static function parseHeaders(string $headerContent) : array + { + $headers = []; + $headerParts = preg_split('/\\R/s', $headerContent, -1, PREG_SPLIT_NO_EMPTY); + foreach ($headerParts as $headerPart) { + if (strpos($headerPart, ':') === false) { + continue; + } + list($headerName, $headerValue) = explode(':', $headerPart, 2); + $headerName = strtolower(trim($headerName)); + $headerValue = trim($headerValue); + if (strpos($headerValue, ';') === false) { + $headers[$headerName] = $headerValue; + } else { + $headers[$headerName] = []; + foreach (explode(';', $headerValue) as $part) { + $part = trim($part); + if (strpos($part, '=') === false) { + $headers[$headerName][] = $part; + } else { + list($name, $value) = explode('=', $part, 2); + $name = strtolower(trim($name)); + $value = trim(trim($value), '"'); + $headers[$headerName][$name] = $value; + } + } + } + } + return $headers; + } + + /** + * Converts bytes to kb mb etc.. + * + * @param int $bytes + * + * @return string + */ + private static function toFormattedBytes(int $bytes) : string + { + $precision = 2; + $base = log($bytes, 1024); + $suffixes = array('', 'K', 'M', 'G', 'T'); + + return round(pow(1024, $base - floor($base)), $precision) . $suffixes[floor($base)]; + } + + + /** + * Formatted bytes to bytes + * @param string $formattedBytes + * + * @return int|null + */ + private static function toBytes(string $formattedBytes): ?int { + $units = ['B', 'K', 'M', 'G', 'T', 'P']; + $unitsExtended = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; + + $number = (int)preg_replace("/[^0-9]+/", "", $formattedBytes); + $suffix = preg_replace("/[^a-zA-Z]+/", "", $formattedBytes); + + //B or no suffix + if(is_numeric(substr($suffix, 0, 1))) { + return preg_replace('/[^\d]/', '', $formattedBytes); + } + + $exponent = array_flip($units)[$suffix] ?? null; + if ($exponent === null) { + $exponent = array_flip($unitsExtended)[$suffix] ?? null; + } + + if($exponent === null) { + return null; + } + return $number * (1024 ** $exponent); + } +} diff --git a/src/Http/Request/MultipartFormDataset.php b/src/Http/Request/MultipartFormDataset.php new file mode 100644 index 0000000..a229e8e --- /dev/null +++ b/src/Http/Request/MultipartFormDataset.php @@ -0,0 +1,34 @@ +params = $multipartRequest->params; + $dataset->files = $multipartRequest->files; + } + return $dataset; + } + + // Handle other requests + if ($method == "POST") { + $dataset->files = $_FILES; + $dataset->params = $_POST; + return $dataset; + } + + if ($method == "GET") { + return $dataset; + } + + $GLOBALS["_".$method] = []; + + //get form params + parse_str(file_get_contents("php://input"), $params); + $GLOBALS["_".$method] = $params; + $dataset->params = $params; + + return $dataset; + } +} diff --git a/src/Http/Response.php b/src/Http/Response.php new file mode 100644 index 0000000..2e2ca93 --- /dev/null +++ b/src/Http/Response.php @@ -0,0 +1,219 @@ +') !== false) { + return false; + } + + libxml_use_internal_errors(true); + simplexml_load_string($content); + $errors = libxml_get_errors(); + libxml_clear_errors(); + + return empty($errors); + } + + /* + * Type of object + */ + protected function type($data) { + // XML + if($this->isValidXml($data)) + { + return 'xml'; + } + + // Json + if(is_string($data) && is_array(json_decode($data, true))) + { + return 'json'; + } + + // Array + if(is_array($data)){ + return 'array'; + } + + // String + if(is_string($data)){ + return 'string'; + } + } + + /** + * JSON Responses + * + */ + public function json($data, $response_code='200') { + // Header + header("Content-Type: application/json"); + + // Response code + http_response_code($response_code); + + // Conversion + switch ($this->type($data)) { + case "xml": + $data = json_encode(simplexml_load_string($data)); + break; + case "array": + $data = json_encode($data); + break; + case "string": + $data = json_encode($data); + break; + } + + // Return + exit($data); + } + + /** + * XML Responses + * + */ + public function xml($data, $response_code='200') { + // Header + header("Content-type: text/xml; charset=utf-8"); + + // Response code + http_response_code($response_code); + + // Conversion + switch ($this->type($data)) { + case "array": + $xml = new \SimpleXMLElement(''); + array_walk_recursive($data, array ($xml, 'addChild')); + $data = $xml->asXML(); + break; + case "json": + $array = json_decode($data, true); + $xml = new \SimpleXMLElement(''); + array_walk_recursive($array, array ($xml, 'addChild')); + $data = $xml->asXML(); + break; + } + + // Return + exit($data); + } + + /** + * Text Responses + * + */ + public function text($data, $response_code='200') { + // Header + header("Content-Type: text/plain; charset=utf-8"); + + // Response code + http_response_code($response_code); + + // Conversion + switch ($this->type($data)) { + case "array": + $data = implode(", ", $data); + break; + } + + // Return + exit($data); + } + + /** + * Add header + * + */ + public function header($key, $value) { + // Header + header("$key: $value"); + + return clone($this); + } + + /** + * Add headers by array + * + */ + public function headers(array $headers) { + // Add headers + foreach ($headers as $key => $value) { + header("$key: $value"); + } + + return clone($this); + } + + /** + * Add HTTP response code + * + */ + public function code($response_code) { + http_response_code($response_code); + + return clone($this); + } + + /** + * Stream file + * + */ + public function file($path) { + // open the file in a binary mode + $name = $this->filesystemRoot() . $path; + + // If file not exists + if(!file_exists($name)){ + return false; + } + + $fp = fopen($name, 'rb'); + + // send the right headers + header("Content-Type: " . mime_content_type($name)); + header("Content-Length: " . filesize($name)); + + // dump the picture and stop the script + fpassthru($fp); + exit; + } +} \ No newline at end of file diff --git a/src/Http/Url.php b/src/Http/Url.php new file mode 100644 index 0000000..c78705a --- /dev/null +++ b/src/Http/Url.php @@ -0,0 +1,183 @@ +public_path; + } + + /** + * Set public folder path + * + */ + public function setPublicPath($path) { + return $this->public_path = $path; + } + + /** + * Get root of server + * + * @param string $path + */ + public function serverRoot($path='') { + return $_SERVER['DOCUMENT_ROOT'].'/'.$path; + } + + /** + * Get root + * + * @param string $path + * @return string $path + */ + public function root($path='') { + if (PHP_SAPI != 'cli'){ + $script_filename = str_replace('\\', '/', $_SERVER['SCRIPT_FILENAME']); + $script_filename = explode("/", $script_filename); + $script_filename = $script_filename[count($script_filename)-1]; + + $script_folder = trim(preg_replace('/' . $script_filename . '/', '/', $_SERVER['SCRIPT_FILENAME'], 1), '/'); + + $path = $script_folder . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + }else{ + $path = getcwd() . DIRECTORY_SEPARATOR . trim($path, '/'); + $path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path); + return $path; + } + } + + /** + * Get root public + * + * @param string $path + */ + public function publicRoot($path='') { + $public_path = $this->getPublicPath(); + + return $this->root($public_path.'/'.$path); + } + + /** + * Get url of server + * + * @param string $path + * @return string $path + */ + public function serverLink($path='') { + $protocol = ($_SERVER['REQUEST_SCHEME'] ?? 'http') . '://'; + $host = $_SERVER['HTTP_HOST'] ?? null; + + $url = trim($protocol . $host . '/' . $path, '/'); + + return $url; + } + + /** + * Get url + * + * @param string $path + * @return string $path + */ + public function link($path='') { + $protocol = ($_SERVER['REQUEST_SCHEME'] ?? 'http') . '://'; + $host = $_SERVER['HTTP_HOST'] ?? null; + + $script_name = str_replace('\\', '', dirname($_SERVER['SCRIPT_NAME'])); + $script_name = $script_name . '/' . trim($path, '/'); + + return trim($protocol . $host . $script_name, '/'); + } + + /** + * Get public + * + * @param string $path + * @return string $path + */ + public function public($path='') { + $public_path = $this->getPublicPath(); + + return $this->link($public_path.'/'.$path); + } + + /** + * redirect to url + * + * @return bool + */ + public function go($url) { + header('location: ' . $url); + exit(); + } + + /** + * URL back + * + * @return bool + */ + public function back() { + return $_SERVER['HTTP_REFERER'] ?? null; + } + + /** + * Redirect to back + * + */ + public function goBack() { + $url = $_SERVER['HTTP_REFERER'] ?? null; + header('location: ' . $url); + exit(); + } + + /** + * URL encode + * + */ + public function encode($path) { + return urlencode($path); + } + + /** + * URL decode + * + */ + public function decode($path) { + return urldecode($path); + } + +} \ No newline at end of file diff --git a/src/Img.php b/src/Img.php new file mode 100644 index 0000000..650d991 --- /dev/null +++ b/src/Img.php @@ -0,0 +1,41 @@ +local; + } + + /** + * Set local(language) + * + */ + public function setLocal($lang) { + return $this->local = $lang; + } + + /** + * Is local(language) + * + */ + public function isLocal($lang) { + if($this->local != $lang){ + return false; + } + + return true; + } + + /** + * Load + * + * Load content from string + */ + protected function load($content, $lang=null) { + // Local + $local = (is_null($lang)) ? $this->local : $lang ; + + // file with prefix + $path = $this->filesystemPath() . DIRECTORY_SEPARATOR . $local . DIRECTORY_SEPARATOR . $content . ".php"; + + if (is_file($path)) { + // load + $data = new Loader(include($path)); + } else { + throw new \Exception("File not found! ('$path') "); + } + + return $this->prepare_load_content($path, $data); + } + + /** + * Prepare Load content + * + */ + protected function prepare_load_content($path, $data) { + // Check in cache + $cache_path = array_column($this->cache, 'path'); + if(in_array($path, $cache_path)){ + $index = array_search($path, $cache_path); + $data = $this->cache[$index]['data']; + + return $data; + } + + // Cache + $cache = [[ + "path" => $path, + "data" => $data + ]]; + + $this->cache = array_merge($this->cache, $cache); + + // Return + return $data; + } + + /** + * Load from file + * + */ + public function file(string $path) { + // extension + if(empty(pathinfo($path, PATHINFO_EXTENSION))){ + $ext = '.php'; + }else{ + $ext = ''; + }; + + $path = $this->filesystem_root() . $path . $ext; + + if(!is_file($path)){ + throw new \Exception("File not found! ('$path') "); + } + + $data = new Loader(include($path)); + + return $this->prepare_load_content($path, $data); + } + + /** + * Load from file + * + */ + public function content(array $array) { + if(is_array($array)){ + $data = new Loader($array); + $path = null; + }else{ + throw new \Exception("Not array!"); + } + + return $this->prepare_load_content($path, $data); + } + + /** + * Get + * + */ + public function get($content, $key, $default=null, $lang=null) { + return ($this->load($content, $lang))->get($key, array(),$default); + } + + /** + * Set + * + */ + public function set($content, $key, $value, $lang=null) { + return ($this->load($content, $lang))->set($key, $value); + } + + /** + * Has + * + */ + public function has($content, $key, $lang=null) { + return ($this->load($content, $lang))->has($key); + } + + /** + * Detect + * + * get detect browser language + */ + public function detect() { + return substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) ?? null; + } + + /** + * Suggestion + * + * get suggestion browser language + */ + public function suggestion() { + return substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 15, 2) ?? null; + } + + /** + * Delete + * + */ + public function delete($content, $key) { + return ($this->load($content))->delete($key); + } + + /** + * Destroy + * + */ + public function destroy($content) { + return ($this->load($content))->destroy(); + } + + /** + * Key + * + */ + public function key($content, $key, $newKey) { + return ($this->load($content))->name($key, $newKey); + } + + /** + * Return array for all data + * + */ + public function toArray($content, $lang=null) { + return ($this->load($content, $lang))->toArray(); + } + + /** + * Return object for all data + * + */ + public function toObject($content, $lang=null) { + return ($this->load($content, $lang))->toObject(); + } + + /** + * Return object for all data + * + */ + public function all($content, $lang=null) { + return ($this->load($content, $lang))->all(); + } + +} \ No newline at end of file diff --git a/src/Lang/Trans/Loader.php b/src/Lang/Trans/Loader.php new file mode 100644 index 0000000..75d1508 --- /dev/null +++ b/src/Lang/Trans/Loader.php @@ -0,0 +1,246 @@ +data = $data; + } + + /* + * Get + * + */ + public function get($key, $vars=[], $default=null) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!isset($array[$k])) { + if(! is_callable($default)){ + return $default; + }else{ + return call_user_func($default); + } + } + + if (sizeof($keys) === 0) { + return $this->prepareValueWithVars($array[$k], $vars); + } + + $array = &$array[$k]; + } + } + + return true; + } + + /* + * Prepare value with vars + * + */ + protected function prepareValueWithVars($value, $vars) + { + $t = $value; + + // Escape + preg_match_all('~\\\{{(.*?)\}}~si', $t, $escape); + $escape = $escape[1]; + + // Matches + preg_match_all('~\{{(.*?)\}}~si', $t, $matches); + $matches = array_diff($matches[1], $escape); + + if ( isset($matches)) { + foreach ( $matches as $var => $value ) { + $t = str_replace('{{' . $value . '}}', $vars[$value], $t); + } + } + + // Escape "\{{" to "{{" + $t = str_replace('\{{', "{{", $t); + + return $t; + } + + /* + * Set + * + */ + public function set($key, $value) + { + $array = $this->data; + + if (is_string($key) && !empty($key)) { + + $keys = explode('.', $key); + $arrTmp = &$array; + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!is_array($arrTmp)) { + $arrTmp = []; + } + + if (!isset($arrTmp[$k])) { + $arrTmp[$k] = []; + } + + if (sizeof($keys) === 0) { + $arrTmp[$k] = $value; + $this->data = $arrTmp; + } + + $arrTmp = &$arrTmp[$k]; + } + } + + return true; + } + + /* + * Has + */ + public function has($key) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + $tempKey = $array; + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if(!isset($tempKey[$k])){ + return false; + }else{ + $tempKey = $tempKey[$k]; + } + } + + return true; + } + } + + /* + * Delete + * + */ + public function delete($key) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + + while (sizeof($keys) >= 1) { + $k = array_shift($keys); + + if (!isset($array[$k])) { + return false; + } + + if (sizeof($keys) === 0) { + unset($array[$k]); + $this->data = $array; + return true; + } + } + } + } + + /* + * Destroy + * + */ + public function destroy() + { + return $this->data = array(); + } + + /* + * Name + * + */ + public function name($key, $newKey) + { + $array = $this->data; + + if (is_string($key) && is_array($array)) { + $keys = explode('.', $key); + $keyString = '$array'; + $keyLastString = ''; + + foreach($keys as $index => $key){ + if($index != array_key_last($array)){ + $keyLastString = "['$key']"; + }else{ + $keyString .= "['$key']"; + } + } + + eval("$keyString"."['$newKey']=$keyString$keyLastString;" . "unset($keyString$keyLastString);"); + + return $this->data = $array; + } + } + + /** + * Return array for all data + * + */ + public function toArray() { + return $this->data; + } + + /** + * Return object for all data + * + */ + public function toObject() { + return json_decode(json_encode($this->toArray()), false); + } + + /** + * Return object for all data + * + */ + public function all() { + return $this->toObject(); + } + +} \ No newline at end of file diff --git a/src/Mail.php b/src/Mail.php new file mode 100644 index 0000000..0e486ad --- /dev/null +++ b/src/Mail.php @@ -0,0 +1,41 @@ +truecolorTransparent($canvas); + + // Resized + imagecopyresized($canvas, $this->image, 0, 0, 0, 0, $width,$height, imagesx($this->image), imagesy($this->image)); + + // Return + $this->image = $canvas; + + return clone($this); + } + + /* + * Get width + */ + public function width() { + return imagesx($this->image); + } + + /* + * Get Height + */ + public function height() { + return imagesy($this->image); + } + + /* + * Rotate + */ + public function rotate($angle) { + $transparent = imagecolorallocatealpha($this->image, 255, 255, 255, 127); + + $this->image = imagerotate($this->image, $angle, $transparent); + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Media/Img/ColorTrait.php b/src/Media/Img/ColorTrait.php new file mode 100644 index 0000000..8081afe --- /dev/null +++ b/src/Media/Img/ColorTrait.php @@ -0,0 +1,60 @@ +color = (empty($a)) ? imagecolorallocate($this->image, $r, $g, $b) : imagecolorallocatealpha($this->image, $r, $g, $b, $a); + + return clone($this); + } + + /* + * Fill + */ + public function fill($x, $y) { + imagefill($this->image, $x, $y, $this->color); + + return clone($this); + } + + /* + * Picker + */ + public function picker($x, $y) { + $picker = imagecolorat($this->image, $x, $y); + + $red = ($picker >> 16) & 0xff; + $green = ($picker >> 8) & 0xff; + $blue = $picker & 0xff; + + return ["red" => $red, "green" => $green, "blue" => $blue]; + } + +} \ No newline at end of file diff --git a/src/Media/Img/DrawTrait.php b/src/Media/Img/DrawTrait.php new file mode 100644 index 0000000..b06ef41 --- /dev/null +++ b/src/Media/Img/DrawTrait.php @@ -0,0 +1,87 @@ +image, $x1, $y1, $x2, $y2, $this->color); + + return clone($this); + } + + /* + * Thickness + */ + public function thickness($value) { + // Get + if (empty($value)) {return $this->thickness;} + + // Set + imagesetthickness($this->image, $value); + + return clone($this); + } + + /* + * Rectangle + */ + public function rectangle($x1, $y1, $x2, $y2) { + imagerectangle($this->image, $x1, $y1, $x2, $y2, $this->color); + + return clone($this); + } + + /* + * Filled rectangle + */ + public function filledRectangle($x1, $y1, $x2, $y2) { + imagefilledrectangle($this->image, $x1, $y1, $x2, $y2, $this->color); + + return clone($this); + } + + /* + * Ellipse + */ + public function ellipse($x1, $y1, $x2, $y2) { + imageellipse($this->image, $x1, $y1, $x2, $y2, $this->color); + + return clone($this); + } + + /* + * Filled ellipse + */ + public function filledEllipse($x1, $y1, $x2, $y2) { + imagefilledellipse($this->image, $x1, $y1, $x2, $y2, $this->color); + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Media/Img/FilterTrait.php b/src/Media/Img/FilterTrait.php new file mode 100644 index 0000000..208e7bc --- /dev/null +++ b/src/Media/Img/FilterTrait.php @@ -0,0 +1,104 @@ +image, IMG_FILTER_EMBOSS); + + return clone($this); + } + + /* + * Negate + */ + public function negate() { + imagefilter($this->image, IMG_FILTER_NEGATE); + + return clone($this); + } + + /* + * Contrast + */ + public function contrast($value) { + imagefilter($this->image, IMG_FILTER_CONTRAST, $value); + + return clone($this); + } + + /* + * Edgedetect + */ + public function edgedetect() { + imagefilter($this->image, IMG_FILTER_EDGEDETECT); + + return clone($this); + } + + /* + * Grayscale + */ + public function grayscale() { + imagefilter($this->image, IMG_FILTER_GRAYSCALE); + + return clone($this); + } + + /* + * Mean removal + */ + public function meanRemoval() { + imagefilter($this->image, IMG_FILTER_MEAN_REMOVAL); + + return clone($this); + } + + /* + * Colorize + */ + public function colorize($r, $g, $b, $a=0) { + imagefilter($this->image, IMG_FILTER_COLORIZE, $r, $g, $b ,$a); + + return clone($this); + } + + /* + * Pixelate + */ + public function pixelate($value) { + imagefilter($this->image, IMG_FILTER_PIXELATE, $value); + + return clone($this); + } + + /* + * Brightness + */ + public function brightness($value) { + imagefilter($this->image, IMG_FILTER_BRIGHTNESS, $value); + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Media/Img/HelperTrait.php b/src/Media/Img/HelperTrait.php new file mode 100644 index 0000000..57435af --- /dev/null +++ b/src/Media/Img/HelperTrait.php @@ -0,0 +1,179 @@ +preparePathFileSystem($path); + + // Save by extension + switch (strtolower($extension)) { + case 'png': + imagepng($this->image, $path, 1); + break; + + case 'jpeg': + $image = imagejpeg($this->image, $path, $this->quality); + break; + + case 'gif': + imagegif($this->image, $path, $this->quality); + break; + + case 'bmp': + imagebmp($this->image, $path, $this->quality); + break; + + case 'webp': + imagewebp($this->image, $path, $this->quality); + break; + + default: + throw new \Exception("Unsupported image type. GD driver is only able to decode JPG, PNG, GIF, BMP or WebP files."); + } + + // Clear data + $this->clearData(); + + return $this->image; + } + + /** + * Clear data + * + * @return string + */ + protected function clearData() + { + // Clear data + $this->image = null; + $this->type = "jpeg"; + + // Clean content + $buffer = ob_get_contents(); + ob_end_clean(); + + return $buffer; + } + + /** + * Initiates new image from path in filesystem + * + */ + protected function initFromPath($path) + { + // get mime type of file + $mime = finfo_file(finfo_open(FILEINFO_MIME_TYPE), $path); + + // define core + switch (strtolower($mime)) { + case 'image/png': + case 'image/x-png': + $core = @imagecreatefrompng($path); + $this->type = 'png'; + break; + + case 'image/jpg': + case 'image/jpeg': + case 'image/pjpeg': + $core = @imagecreatefromjpeg($path); + if (!$core) { + $core= @imagecreatefromstring(file_get_contents($path)); + } + $this->type = 'jpeg'; + break; + + case 'image/gif': + $core = @imagecreatefromgif($path); + $this->type = 'gif'; + break; + + case 'image/bmp': + $core = @imagecreatefromwbmp($path); + $this->type = 'bmp'; + break; + + case 'image/webp': + case 'image/x-webp': + if (!function_exists('imagecreatefromwebp')) { + throw new \Exception("Unsupported image type. GD/PHP installation does not support WebP format."); + } + $core = @imagecreatefromwebp($path); + $this->type = 'webp'; + break; + + default: + throw new \Exception("Unsupported image type. GD driver is only able to decode JPG, PNG, GIF, BMP or WebP files."); + } + + if (empty($core)) {throw new \Exception("Unable to decode image from file ({$path}).");} + + // build image + $this->image = $core; + $this->mime = mime_content_type($path); + + imagedestroy($core); + imagedestroy($this->image); + + return $core; + } + + /** + * Initiates new image from binary data + * + */ + protected function initFromBinary($binary) + { + $image = @imagecreatefromstring($binary); + + if ($image === false) {throw new \Exception("Unable to init from given binary data.");} + + $this->image = $image; + + imagedestroy($image); + imagedestroy($this->image); + + return $image; + } + + /** + * Truecolor transparent + */ + protected function truecolorTransparent($canvas) + { + $width = imagesx($canvas); + $height = imagesy($canvas); + + imagealphablending($canvas, false); + $transparent = imagecolorallocatealpha($canvas, 255, 255, 255, 127); + imagefilledrectangle($canvas, 0, 0, $width, $height, $transparent); + imagecolortransparent($canvas, $transparent); + imagealphablending($canvas, true); + } + +} \ No newline at end of file diff --git a/src/Media/Img/LoaderTrait.php b/src/Media/Img/LoaderTrait.php new file mode 100644 index 0000000..cbf305b --- /dev/null +++ b/src/Media/Img/LoaderTrait.php @@ -0,0 +1,93 @@ +filesystemRoot() . $path; + + if (is_file($path_file)) { + // File + $image = $this->initFromPath($path_file); + }else{ + // string + $image = $this->initFromBinary($path); + } + + $this->image = $image; + + return clone($this); + } + + /* + * Load + */ + public function image($path='') { + return $this->load($path); + } + + /* + * Create + */ + public function create($width, $height) { + // New canvas + $canvas = imagecreatetruecolor($width, $height); + + // Transparent + $this->truecolorTransparent($canvas); + + $this->image = $canvas; + imagedestroy($canvas); + imagedestroy($this->image); + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Media/Img/SaveTrait.php b/src/Media/Img/SaveTrait.php new file mode 100644 index 0000000..ec4a5ec --- /dev/null +++ b/src/Media/Img/SaveTrait.php @@ -0,0 +1,110 @@ +quality; + } + + /* + * Set quality + */ + public function setQuality($quality) { + $this->quality = $quality; + + return clone($this); + } + + /* + * Quality + * Set quality + */ + public function quality($quality) { + return $this->setQuality($quality); + } + + /** + * Png + * + */ + public function png($file=null) + { + // Save to file(file!=null) or browse image(file=null) + return $this->saveToFile($file, "png"); + } + + /** + * Jpg + * + */ + public function jpg($file=null) + { + // Save to file(file!=null) or browse image(file=null) + return $this->saveToFile($file, "jpeg"); + } + + /** + * Gif + * + */ + public function gif($file=null) + { + // Save to file(file!=null) or browse image(file=null) + return $this->saveToFile($file, "gif"); + } + + /** + * Bmp + * + */ + public function bmp($file=null) + { + // Save to file(file!=null) or browse image(file=null) + return $this->saveToFile($file, "bmp"); + } + + /** + * Webp + * + */ + public function webp($file=null) + { + // Save to file(file!=null) or browse image(file=null) + return $this->saveToFile($file, "webp"); + } + +} \ No newline at end of file diff --git a/src/Media/Img/TextTrait.php b/src/Media/Img/TextTrait.php new file mode 100644 index 0000000..f3dd0a8 --- /dev/null +++ b/src/Media/Img/TextTrait.php @@ -0,0 +1,105 @@ +font = null; + + return clone($this); + } + + /* + * Font + * Set font + */ + public function font($value) { + return $this->font($value); + } + + /* + * Get font + */ + public function getFont() { + return $this->font; + } + + /* + * Set font + */ + public function setFont($value) { + $this->font = $value; + + return clone($this); + } + + /* + * Size + * Set size + */ + public function size($value) { + return $this->size($value); + } + + /* + * Get size + */ + public function getSize() { + return $this->size; + } + + /* + * Set size + */ + public function setSize($value) { + $this->size = $value; + + return clone($this); + } + + /* + * Text + */ + public function text($x, $y, $string, $angle=0) { + if(is_null($this->font)) { + imagestring($this->image, $this->size, $x, $y, $string, $this->color); + }else{ + imagettftext($this->image, $this->size, $angle, $x, $y, $this->color, $this->font, $string); + } + + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Middleware.php b/src/Middleware.php new file mode 100644 index 0000000..6c4e925 --- /dev/null +++ b/src/Middleware.php @@ -0,0 +1,41 @@ + '', "api" => "api"]; + + /** + * Controller namespace + * + */ + protected $controller_namespace; + + /** + * Controller default method + * + */ + protected $controllerDefaultMethod; + + /** + * Middleware namespace + * + */ + protected $middleware_namespace; + + /** + * Middleware method + * + */ + protected $middlewareMethod; + + /** + * Method input + * + */ + protected $methodInput = '_method'; + + /** + * Patterns + * + */ + protected $patterns = []; + + /** + * Current + * + */ + protected $current; + + /** + * Options + * + */ + protected $options = [ + "prefix" => null, + "suffix" => null, + "name" => null, + "controller" => null, + "middleware" => array(), + ]; + + /** + * Method input + * + */ + protected $csrf = ["enable"=>true, "value"=>null, "input"=>"_csrf"]; + + /** + * Route parameter + * + */ + protected $route_parameter = ["{", "}"]; + + /** + * Route optional parameter + * + */ + protected $route_optional_parameter = ["[", "]"]; + + /** + * Fallback + * + */ + protected $fallback = null; + + /** + * Route + * + */ + protected $route = null; + + /** + * Routes in group + * + */ + protected $routes_in_group = false; + + /** + * Routes not in group toggle + * + */ + protected $routes_not_in_group_toggle = true; + + /** + * Construct + * + */ + public function __construct(){} + + /** + * Get prefix gates + * + */ + public function getPrefixGates() { + return $this->prefixGates; + } + + /** + * Set prefix gates + * + */ + public function setPrefixGates($gate, $prefix) { + $this->prefixGates[$gate] = $prefix; + return clone($this); + } + + /** + * Get method input + * + */ + public function getMethodInput() { + return $this->methodInput; + } + + /** + * Set method input + * + */ + public function setMethodInput($value) { + $this->methodInput = $value; + return clone($this); + } + + /** + * Get controller namespace + * + */ + public function getControllerNamespace() { + return $this->controller_namespace; + } + + /** + * Set controller namespace + * + */ + public function setControllerNamespace($value) { + $this->controller_namespace = $value; + return clone($this); + } + + /** + * Get controller default method + * + */ + public function getControllerDefaultMethod() { + return $this->controllerDefaultMethod; + } + + /** + * Set controller default method + * + */ + public function setControllerDefaultMethod($value) { + $this->controllerDefaultMethod = $value; + return clone($this); + } + + /** + * Get middleware namespace + * + */ + public function getMiddlewareNamespace() { + return $this->middleware_namespace; + } + + /** + * Set middleware namespace + * + */ + public function setMiddlewareNamespace($value) { + $this->middleware_namespace = $value; + return clone($this); + } + + /** + * Get middleware method + * + */ + public function getMiddlewareMethod() { + return $this->middlewareMethod; + } + + /** + * Set middleware method + * + */ + public function setMiddlewareMethod($value) { + $this->middlewareMethod = $value; + return clone($this); + } + + /** + * Set options defaults + * + */ + protected function setOptionsDefaults(){ + $this->options = [ + "prefix" => null, + "suffix" => null, + "name" => null, + "controller" => null, + "middleware" => array(), + ]; + } + + /** + * Add route + * + * @param String methods + * @param String $uri + * @param Callable|String $callback + */ + protected function add(String $method, String $uri, $callback, Array $options = []) { + // URI + $uri = trim($this->options['prefix'] . $uri . '/' . $this->options['suffix'], '/'); + + $uri = $this->prefixGates[$this->gate].'/'.$uri; + $uri = ($uri != '/') ? '/'.$uri : $uri; + $uri = str_replace("//", "/", $uri); + $uri = ($uri == '/') ? $uri : rtrim($uri, '/'); + + // Status + $status_route = true; + $status_route_index = array_search($uri, array_column($this->routes, 'uri')); + + if(is_numeric($status_route_index)){ + $status_route_selected = $this->routes[$status_route_index]; + + $status_route = ( + $status_route_selected['gate'] == $this->gate + && $status_route_selected['method'] == $method + ) ? false : true ; + } + + // Add + if($status_route === true) { + // Route + $route = [ + "gate" => $this->gate, + "method" => $method, + "uri" => $uri, + "callback" => $callback, + "options" => [ + "name" => $this->options['name'], + "controller" => $this->options['controller'], + "middleware" => $this->options['middleware'], + ], + ]; + + // Set options defaults + $this->setOptionsDefaults(); + + // Add to routes + $this->routes[] = $route; + } + + return clone($this); + } + + /** + * Run + * + */ + public function run() + { + // Script file name + $script_filename = explode("/", $_SERVER['SCRIPT_FILENAME']); + $script_filename = $script_filename[count($script_filename)-1]; + + // Script folder name + $script_foldername = substr($_SERVER['SCRIPT_NAME'], 0, strpos($_SERVER['SCRIPT_NAME'], basename($_SERVER['SCRIPT_NAME']))); + + // Script folder + $script_folder = explode("/", trim($script_foldername, "/")); + $script_folder = $script_folder[count($script_folder)-1]; + + // Routes + $routes = $this->routes; + + // QUERY_STRING + if(!isset($_SERVER['QUERY_STRING'])){ $_SERVER['QUERY_STRING']=''; }; + + // Request + $request = str_replace('%20', ' ', $_SERVER['REQUEST_URI']); + $request = rtrim($request, '?'.$_SERVER['QUERY_STRING']); + $request = '/' . $script_folder . '/' . substr($request, strlen($script_foldername)) . '/'; + + $request = ltrim($request, "/"); + $request_url = ltrim($request, "/"); + + $request = ltrim($request, $script_folder); + $request = (empty($request)) ? $request_url : $request ; + + $request = '/' . ltrim($request, '/'); + // $request = ($request == '/') ? '/' : urldecode($request) . '/'; + $request = rtrim($request, '/'); + $request = (empty($request)) ? '/' : $request . '/'; + + // Method + $method = strtolower($_SERVER["REQUEST_METHOD"]); + + // Custom method + if($method == 'post'){ + if (isset($_POST[$this->methodInput])) { + $method = strtolower($_POST[$this->methodInput]); + } + } + + // Get route & check method + $list = array_values(array_column($routes, 'method')); + $keys = array_keys(array_filter($list, function($value) use ($method) {return $value == $method;})); + $routes = array_intersect_key($routes, array_flip($keys)); + + + // Prepare uri of routes && check + foreach ($routes as $key => $value) { + $routes[$key]['uri'] = ($routes[$key]['uri'] == '/') ? '/' : $routes[$key]['uri'] . '/'; + $myRoute = $routes[$key]; + $routes[$key]['uri'] = '#^' . preg_replace("/\/" . $this->route_parameter[0] . "(.*?)" . $this->route_parameter[1] . "/", '\/([^\/]*)', $routes[$key]['uri']) . '$#'; + + if (preg_match($routes[$key]['uri'], $request, $params)) { + // Route + $route = $myRoute; + + // Parameters + array_shift($params); + + // Patterns + if (!$this->executePatterns($route, $params)) { continue; } + + // Middleware + $this->executeMiddleware($route); + + // Cross-site request forgery + $this->executeCsrf($route); + + // Current + $this->current = $route; + + // Controller + return $this->executeController($route, $params); + } + + } + + // Fallback + if(!is_null($this->fallback)){ + return $this->executeFallback($this->fallback); + } + + // 404 + http_response_code(404); + throw new \Exception("Routes not found!"); + } + +} \ No newline at end of file diff --git a/src/Router/Route/CollectTrait.php b/src/Router/Route/CollectTrait.php new file mode 100644 index 0000000..8fc7f1b --- /dev/null +++ b/src/Router/Route/CollectTrait.php @@ -0,0 +1,299 @@ +$methods[$key]]); + } + } + + $methods = $list; + } + + // Add + $name_cache = $options['name'] ?? null; + + foreach($methods as $key => $method){ + // Method + if(empty($key)){ + $key = explode(',', $callback)[1] ?? null; + } + + // Name + if (isset($options['name']) && !empty($options['name'])) { + $options['name'] = preg_replace('/\d/', 'a\\0b', $name_cache . '_' . $method); + } + + // Callback + if (is_string($callback)) { + $callback_func = $callback . "," . $key; + } + + // URI + $method_uri = $uri.$methods_uri[$key]; + + // Submit + $this->addToMethods($method, $method_uri, $callback_func, $options); + } + + return clone($this); + } + + /** + * Add to collection (Any) routes + * + */ + protected function addToAnyCollect($uri, $callback, $options, $methods) { + // Except + if (isset($options['except'])) { + $except = $options['except']; + + if (is_string($except)) { + $except = explode(",", $options['except']); + $except = array_map('trim', $except); + } + + $methods = array_flip($methods); + + foreach($except as $key){ + if (isset($methods[$key])) { + unset($methods[$key]); + } + } + + $methods = array_flip($methods); + } + + // only + if (isset($options['only'])) { + $only = $options['only']; + + if (is_string($only)) { + $only = explode(",", $options['only']); + $only = array_map('trim', $only); + } + + $list = array(); + foreach($only as $key){ + if (in_array($key, $methods)) { + array_push($list, $key); + } + } + + $methods = $list; + } + + + // Add + $name_cache = $options['name'] ?? null; + foreach($methods as $method){ + // Name + if (isset($options['name']) && !empty($options['name'])) { + $options['name'] = preg_replace('/\d/', 'a\\0b', $name_cache . '_' . $method); + + } + // Submit + $this->addToMethods($method, $uri, $callback, $options); + } + + return clone($this); + } + + /** + * Add any collection routes + * + */ + public function any($uri, $callback, $options = []) { + // Methods + $methods = array("get","post","put","delete","patch","copy","options","lock","unlock","propfind"); + + // Add to collection + $this->addToAnyCollect($uri, $callback, $options, $methods); + } + + /** + * Methods collection routes + * + */ + public function methods($methods, $uri, $callback, $options = []) { + // Methods + $methods = array_unique($methods); + + // Add to collection + $this->addToAnyCollect($uri, $callback, $options, $methods); + } + + /** + * Add crud collection routes + * + */ + public function crud($uri, $callback, $options = []) { + // List + $list = array( + [ + "controller" => "index", + "method" => "get", + "uri" => "", + ], + + [ + "controller" => "create", + "method" => "get", + "uri" => "/create", + ], + + [ + "controller" => "store", + "method" => "post", + "uri" => "", + ], + + [ + "controller" => "show", + "method" => "get", + "uri" => "/show//".$this->route_parameter[0]."id".$this->route_parameter[1], + ], + + [ + "controller" => "edit", + "method" => "get", + "uri" => "/edit", + "uri" => "/show//".$this->route_parameter[0]."id".$this->route_parameter[1]."/edit", + ], + + [ + "controller" => "update", + "method" => "put", + "uri" => '/'.$this->route_parameter[0]."id".$this->route_parameter[1], + ], + + [ + "controller" => "delete", + "method" => "delete", + "uri" => '/'.$this->route_parameter[0]."id".$this->route_parameter[1], + ], + ); + + // Add to collection + $this->addToCollect($uri, $callback, $options, $list); + } + + /** + * Add api crud collection routes + * + */ + public function apiCrud($uri, $callback, $options = []) { + // List + $list = array( + [ + "controller" => "index", + "method" => "get", + "uri" => "", + ], + + [ + "controller" => "store", + "method" => "post", + "uri" => "", + ], + + [ + "controller" => "show", + "method" => "get", + "uri" => "/show//".$this->route_parameter[0]."id".$this->route_parameter[1], + ], + + [ + "controller" => "update", + "method" => "put", + "uri" => '/'.$this->route_parameter[0]."id".$this->route_parameter[1], + ], + + [ + "controller" => "delete", + "method" => "delete", + "uri" => '/'.$this->route_parameter[0]."id".$this->route_parameter[1], + ], + ); + + // Add to collection + $this->addToCollect($uri, $callback, $options, $list); + } + + /** + * Add cruds collection routes + * + */ + public function cruds(array $uri) { + foreach($uri as $url){ + $this->crud($url[0], $url[1], []); + } + } + + /** + * Add api cruds collection routes + * + */ + public function apiCruds(array $uri) { + foreach($uri as $url){ + $this->apiCrud($url[0], $url[1], []); + } + } + +} + diff --git a/src/Router/Route/ControllerTrait.php b/src/Router/Route/ControllerTrait.php new file mode 100644 index 0000000..acd4665 --- /dev/null +++ b/src/Router/Route/ControllerTrait.php @@ -0,0 +1,133 @@ +controller_namespace . '\\' . $class); + } + + // Method + if(isset($callback[1])){ + $method = $callback[1]; + $method__invoke = false; + }else{ + $method = $this->getControllerDefaultMethod(); + $method__invoke = true; + } + + // Call callback + if (class_exists($class)) { + $object = new $class; + if (method_exists($object, $method)) { + if($method__invoke && method_exists($object, '__invoke')) { + $result = call_user_func_array([$object, '__invoke'], $params); + if(is_string($result)){ echo $result; } + return $result; + }else{ + $result = call_user_func_array([$object, $method], $params); + if(is_string($result)){ echo $result; } + return $result; + } + } else { + throw new \Exception("The method " . $method . " is not exists at " . $class); + } + } else { + throw new \Exception("Class " . $class . " is not found"); + } + } + + /** + * Execute fallback + * + */ + public function executeFallback($callback) { + // Call function + if (is_callable($callback)) { + return call_user_func($callback); + } + + // Callback + if(is_array($callback)){ + $callback = $callback; + }else{ + $callback = explode(',', $callback); + $callback = array_map('trim', $callback); + } + + // Class + $class = $callback[0]; + $class = str_replace(['.', '/', '//', '\\\\'],"\\" ,$class); + if (!class_exists($class)) { + $class = trim($this->controller_namespace . '\\' . $class); + } + + // Method + if(isset($callback[1])){ + $method = $callback[1]; + $method__invoke = false; + }else{ + $method = $this->getControllerDefaultMethod(); + $method__invoke = true; + } + + // Call callback + if (class_exists($class)) { + $object = new $class; + if (method_exists($object, $method)) { + if($method__invoke && method_exists($object, '__invoke')) { + return call_user_func([$object, '__invoke']); + }else{ + return call_user_func([$object, $method]); + } + } else { + throw new \Exception("The method " . $method . " is not exists at " . $class); + } + } else { + throw new \Exception("Class " . $class . " is not found"); + } + } + +} \ No newline at end of file diff --git a/src/Router/Route/CsrfTrait.php b/src/Router/Route/CsrfTrait.php new file mode 100644 index 0000000..179fd1f --- /dev/null +++ b/src/Router/Route/CsrfTrait.php @@ -0,0 +1,62 @@ +csrf; + } + + /** + * Set cross-site request forgery + * + */ + public function setCsrf(array $value) { + return $this->csrf = $value; + } + + /** + * Execute cross-site request forgery + * + * + */ + protected function executeCsrf($route) { + if($route['gate']=='web' && $route['method']!='get' && $this->csrf['enable']){ + + if(isset($_POST[$this->csrf['input']])){ + if($_POST[$this->csrf['input']] != $this->csrf['value']){ + // 403 + http_response_code(403); + throw new \Exception("CSRF not vaild."); + } + }else{ + // 403 + http_response_code(403); + throw new \Exception("CSRF not vaild."); + } + + } + } + +} \ No newline at end of file diff --git a/src/Router/Route/GatesTrait.php b/src/Router/Route/GatesTrait.php new file mode 100644 index 0000000..2430c44 --- /dev/null +++ b/src/Router/Route/GatesTrait.php @@ -0,0 +1,61 @@ +gate = strtolower($gate); + + // Callable + if (is_callable($callback)) { + call_user_func($callback); + } else { + throw new \Exception("Please provide valid callback function"); + } + + $this->gate = 'web'; + } + + /** + * Set web group for routing + * + * @param callback $callback + */ + public function web(Callable $callback) { + $this->addToGates('web', $callback); + } + + /** + * Set api group for routing + * + * @param callback $callback + */ + public function api(Callable $callback) { + $this->addToGates('api', $callback); + } + +} \ No newline at end of file diff --git a/src/Router/Route/MethodsTrait.php b/src/Router/Route/MethodsTrait.php new file mode 100644 index 0000000..a6317ae --- /dev/null +++ b/src/Router/Route/MethodsTrait.php @@ -0,0 +1,224 @@ +options; + + $options = [ + "prefix" => $options['prefix'] ?? '', + "suffix" => $options['suffix'] ?? '', + "name" => $options['name'] ?? '', + "controller" => $options['controller'] ?? '', + "middleware" => $options['middleware'] ?? array(), + ]; + + // Options + if(!empty($options)){ + $this->setOptions($options); + } + + // Optional parameters + $startTag = $this->route_optional_parameter[0]; + $endTag = $this->route_optional_parameter[1]; + $pattern = "#\s*\\$startTag.+\\$endTag\s*#U"; + $uri_pattern = explode(":", $uri)[0]; + + if(preg_match($pattern, $uri_pattern)){ + return $this->addToMethodsOptionalParameters($name, $uri, $callback, $options); + } + + // Add + $this->add($name, $uri, $callback, $options); + + // Options defaults + $this->setOptions($optionsCache); + + // Set route as group + if($this->routes_in_group && !$this->routes_not_in_group_toggle){ + $this->routes_in_group = false; + $this->routes_not_in_group_toggle = true; + } + if(!$this->routes_in_group){ + $this->route = array_key_last($this->routes); + }else{ + $this->route[] = array_key_last($this->routes); + } + } + + protected function addToMethodsOptionalParameters($name, $uri, $callback, $options) { + // Without + $startTag = $this->route_optional_parameter[0]; + $endTag = $this->route_optional_parameter[1]; + $pattern = "#\s*\\$startTag.+\\$endTag\s*#U"; + + $url = preg_replace($pattern, '', $uri); + $url = preg_replace('~/+~', '/', $url); + $this->addToMethods($name, $url, $callback, $options); + + // With + $url = str_replace(['[', ']'], ['{', '}'], $uri); + $this->addToMethods($name, $url, $callback, $options); + } + + /** + * Add new get method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function get($uri, $callback, $options = []) { + $this->addToMethods('get', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new post method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function post($uri, $callback, $options = []) { + $this->addToMethods('post', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new delete method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function delete($uri, $callback, $options = []) { + $this->addToMethods('delete', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new put method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function put($uri, $callback, $options = []) { + $this->addToMethods('put', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new patch method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function patch($uri, $callback, $options = []) { + $this->addToMethods('patch', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new copy method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function copy($uri, $callback, $options = []) { + $this->addToMethods('copy', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new options method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function options($uri, $callback, $options = []) { + $this->addToMethods('options', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new lock method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function lock($uri, $callback, $options = []) { + $this->addToMethods('lock', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new unlock method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function unlock($uri, $callback, $options = []) { + $this->addToMethods('unlock', $uri, $callback, $options); + + return clone($this); + } + + /** + * Add new propfind method + * + * @param string $uri + * @param callback $callback + * @param array $options + */ + public function propfind($uri, $callback, $options = []) { + $this->addToMethods('propfind', $uri, $callback, $options); + + return clone($this); + } + +} + + + diff --git a/src/Router/Route/MiddlewareTrait.php b/src/Router/Route/MiddlewareTrait.php new file mode 100644 index 0000000..7c76c1e --- /dev/null +++ b/src/Router/Route/MiddlewareTrait.php @@ -0,0 +1,74 @@ + $middleware) { + // Call function + if (is_callable($middleware)) { + call_user_func($middleware); + continue; + } + + // Class + $class = trim(explode(',', $middleware)[0]) ?? null; + $class = trim(explode(':', $class)[0]) ?? null; + + // Method + $method = explode(':', $middleware)[1] ?? $this->getMiddlewareMethod(); + $method = trim(explode(',', $method)[0]) ?? $this->getMiddlewareMethod(); + + // Parameters + $params = explode(',', $middleware) ?? null; + array_shift($params); + $params = array_map('trim', $params) ?? null; + $params = array_filter($params); + + // Middleware namespace + if (!class_exists($class)) { + $class = trim($this->getMiddlewareNamespace() . '\\' . $class); + } + + + // Call callback + if (class_exists($class)) { + $object = new $class; + if (method_exists($object, $method)) { + call_user_func_array([$object, $method], $params); + } else { + throw new \Exception("The method " . $method . " is not exists at " . $class); + } + } else { + throw new \Exception("Class " . $class . " is not found"); + } + + } + } + +} \ No newline at end of file diff --git a/src/Router/Route/OptionsTrait.php b/src/Router/Route/OptionsTrait.php new file mode 100644 index 0000000..f8d7ea2 --- /dev/null +++ b/src/Router/Route/OptionsTrait.php @@ -0,0 +1,182 @@ +options = [ + "prefix" => isset($options['prefix']) ? $this->options['prefix'] . trim($options['prefix']) : null, + "suffix" => isset($options['suffix']) ? $this->options['suffix'] . trim($options['suffix']) : null, + "name" => isset($options['name']) ? $this->options['name'] . trim($options['name']) : null, + "controller" => isset($options['controller']) ? $this->options['controller'] . trim($options['controller']) : null, + "middleware" => isset($options['middleware']) ? ((is_array($options['middleware'])) ? array_merge($this->options['middleware'] ,$options['middleware']) : array_merge($this->options['middleware'] ,array($options['middleware']))) : array(), + ]; + }else{ + $this->options = [ + "prefix" => ($key=='prefix') ? $this->options['prefix'] . trim($value) : null, + "suffix" => ($key=='suffix') ? $this->options['suffix'] . trim($value) : null, + "name" => ($key=='name') ? $this->options['name'] . trim($value) : null, + "controller" => ($key=='controller') ? $this->options['controller'] . trim($value) : null, + "middleware" => ($key=='middleware') ? array_merge($this->options['middleware'] ,$value) : array(), + ]; + } + } + + /** + * Set options defaults + * + */ + protected function setOptionsDefaults(){ + $this->options = [ + "prefix" => null, + "suffix" => null, + "name" => null, + "controller" => null, + "middleware" => array(), + ]; + } + + /** + * Chaining methods prepare + * + */ + public function chainingMethodsPrepare($method, ...$arg) { + // Not routes + if(is_null($this->route)){ return false; } + + if(!$this->routes_in_group){ + // Route not in group + $this->{$method}($this->route, ...$arg); + }else{ + // Route in group + foreach($this->route as $route){ + $this->{$method}($route, ...$arg); + } + } + + // Return + return clone($this); + } + + /** + * Prefix + * + */ + public function prefix($value) { + return $this->chainingMethodsPrepare("prefixPrepare", $value); + } + + protected function prefixPrepare($route, $value) { + // Gate + $gate = trim($this->prefixGates[$this->routes[$route]['gate']], '/'); + $gate = empty($gate) ? $gate : "$gate/"; + + // Value + $value = trim($value, '/'); + + // URI + $uri = trim($this->routes[$route]['uri'], '/'); + $uri = "$gate$value/" . ltrim($uri, "$gate/"); + + $this->routes[$route]['uri'] = rtrim('/' . $uri, '/'); + } + + /** + * Suffix + * + */ + public function suffix($value) { + return $this->chainingMethodsPrepare("suffixPrepare", $value); + } + + protected function suffixPrepare($route, $value) { + $this->routes[$route]['uri'] .= '/' . trim('/' . trim($value, '/') , '/'); + } + + /** + * name + * + */ + public function name($value) { + return $this->chainingMethodsPrepare("namePrepare", $value); + } + + protected function namePrepare($route, $value) { + $this->routes[$route]['options']['name'] .= trim($value); + } + + /** + * Middleware + * + */ + public function middleware($value) { + return $this->chainingMethodsPrepare("middlewarePrepare", $value); + } + + protected function middlewarePrepare($route, $value) { + $this->routes[$route]['options']['middleware'][] = $value; + } + + /** + * Controller + * + */ + public function controller($value) { + return $this->chainingMethodsPrepare("controllerPrepare", $value); + } + + protected function controllerPrepare($route, $value) { + $this->routes[$route]['options']['controller'] .= $value; + } + + /** + * Group + * + */ + public function group(callable $callback, array $options=[]) { + // Set route as group + $this->route = []; + $this->routes_in_group = true; + + // Options + $this->setOptions($options); + + if (is_callable($callback)) { + call_user_func($callback); + } else { + throw new \BadFunctionCallException("Valid callback function!"); + } + + // Options defaults + $this->setOptionsDefaults(); + + // Set route as group + $this->routes_not_in_group_toggle = false; + + // Return + return clone($this); + } + +} \ No newline at end of file diff --git a/src/Router/Route/PatternsTrait.php b/src/Router/Route/PatternsTrait.php new file mode 100644 index 0000000..870ba10 --- /dev/null +++ b/src/Router/Route/PatternsTrait.php @@ -0,0 +1,71 @@ + $value){ + $params = explode(":", $value)[0]; + $param_pattern = (isset(explode(":", $value)[1])) ? explode(":", $value)[1] : null; + + if(is_null($param_pattern)){ + $param_pattern = (array_key_exists($params, $this->patterns)) ? $this->patterns[$params] : $param_pattern; + } + + switch ($param_pattern) { + case 'alpha': + $param_pattern = "[A-Za-z]+"; + break; + case 'alphaNumeric': + $param_pattern = "[A-Za-z0-9_]+"; + break; + case 'numeric': + $param_pattern = "[0-9]+"; + break; + } + + // Pattern + $pattern = (isset($param_pattern)) ? '/'.$param_pattern.'/' : null; + + // False + if(!is_null($pattern) && !preg_match($pattern, $values[$key])){ return false; } + } + + // True + return true; + } + + /** + * Pattern + * + */ + public function pattern($Parameter, $pattern) { + $this->patterns[$Parameter] = $pattern; + } + +} \ No newline at end of file diff --git a/src/Router/Route/ToolsTrait.php b/src/Router/Route/ToolsTrait.php new file mode 100644 index 0000000..b3b6e2a --- /dev/null +++ b/src/Router/Route/ToolsTrait.php @@ -0,0 +1,174 @@ +routes, 'uri'); + + // New + $new = trim($new); + $new = '/'.trim($new, '/'); + + // Old + $old = trim($old); + $old = '/'.trim($old, '/'); + + // Match + if (in_array($old, $list)) + { + // Route + $route = $this->routes[array_search($old, $list)]; + + // New route + $new_route = $route; + $new_route['uri'] = $new; + + // Add to routes + array_push($this->routes, $new_route); + } + else + { + throw new \Exception("Route not found!"); + } + + return clone($this); + } + + /** + * Get list of routes + * + */ + public function list() { + $routes = array(); + + foreach($this->routes as $route){ + $routes[] = [ + "gate" => $route["gate"], + "method" => $route["method"], + "name" => $route["options"]['name'], + "uri" => $route["uri"], + "url" => $this->link($route["uri"]) + ]; + } + + return json_decode(json_encode($routes)); + } + + /** + * Get url route by name + * + */ + public function url($name, $parameters=[]) { + // List + $list = array_filter(array_column(array_column($this->routes, 'options'), 'name')); + + // Get key + $key = array_search($name, $list); + + // False + if(!isset($this->routes[$key])){ + return false; + } + + // WWW + $path = trim($this->routes[$key]['uri'],'/'); + + // Parameters + $keys = array_keys($parameters); + $keysWithParameters = array_map(function($x){ return '{' . $x . '}'; }, $keys); + $keysWithOptionalParameters = array_map(function($x){ return '[' . $x . ']'; }, $keys); + $keys = array_merge($keysWithParameters, $keysWithOptionalParameters); + + $values = array_values($parameters); + $values = array_merge($values, $values); + + // Path + $path = str_replace($keys, $values, $path); + + // Protocol + $protocol = ($_SERVER['REQUEST_SCHEME'] ?? 'http') . '://'; + + // Host + $host = $_SERVER['HTTP_HOST'] ?? null; + + // Script name + $script_name = str_replace('\\', '', dirname($_SERVER['SCRIPT_NAME'])); + $script_name = str_replace('/public', '', $script_name).'/'.$path; + + // URL + $url = $protocol . $host . $script_name; + + return $url; + } + + /** + * go + * + */ + public function go($name, $parameters = []) { + header('location: ' . $this->url($name, $parameters)); + exit(); + } + + /** + * Get url + * + * @param string $path + * @return string $path + */ + protected function link($path='') { + $protocol = ($_SERVER['REQUEST_SCHEME'] ?? 'http') . '://'; + $host = $_SERVER['HTTP_HOST'] ?? null; + + $script_name = str_replace('\\', '', dirname($_SERVER['SCRIPT_NAME'])); + $script_name = $script_name . '/' . trim($path, '/'); + + return trim($protocol . $host . $script_name, '/'); + } + + /** + * Current + * + */ + public function current() { + return (object) [ + "gate" => $this->current["gate"], + "method" => $this->current["method"], + "name" => $this->current["options"]['name'], + "uri" => $this->current["uri"], + "url" => $this->link($this->current["uri"]) + ]; + } + + /** + * Fallback + * + */ + public function fallback($callback) { + $this->fallback = $callback; + } + +} \ No newline at end of file diff --git a/src/Schema.php b/src/Schema.php new file mode 100644 index 0000000..2d456e8 --- /dev/null +++ b/src/Schema.php @@ -0,0 +1,41 @@ +decrypt($value); + + if($value==$hash){ + return true; + }else{ + return false; + } + } + +} \ No newline at end of file diff --git a/src/Security/Csrf.php b/src/Security/Csrf.php new file mode 100644 index 0000000..60ddb20 --- /dev/null +++ b/src/Security/Csrf.php @@ -0,0 +1,128 @@ +key = $key; + $this->input = $input; + } + + /* + * Get input + * + */ + public function getInput() { + return $this->input; + } + + /* + * Set input + * + */ + public function setInput($input) { + return $this->input = $input; + } + + /* + * Get key + * + */ + public function getKey() { + return $this->key; + } + + /* + * Set key + * + */ + public function setKey($key) { + return $this->key = $key; + } + + /** + * Run + * + **/ + public function run() { + if (!isset($_SESSION[$this->key])) { + return $_SESSION[$this->key] = bin2hex(random_bytes(24)); + } + } + + /** + * Create + * + **/ + public function create() { + return $_SESSION[$this->key] = bin2hex(random_bytes(24)); + } + + /** + * Get + * + **/ + public function get() { + return $_SESSION[$this->key] ?? ''; + } + + /** + * Delete + * + **/ + public function delete() { + unset($_SESSION[$this->key]); + } + + /** + * Check + * + **/ + public function check() { + if (isset($_POST[$this->input]) && hash_equals($_SESSION[$this->key], $_POST[$this->input])) { + return true; + } else { + return false; + } + } + +} \ No newline at end of file diff --git a/src/Security/Obfuscate.php b/src/Security/Obfuscate.php new file mode 100644 index 0000000..20cf5bc --- /dev/null +++ b/src/Security/Obfuscate.php @@ -0,0 +1,79 @@ +/'), array('',''), $code); + $name = trim($name); + + $code = base64_encode($code); + $codePreOutput = <<<'DATA1' + $___________='[NAME] Class...'; + [BREAK] + $___ = 'X19sYW1iZGE=' ; + $______= 'cmV0dXJuIGV2YWwoJF9fXyk7' ; + + + $____ = 'base64_decode'; $___________='[DATA]'; + + $______=$____($______); $___=$____($___); $_____=$___('$___',$______); + [BREAK] + $_____($____($___________)); + DATA1; + + $codeOutput = <<<'DATA2' + $_='[NAME]'; + [BREAK] + $_____=' b2JfZW5kX2NsZWFu'; $______________='cmV0dXJuIGV2YWwoJF8pOw=='; + $__________________='X19sYW1iZGE='; + + $______=' Z3p1bmNvbXByZXNz'; $___=' b2Jfc3RhcnQ='; $____='b2JfZ2V0X2NvbnRlbnRz'; $__= 'base64_decode' ; $______=$__($______); if(!function_exists('__lambda')){function __lambda($sArgs,$sCode){return eval("return function($sArgs){{$sCode}};");}} $__________________=$__($__________________); $______________=$__($______________); + $__________=$__________________('$_',$______________); $_____=$__($_____); $____=$__($____); $___=$__($___); $_='[PRE_OUTPUT]'; + + $___();$__________($______($__($_))); $________=$____(); + $_____(); echo [BREAK] $________; + + DATA2; + + // Spaces + $spaces = str_repeat("\r\n", 99 + (strlen($name) * 4)); + + // Output + $codePreOutput = str_replace(array('[DATA]', '[NAME]', '[BREAK]'), array($code, $name, $spaces . "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n"), $codePreOutput); + $codeOutput = str_replace(array('[PRE_OUTPUT]', '[NAME]', '[BREAK]'), array(base64_encode(gzcompress($codePreOutput, 9)), $name, $spaces), $codeOutput); + + $codePreOutput = trim(preg_replace('/\s+/', ' ', $codePreOutput)); + + return trim(preg_replace('/\s+/', ' ', $codeOutput)); + } + +} \ No newline at end of file diff --git a/src/Security/Password.php b/src/Security/Password.php new file mode 100644 index 0000000..07e521e --- /dev/null +++ b/src/Security/Password.php @@ -0,0 +1,80 @@ +true, "alphaCaps"=>true, "numeric"=>true, "special"=>true]) + { + // small letters + $_alphaSmall = ("alphaSmall") ? 'abcdefghijklmnopqrstuvwxyz' : ''; + // CAPITAL LETTERS + $_alphaCaps = ("alphaCaps") ? strtoupper($_alphaSmall) : ''; + // numerics + $_numerics = ("numeric") ? '1234567890' : ''; + // Special Characters + $_specialChars = ("special") ? '`~!@#$%^&*()-_=+]}[{;:,<.>/?\'"\|' : ''; + + // Contains all characters + $_container = $_alphaSmall.$_alphaCaps.$_numerics.$_specialChars; + + // will contain the desired pass + $password = ''; + + // Loop till the length mentioned + for($i = 0; $i < $length; $i++) { + // Get Randomized Length + $_rand = rand(0, strlen($_container) - 1); + // returns part of the string [high tensile strength] + $password .= substr($_container, $_rand, 1); + } + + // Returns the generated Pass + return $password; + } + +} \ No newline at end of file diff --git a/src/Security/Validator.php b/src/Security/Validator.php new file mode 100644 index 0000000..743e263 --- /dev/null +++ b/src/Security/Validator.php @@ -0,0 +1,313 @@ +registerValidators(); + } + + /** + * Register or override existing validator + * + * @param mixed $key + * @param \use Kiaan\Security\Rule $rule + * @return void + */ + public function setValidator(string $key, $rule) + { + $this->validators[$key] = $rule; + $rule->setKey($key); + } + + /** + * Get validator object from given $key + * + * @param mixed $key + * @return mixed + */ + public function getValidator($key) + { + return isset($this->validators[$key]) ? $this->validators[$key] : null; + } + + /** + * Set namespace + * + */ + public function setNamespace($namespace) + { + return $this->namespace = $namespace; + } + + /** + * Get namespace + * + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * Set path + * + */ + public function setPath($path) + { + return $this->path = $path; + } + + /** + * Get path + * + */ + public function getPath() + { + return $this->path; + } + + /** + * Validate $inputs + * + * @param array $inputs + * @param array $rules + * @param array $messages + * @return Validation + */ + public function validate(array $inputs, array $rules, array $messages = []): Validation + { + $validation = $this->make($inputs, $rules, $messages); + $validation->validate(); + return $validation; + } + + /** + * Given $inputs, $rules and $messages to make the Validation class instance + * + * @param array $inputs + * @param array $rules + * @param array $messages + * @return Validation + */ + public function make(array $inputs, array $rules, array $messages = []): Validation + { + $messages = array_merge($this->messages, $messages); + $validation = new Validation($this, $inputs, $rules, $messages); + + return $validation; + } + + /** + * Magic invoke method to make Rule instance + * + * @param string $rule + * @return Rule + */ + public function __invoke(string $rule): Validator\Rule + { + $args = func_get_args(); + $rule = array_shift($args); + $params = $args; + $validator = $this->getValidator($rule); + if (!$validator) { + throw new \Exception("Validator '{$rule}' is not registered", 1); + } + + $clonedValidator = clone $validator; + $clonedValidator->fillParameters($params); + + return $clonedValidator; + } + + /** + * Initialize validators array + * + * @return void + */ + protected function registerValidators() + { + $baseValidator = [ + 'required' => new Validator\Rules\Required, + 'required_if' => new Validator\Rules\RequiredIf, + 'required_unless' => new Validator\Rules\RequiredUnless, + 'required_with' => new Validator\Rules\RequiredWith, + 'required_without' => new Validator\Rules\RequiredWithout, + 'required_with_all' => new Validator\Rules\RequiredWithAll, + 'required_without_all' => new Validator\Rules\RequiredWithoutAll, + 'email' => new Validator\Rules\Email, + 'alpha' => new Validator\Rules\Alpha, + 'numeric' => new Validator\Rules\Numeric, + 'alpha_num' => new Validator\Rules\AlphaNum, + 'alpha_dash' => new Validator\Rules\AlphaDash, + 'alpha_spaces' => new Validator\Rules\AlphaSpaces, + 'in' => new Validator\Rules\In, + 'not_in' => new Validator\Rules\NotIn, + 'min' => new Validator\Rules\Min, + 'max' => new Validator\Rules\Max, + 'between' => new Validator\Rules\Between, + 'url' => new Validator\Rules\Url, + 'integer' => new Validator\Rules\Integer, + 'boolean' => new Validator\Rules\Boolean, + 'ip' => new Validator\Rules\Ip, + 'ipv4' => new Validator\Rules\Ipv4, + 'ipv6' => new Validator\Rules\Ipv6, + 'extension' => new Validator\Rules\Extension, + 'array' => new Validator\Rules\TypeArray, + 'same' => new Validator\Rules\Same, + 'regex' => new Validator\Rules\Regex, + 'date' => new Validator\Rules\Date, + 'accepted' => new Validator\Rules\Accepted, + 'present' => new Validator\Rules\Present, + 'different' => new Validator\Rules\Different, + 'uploaded_file' => new Validator\Rules\UploadedFile, + 'mimes' => new Validator\Rules\Mimes, + 'callback' => new Validator\Rules\Callback, + 'before' => new Validator\Rules\Before, + 'after' => new Validator\Rules\After, + 'lowercase' => new Validator\Rules\Lowercase, + 'uppercase' => new Validator\Rules\Uppercase, + 'json' => new Validator\Rules\Json, + 'digits' => new Validator\Rules\Digits, + 'digits_between' => new Validator\Rules\DigitsBetween, + 'defaults' => new Validator\Rules\Defaults, + 'default' => new Validator\Rules\Defaults, // alias of defaults + 'nullable' => new Validator\Rules\Nullable, + 'string' => new Validator\Rules\Str, + 'sometimes' => new Validator\Rules\Sometimes, + 'unique' => new Validator\Rules\Unique($this->getPdo()), + 'exists' => new Validator\Rules\Exists($this->getPdo()) + ]; + + foreach ($baseValidator as $key => $validator) { + $this->setValidator($key, $validator); + } + } + + /** + * Add custome rules + * + * @return void + */ + public function rules(array $rules) + { + foreach ($rules as $key => $validator) { + if(!is_object($validator)){ + if(!class_exists($validator)){ + $validator = $this->getNamespace()."\\$validator"; + } + + $validator = new $validator; + } + + $this->setValidator($key, $validator); + } + } + + /** + * Given $ruleName and $rule to add new validator + * + * @param string $ruleName + * @param \use Kiaan\Security\Rule $rule + * @return void + */ + public function addValidator(string $ruleName, $rule) + { + if (!$this->allowRuleOverride && array_key_exists($ruleName, $this->validators)) { + throw new \Exception("You cannot override a built in rule. You have to rename your rule"); + } + + $this->setValidator($ruleName, $rule); + } + + /** + * Set rule can allow to be overrided + * + * @param boolean $status + * @return void + */ + public function allowRuleOverride(bool $status = false) + { + $this->allowRuleOverride = $status; + } + + /** + * Set this can use humanize keys + * + * @param boolean $useHumanizedKeys + * @return void + */ + public function setUseHumanizedKeys(bool $useHumanizedKeys = true) + { + $this->useHumanizedKeys = $useHumanizedKeys; + } + + /** + * Get $this->useHumanizedKeys value + * + * @return void + */ + public function isUsingHumanizedKey(): bool + { + return $this->useHumanizedKeys; + } +} diff --git a/src/Security/Validator/Attribute.php b/src/Security/Validator/Attribute.php new file mode 100644 index 0000000..bb68772 --- /dev/null +++ b/src/Security/Validator/Attribute.php @@ -0,0 +1,325 @@ +validation = $validation; + $this->alias = $alias; + $this->key = $key; + foreach ($rules as $rule) { + $this->addRule($rule); + } + } + + /** + * Set the primary attribute + * + * @param \use Kiaan\Security\Attribute $primaryAttribute + * @return void + */ + public function setPrimaryAttribute(Attribute $primaryAttribute) + { + $this->primaryAttribute = $primaryAttribute; + } + + /** + * Set key indexes + * + * @param array $keyIndexes + * @return void + */ + public function setKeyIndexes(array $keyIndexes) + { + $this->keyIndexes = $keyIndexes; + } + + /** + * Get primary attributes + * + * @return \use Kiaan\Security\Attribute|null + */ + public function getPrimaryAttribute() + { + return $this->primaryAttribute; + } + + /** + * Set other attributes + * + * @param array $otherAttributes + * @return void + */ + public function setOtherAttributes(array $otherAttributes) + { + $this->otherAttributes = []; + foreach ($otherAttributes as $otherAttribute) { + $this->addOtherAttribute($otherAttribute); + } + } + + /** + * Add other attributes + * + * @param \use Kiaan\Security\Attribute $otherAttribute + * @return void + */ + public function addOtherAttribute(Attribute $otherAttribute) + { + $this->otherAttributes[] = $otherAttribute; + } + + /** + * Get other attributes + * + * @return array + */ + public function getOtherAttributes(): array + { + return $this->otherAttributes; + } + + /** + * Add rule + * + * @param \use Kiaan\Security\Rule $rule + * @return void + */ + public function addRule(Rule $rule) + { + $rule->setAttribute($this); + $rule->setValidation($this->validation); + $this->rules[$rule->getKey()] = $rule; + } + + /** + * Get rule + * + * @param string $ruleKey + * @return void + */ + public function getRule(string $ruleKey) + { + return $this->hasRule($ruleKey)? $this->rules[$ruleKey] : null; + } + + /** + * Get rules + * + * @return array + */ + public function getRules(): array + { + return $this->rules; + } + + /** + * Check the $ruleKey has in the rule + * + * @param string $ruleKey + * @return bool + */ + public function hasRule(string $ruleKey): bool + { + return isset($this->rules[$ruleKey]); + } + + /** + * Set required + * + * @param boolean $required + * @return void + */ + public function setRequired(bool $required) + { + $this->required = $required; + } + + /** + * Set rule is required + * + * @return boolean + */ + public function isRequired(): bool + { + return $this->required; + } + + /** + * Get key + * + * @return string + */ + public function getKey(): string + { + return $this->key; + } + + /** + * Get key indexes + * + * @return array + */ + public function getKeyIndexes(): array + { + return $this->keyIndexes; + } + + /** + * Get value + * + * @param string|null $key + * @return mixed + */ + public function getValue(string $key = null) + { + if ($key && $this->isArrayAttribute()) { + $key = $this->resolveSiblingKey($key); + } + + if (!$key) { + $key = $this->getKey(); + } + + return $this->validation->getValue($key); + } + + /** + * Get that is array attribute + * + * @return boolean + */ + public function isArrayAttribute(): bool + { + return count($this->getKeyIndexes()) > 0; + } + + /** + * Check this attribute is using dot notation + * + * @return boolean + */ + public function isUsingDotNotation(): bool + { + return strpos($this->getKey(), '.') !== false; + } + + /** + * Resolve sibling key + * + * @param string $key + * @return string + */ + public function resolveSiblingKey(string $key): string + { + $indexes = $this->getKeyIndexes(); + $keys = explode("*", $key); + $countAsterisks = count($keys) - 1; + if (count($indexes) < $countAsterisks) { + $indexes = array_merge($indexes, array_fill(0, $countAsterisks - count($indexes), "*")); + } + $args = array_merge([str_replace("*", "%s", $key)], $indexes); + return call_user_func_array('sprintf', $args); + } + + /** + * Get humanize key + * + * @return string + */ + public function getHumanizedKey() + { + $primaryAttribute = $this->getPrimaryAttribute(); + $key = str_replace('_', ' ', $this->key); + + // Resolve key from array validation + if ($primaryAttribute) { + $split = explode('.', $key); + $key = implode(' ', array_map(function ($word) { + if (is_numeric($word)) { + $word = $word + 1; + } + return Helper::snakeCase($word, ' '); + }, $split)); + } + + return ucfirst($key); + } + + /** + * Set alias + * + * @param string $alias + * @return void + */ + public function setAlias(string $alias) + { + $this->alias = $alias; + } + + /** + * Get alias + * + * @return string|null + */ + public function getAlias() + { + return $this->alias; + } +} diff --git a/src/Security/Validator/Build/ValidatorBuild.php b/src/Security/Validator/Build/ValidatorBuild.php new file mode 100644 index 0000000..423a1e9 --- /dev/null +++ b/src/Security/Validator/Build/ValidatorBuild.php @@ -0,0 +1,23 @@ +messages = $messages; + } + + /** + * Add message for given key and rule + * + * @param string $key + * @param string $rule + * @param string $message + * @return void + */ + public function add(string $key, string $rule, string $message) + { + if (!isset($this->messages[$key])) { + $this->messages[$key] = []; + } + + $this->messages[$key][$rule] = $message; + } + + /** + * Get messages count + * + * @return int + */ + public function count(): int + { + return count($this->all()); + } + + /** + * Check given key is existed + * + * @param string $key + * @return bool + */ + public function has(string $key): bool + { + list($key, $ruleName) = $this->parsekey($key); + if ($this->isWildcardKey($key)) { + $messages = $this->filterMessagesForWildcardKey($key, $ruleName); + return count(Helper::arrayDot($messages)) > 0; + } else { + $messages = isset($this->messages[$key])? $this->messages[$key] : null; + + if (!$ruleName) { + return !empty($messages); + } else { + return !empty($messages) and isset($messages[$ruleName]); + } + } + } + + /** + * Get the first value of array + * + * @param string $key + * @return mixed + */ + public function first(string $key) + { + list($key, $ruleName) = $this->parsekey($key); + if ($this->isWildcardKey($key)) { + $messages = $this->filterMessagesForWildcardKey($key, $ruleName); + $flattenMessages = Helper::arrayDot($messages); + return array_shift($flattenMessages); + } else { + $keyMessages = isset($this->messages[$key])? $this->messages[$key] : []; + + if (empty($keyMessages)) { + return null; + } + + if ($ruleName) { + return isset($keyMessages[$ruleName])? $keyMessages[$ruleName] : null; + } else { + return array_shift($keyMessages); + } + } + } + + /** + * Get messages from given key, can be use custom format + * + * @param string $key + * @param string $format + * @return array + */ + public function get(string $key, string $format = ':message'): array + { + list($key, $ruleName) = $this->parsekey($key); + $results = []; + if ($this->isWildcardKey($key)) { + $messages = $this->filterMessagesForWildcardKey($key, $ruleName); + foreach ($messages as $explicitKey => $keyMessages) { + foreach ($keyMessages as $rule => $message) { + $results[$explicitKey][$rule] = $this->formatMessage($message, $format); + } + } + } else { + $keyMessages = isset($this->messages[$key])? $this->messages[$key] : []; + foreach ($keyMessages as $rule => $message) { + if ($ruleName and $ruleName != $rule) { + continue; + } + $results[$rule] = $this->formatMessage($message, $format); + } + } + + return $results; + } + + /** + * Get all messages + * + * @param string $format + * @return array + */ + public function all(string $format = ':message'): array + { + $messages = $this->messages; + $results = []; + foreach ($messages as $key => $keyMessages) { + foreach ($keyMessages as $message) { + $results[] = $this->formatMessage($message, $format); + } + } + return $results; + } + + /** + * Get the first message from existing keys + * + * @param string $format + * @param boolean $dotNotation + * @return array + */ + public function firstOfAll(string $format = ':message', bool $dotNotation = false): array + { + $messages = $this->messages; + $results = []; + foreach ($messages as $key => $keyMessages) { + if ($dotNotation) { + $results[$key] = $this->formatMessage(array_shift($messages[$key]), $format); + } else { + Helper::arraySet($results, $key, $this->formatMessage(array_shift($messages[$key]), $format)); + } + } + return $results; + } + + /** + * Get plain array messages + * + * @return array + */ + public function list($key=null): array + { + if(is_null($key)){ + return $this->messages; + } + + return $this->messages[$key]; + } + + /** + * Parse $key to get the array of $key and $ruleName + * + * @param string $key + * @return array + */ + protected function parseKey(string $key): array + { + $expl = explode(':', $key, 2); + $key = $expl[0]; + $ruleName = isset($expl[1])? $expl[1] : null; + return [$key, $ruleName]; + } + + /** + * Check the $key is wildcard + * + * @param mixed $key + * @return bool + */ + protected function isWildcardKey(string $key): bool + { + return false !== strpos($key, '*'); + } + + /** + * Filter messages with wildcard key + * + * @param string $key + * @param mixed $ruleName + * @return array + */ + protected function filterMessagesForWildcardKey(string $key, $ruleName = null): array + { + $messages = $this->messages; + $pattern = preg_quote($key, '#'); + $pattern = str_replace('\*', '.*', $pattern); + + $filteredMessages = []; + + foreach ($messages as $k => $keyMessages) { + if ((bool) preg_match('#^'.$pattern.'\z#u', $k) === false) { + continue; + } + + foreach ($keyMessages as $rule => $message) { + if ($ruleName and $rule != $ruleName) { + continue; + } + $filteredMessages[$k][$rule] = $message; + } + } + + return $filteredMessages; + } + + /** + * Get formatted message + * + * @param string $message + * @param string $format + * @return string + */ + protected function formatMessage(string $message, string $format): string + { + return str_replace(':message', $message, $format); + } +} diff --git a/src/Security/Validator/Helper.php b/src/Security/Validator/Helper.php new file mode 100644 index 0000000..888d883 --- /dev/null +++ b/src/Security/Validator/Helper.php @@ -0,0 +1,268 @@ + $value) { + if (is_array($value) && ! empty($value)) { + $results = array_merge($results, static::arrayDot($value, $prepend.$key.'.')); + } else { + $results[$prepend.$key] = $value; + } + } + + return $results; + } + + /** + * Set an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array|null $key + * @param mixed $value + * @param bool $overwrite + * @return mixed + */ + public static function arraySet(&$target, $key, $value, $overwrite = true): array + { + if (is_null($key)) { + if ($overwrite) { + return $target = array_merge($target, $value); + } + return $target = array_merge($value, $target); + } + + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*') { + if (! is_array($target)) { + $target = []; + } + + if ($segments) { + foreach ($target as &$inner) { + static::arraySet($inner, $segments, $value, $overwrite); + } + } elseif ($overwrite) { + foreach ($target as &$inner) { + $inner = $value; + } + } + } elseif (is_array($target)) { + if ($segments) { + if (! array_key_exists($segment, $target)) { + $target[$segment] = []; + } + + static::arraySet($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite || ! array_key_exists($segment, $target)) { + $target[$segment] = $value; + } + } else { + $target = []; + + if ($segments) { + static::arraySet($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite) { + $target[$segment] = $value; + } + } + + return $target; + } + + /** + * Unset an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array $key + * @return mixed + */ + public static function arrayUnset(&$target, $key) + { + if (!is_array($target)) { + return $target; + } + + $segments = is_array($key) ? $key : explode('.', $key); + $segment = array_shift($segments); + + if ($segment == '*') { + $target = []; + } elseif ($segments) { + if (array_key_exists($segment, $target)) { + static::arrayUnset($target[$segment], $segments); + } + } elseif (array_key_exists($segment, $target)) { + unset($target[$segment]); + } + + return $target; + } + + /** + * Get snake_case format from given string + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snakeCase(string $value, string $delimiter = '_'): string + { + if (! ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', ucwords($value)); + $value = strtolower(preg_replace('/(.)(?=[A-Z])/u', '$1'.$delimiter, $value)); + } + + return $value; + } + + /** + * Join string[] to string with given $separator and $lastSeparator. + * + * @param array $pieces + * @param string $separator + * @param string|null $lastSeparator + * @return string + */ + public static function join(array $pieces, string $separator, string $lastSeparator = null): string + { + if (is_null($lastSeparator)) { + $lastSeparator = $separator; + } + + $last = array_pop($pieces); + + switch (count($pieces)) { + case 0: + return $last ?: ''; + case 1: + return $pieces[0] . $lastSeparator . $last; + default: + return implode($separator, $pieces) . $lastSeparator . $last; + } + } + + /** + * Wrap string[] by given $prefix and $suffix + * + * @param array $strings + * @param string $prefix + * @param string|null $suffix + * @return array + */ + public static function wraps(array $strings, string $prefix, string $suffix = null): array + { + if (is_null($suffix)) { + $suffix = $prefix; + } + + return array_map(function ($str) use ($prefix, $suffix) { + return $prefix . $str . $suffix; + }, $strings); + } +} diff --git a/src/Security/Validator/Rule.php b/src/Security/Validator/Rule.php new file mode 100644 index 0000000..fe9f340 --- /dev/null +++ b/src/Security/Validator/Rule.php @@ -0,0 +1,245 @@ +validation = $validation; + } + + /** + * Set key + * + * @param string $key + * @return void + */ + public function setKey(string $key) + { + $this->key = $key; + } + + /** + * Get key + * + * @return string + */ + public function getKey() + { + return $this->key ?: get_class($this); + } + + /** + * Set attribute + * + * @param \use Kiaan\Security\Attribute $attribute + * @return void + */ + public function setAttribute(Attribute $attribute) + { + $this->attribute = $attribute; + } + + /** + * Get attribute + * + * @return \use Kiaan\Security\Attribute|null + */ + public function getAttribute() + { + return $this->attribute; + } + + /** + * Get parameters + * + * @return array + */ + public function getParameters(): array + { + return $this->params; + } + + /** + * Set params + * + * @param array $params + * @return \use Kiaan\Security\Rule + */ + public function setParameters(array $params): Rule + { + $this->params = array_merge($this->params, $params); + return clone($this); + } + + /** + * Set parameters + * + * @param string $key + * @param mixed $value + * @return \use Kiaan\Security\Rule + */ + public function setParameter(string $key, $value): Rule + { + $this->params[$key] = $value; + return clone($this); + } + + /** + * Fill $params to $this->params + * + * @param array $params + * @return \use Kiaan\Security\Rule + */ + public function fillParameters(array $params): Rule + { + foreach ($this->fillableParams as $key) { + if (empty($params)) { + break; + } + $this->params[$key] = array_shift($params); + } + return clone($this); + } + + /** + * Get parameter from given $key, return null if it not exists + * + * @param string $key + * @return mixed + */ + public function parameter(string $key) + { + return isset($this->params[$key])? $this->params[$key] : null; + } + + /** + * Set parameter text that can be displayed in error message using ':param_key' + * + * @param string $key + * @param string $text + * @return void + */ + public function setParameterText(string $key, string $text) + { + $this->paramsTexts[$key] = $text; + } + + /** + * Get $paramsTexts + * + * @return array + */ + public function getParametersTexts(): array + { + return $this->paramsTexts; + } + + /** + * Check whether this rule is implicit + * + * @return boolean + */ + public function isImplicit(): bool + { + return $this->implicit; + } + + /** + * Just alias of setMessage + * + * @param string $message + * @return \use Kiaan\Security\Validator\Rule + */ + public function message(string $message): Rule + { + return $this->setMessage($message); + } + + /** + * Set message + * + * @param string $message + * @return \use Kiaan\Security\Validator\Rule + */ + public function setMessage(string $message): Rule + { + $this->message = $message; + return clone($this); + } + + /** + * Get message + * + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * Check given $params must be exists + * + * @param array $params + * @return void + */ + protected function requireParameters(array $params) + { + foreach ($params as $param) { + if (!isset($this->params[$param])) { + $rule = $this->getKey(); + throw new \Exception("Missing required parameter '{$param}' on rule '{$rule}'"); + } + } + } +} diff --git a/src/Security/Validator/Rules/Accepted.php b/src/Security/Validator/Rules/Accepted.php new file mode 100644 index 0000000..5663a31 --- /dev/null +++ b/src/Security/Validator/Rules/Accepted.php @@ -0,0 +1,47 @@ +requireParameters($this->fillableParams); + $time = $this->parameter('time'); + + if (!$this->isValidDate($value)) { + throw $this->throwException($value); + } + + if (!$this->isValidDate($time)) { + throw $this->throwException($time); + } + + return $this->getTimeStamp($time) < $this->getTimeStamp($value); + } +} diff --git a/src/Security/Validator/Rules/Alpha.php b/src/Security/Validator/Rules/Alpha.php new file mode 100644 index 0000000..352fbdd --- /dev/null +++ b/src/Security/Validator/Rules/Alpha.php @@ -0,0 +1,44 @@ + 0; + } +} diff --git a/src/Security/Validator/Rules/AlphaNum.php b/src/Security/Validator/Rules/AlphaNum.php new file mode 100644 index 0000000..43b5fde --- /dev/null +++ b/src/Security/Validator/Rules/AlphaNum.php @@ -0,0 +1,48 @@ + 0; + } +} diff --git a/src/Security/Validator/Rules/AlphaSpaces.php b/src/Security/Validator/Rules/AlphaSpaces.php new file mode 100644 index 0000000..be018da --- /dev/null +++ b/src/Security/Validator/Rules/AlphaSpaces.php @@ -0,0 +1,48 @@ + 0; + } +} diff --git a/src/Security/Validator/Rules/Before.php b/src/Security/Validator/Rules/Before.php new file mode 100644 index 0000000..c63637e --- /dev/null +++ b/src/Security/Validator/Rules/Before.php @@ -0,0 +1,60 @@ +requireParameters($this->fillableParams); + $time = $this->parameter('time'); + + if (!$this->isValidDate($value)) { + throw $this->throwException($value); + } + + if (!$this->isValidDate($time)) { + throw $this->throwException($time); + } + + return $this->getTimeStamp($time) > $this->getTimeStamp($value); + } +} diff --git a/src/Security/Validator/Rules/Between.php b/src/Security/Validator/Rules/Between.php new file mode 100644 index 0000000..dc4f8cc --- /dev/null +++ b/src/Security/Validator/Rules/Between.php @@ -0,0 +1,59 @@ +requireParameters($this->fillableParams); + + $min = $this->getBytesSize($this->parameter('min')); + $max = $this->getBytesSize($this->parameter('max')); + + $valueSize = $this->getValueSize($value); + + if (!is_numeric($valueSize)) { + return false; + } + + return ($valueSize >= $min && $valueSize <= $max); + } +} diff --git a/src/Security/Validator/Rules/Boolean.php b/src/Security/Validator/Rules/Boolean.php new file mode 100644 index 0000000..c0620ad --- /dev/null +++ b/src/Security/Validator/Rules/Boolean.php @@ -0,0 +1,44 @@ +setParameter('callback', $callback); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + * @throws \Exception + */ + public function check($value): bool + { + $this->requireParameters($this->fillableParams); + + $callback = $this->parameter('callback'); + if (false === $callback instanceof Closure) { + $key = $this->attribute->getKey(); + throw new InvalidArgumentException("Callback rule for '{$key}' is not callable."); + } + + $callback = $callback->bindTo($this); + $invalidMessage = $callback($value); + + if (is_string($invalidMessage)) { + $this->setMessage($invalidMessage); + return false; + } elseif (false === $invalidMessage) { + return false; + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/Date.php b/src/Security/Validator/Rules/Date.php new file mode 100644 index 0000000..0bdb98e --- /dev/null +++ b/src/Security/Validator/Rules/Date.php @@ -0,0 +1,55 @@ + 'Y-m-d' + ]; + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters($this->fillableParams); + + $format = $this->parameter('format'); + return date_create_from_format($format, $value) !== false; + } +} diff --git a/src/Security/Validator/Rules/Defaults.php b/src/Security/Validator/Rules/Defaults.php new file mode 100644 index 0000000..3db1f25 --- /dev/null +++ b/src/Security/Validator/Rules/Defaults.php @@ -0,0 +1,71 @@ +requireParameters($this->fillableParams); + + $default = $this->parameter('default'); + return true; + } + + /** + * {@inheritDoc} + */ + public function modifyValue($value) + { + return $this->isEmptyValue($value) ? $this->parameter('default') : $value; + } + + /** + * Check $value is empty value + * + * @param mixed $value + * @return boolean + */ + protected function isEmptyValue($value): bool + { + $requiredValidator = new Required; + return false === $requiredValidator->check($value, []); + } +} diff --git a/src/Security/Validator/Rules/Different.php b/src/Security/Validator/Rules/Different.php new file mode 100644 index 0000000..fe54d1e --- /dev/null +++ b/src/Security/Validator/Rules/Different.php @@ -0,0 +1,52 @@ +requireParameters($this->fillableParams); + + $field = $this->parameter('field'); + $anotherValue = $this->validation->getValue($field); + + return $value != $anotherValue; + } +} diff --git a/src/Security/Validator/Rules/Digits.php b/src/Security/Validator/Rules/Digits.php new file mode 100644 index 0000000..0ded436 --- /dev/null +++ b/src/Security/Validator/Rules/Digits.php @@ -0,0 +1,52 @@ +requireParameters($this->fillableParams); + + $length = (int) $this->parameter('length'); + + return ! preg_match('/[^0-9]/', $value) + && strlen((string) $value) == $length; + } +} diff --git a/src/Security/Validator/Rules/DigitsBetween.php b/src/Security/Validator/Rules/DigitsBetween.php new file mode 100644 index 0000000..25028a6 --- /dev/null +++ b/src/Security/Validator/Rules/DigitsBetween.php @@ -0,0 +1,55 @@ +requireParameters($this->fillableParams); + + $min = (int) $this->parameter('min'); + $max = (int) $this->parameter('max'); + + $length = strlen((string) $value); + + return ! preg_match('/[^0-9]/', $value) + && $length >= $min && $length <= $max; + } +} diff --git a/src/Security/Validator/Rules/Email.php b/src/Security/Validator/Rules/Email.php new file mode 100644 index 0000000..985a694 --- /dev/null +++ b/src/Security/Validator/Rules/Email.php @@ -0,0 +1,44 @@ +pdo = $pdo; + } + + public function check($value): bool + { + // make sure required parameters exists + $this->requireParameters(['table', 'column']); + + // getting parameters + $table = $this->parameter('table'); + $column = $this->parameter('column'); + + // do query + $stmt = $this->pdo->prepare("select count(*) as count from {$table} where {$column} = :value"); + $stmt->bindParam(':value', $value); + $stmt->execute(); + $data = $stmt->fetch(\PDO::FETCH_ASSOC); + + // true for valid, false for invalid + return intval($data['count']) > 0; + } +} diff --git a/src/Security/Validator/Rules/Extension.php b/src/Security/Validator/Rules/Extension.php new file mode 100644 index 0000000..f758427 --- /dev/null +++ b/src/Security/Validator/Rules/Extension.php @@ -0,0 +1,50 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + if (count($params) == 1 && is_array($params[0])) { + $params = $params[0]; + } + $this->params['allowed_extensions'] = $params; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['allowed_extensions']); + $allowedExtensions = $this->parameter('allowed_extensions'); + foreach ($allowedExtensions as $key => $ext) { + $allowedExtensions[$key] = ltrim($ext, '.'); + } + + $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; + $allowedExtensionsText = Helper::join(Helper::wraps($allowedExtensions, ".", ""), ', ', ", {$or} "); + $this->setParameterText('allowed_extensions', $allowedExtensionsText); + + $ext = strtolower(pathinfo($value, PATHINFO_EXTENSION)); + return ($ext && in_array($ext, $allowedExtensions)) ? true : false; + } +} diff --git a/src/Security/Validator/Rules/Helpers/MimeTypeGuesser.php b/src/Security/Validator/Rules/Helpers/MimeTypeGuesser.php new file mode 100644 index 0000000..a97aa04 --- /dev/null +++ b/src/Security/Validator/Rules/Helpers/MimeTypeGuesser.php @@ -0,0 +1,820 @@ + 'ez', + 'application/applixware' => 'aw', + 'application/atom+xml' => 'atom', + 'application/atomcat+xml' => 'atomcat', + 'application/atomsvc+xml' => 'atomsvc', + 'application/ccxml+xml' => 'ccxml', + 'application/cdmi-capability' => 'cdmia', + 'application/cdmi-container' => 'cdmic', + 'application/cdmi-domain' => 'cdmid', + 'application/cdmi-object' => 'cdmio', + 'application/cdmi-queue' => 'cdmiq', + 'application/cu-seeme' => 'cu', + 'application/davmount+xml' => 'davmount', + 'application/docbook+xml' => 'dbk', + 'application/dssc+der' => 'dssc', + 'application/dssc+xml' => 'xdssc', + 'application/ecmascript' => 'ecma', + 'application/emma+xml' => 'emma', + 'application/epub+zip' => 'epub', + 'application/exi' => 'exi', + 'application/font-tdpfr' => 'pfr', + 'application/gml+xml' => 'gml', + 'application/gpx+xml' => 'gpx', + 'application/gxf' => 'gxf', + 'application/hyperstudio' => 'stk', + 'application/inkml+xml' => 'ink', + 'application/ipfix' => 'ipfix', + 'application/java-archive' => 'jar', + 'application/java-serialized-object' => 'ser', + 'application/java-vm' => 'class', + 'application/javascript' => 'js', + 'application/json' => 'json', + 'application/jsonml+json' => 'jsonml', + 'application/lost+xml' => 'lostxml', + 'application/mac-binhex40' => 'hqx', + 'application/mac-compactpro' => 'cpt', + 'application/mads+xml' => 'mads', + 'application/marc' => 'mrc', + 'application/marcxml+xml' => 'mrcx', + 'application/mathematica' => 'ma', + 'application/mathml+xml' => 'mathml', + 'application/mbox' => 'mbox', + 'application/mediaservercontrol+xml' => 'mscml', + 'application/metalink+xml' => 'metalink', + 'application/metalink4+xml' => 'meta4', + 'application/mets+xml' => 'mets', + 'application/mods+xml' => 'mods', + 'application/mp21' => 'm21', + 'application/mp4' => 'mp4s', + 'application/msword' => 'doc', + 'application/mxf' => 'mxf', + 'application/octet-stream' => 'bin', + 'application/oda' => 'oda', + 'application/oebps-package+xml' => 'opf', + 'application/ogg' => 'ogx', + 'application/omdoc+xml' => 'omdoc', + 'application/onenote' => 'onetoc', + 'application/oxps' => 'oxps', + 'application/patch-ops-error+xml' => 'xer', + 'application/pdf' => 'pdf', + 'application/pgp-encrypted' => 'pgp', + 'application/pgp-signature' => 'asc', + 'application/pics-rules' => 'prf', + 'application/pkcs10' => 'p10', + 'application/pkcs7-mime' => 'p7m', + 'application/pkcs7-signature' => 'p7s', + 'application/pkcs8' => 'p8', + 'application/pkix-attr-cert' => 'ac', + 'application/pkix-cert' => 'cer', + 'application/pkix-crl' => 'crl', + 'application/pkix-pkipath' => 'pkipath', + 'application/pkixcmp' => 'pki', + 'application/pls+xml' => 'pls', + 'application/postscript' => 'ai', + 'application/prs.cww' => 'cww', + 'application/pskc+xml' => 'pskcxml', + 'application/rdf+xml' => 'rdf', + 'application/reginfo+xml' => 'rif', + 'application/relax-ng-compact-syntax' => 'rnc', + 'application/resource-lists+xml' => 'rl', + 'application/resource-lists-diff+xml' => 'rld', + 'application/rls-services+xml' => 'rs', + 'application/rpki-ghostbusters' => 'gbr', + 'application/rpki-manifest' => 'mft', + 'application/rpki-roa' => 'roa', + 'application/rsd+xml' => 'rsd', + 'application/rss+xml' => 'rss', + 'application/rtf' => 'rtf', + 'application/sbml+xml' => 'sbml', + 'application/scvp-cv-request' => 'scq', + 'application/scvp-cv-response' => 'scs', + 'application/scvp-vp-request' => 'spq', + 'application/scvp-vp-response' => 'spp', + 'application/sdp' => 'sdp', + 'application/set-payment-initiation' => 'setpay', + 'application/set-registration-initiation' => 'setreg', + 'application/shf+xml' => 'shf', + 'application/smil+xml' => 'smi', + 'application/sparql-query' => 'rq', + 'application/sparql-results+xml' => 'srx', + 'application/srgs' => 'gram', + 'application/srgs+xml' => 'grxml', + 'application/sru+xml' => 'sru', + 'application/ssdl+xml' => 'ssdl', + 'application/ssml+xml' => 'ssml', + 'application/tei+xml' => 'tei', + 'application/thraud+xml' => 'tfi', + 'application/timestamped-data' => 'tsd', + 'application/vnd.3gpp.pic-bw-large' => 'plb', + 'application/vnd.3gpp.pic-bw-small' => 'psb', + 'application/vnd.3gpp.pic-bw-var' => 'pvb', + 'application/vnd.3gpp2.tcap' => 'tcap', + 'application/vnd.3m.post-it-notes' => 'pwn', + 'application/vnd.accpac.simply.aso' => 'aso', + 'application/vnd.accpac.simply.imp' => 'imp', + 'application/vnd.acucobol' => 'acu', + 'application/vnd.acucorp' => 'atc', + 'application/vnd.adobe.air-application-installer-package+zip' => 'air', + 'application/vnd.adobe.formscentral.fcdt' => 'fcdt', + 'application/vnd.adobe.fxp' => 'fxp', + 'application/vnd.adobe.xdp+xml' => 'xdp', + 'application/vnd.adobe.xfdf' => 'xfdf', + 'application/vnd.ahead.space' => 'ahead', + 'application/vnd.airzip.filesecure.azf' => 'azf', + 'application/vnd.airzip.filesecure.azs' => 'azs', + 'application/vnd.amazon.ebook' => 'azw', + 'application/vnd.americandynamics.acc' => 'acc', + 'application/vnd.amiga.ami' => 'ami', + 'application/vnd.android.package-archive' => 'apk', + 'application/vnd.anser-web-certificate-issue-initiation' => 'cii', + 'application/vnd.anser-web-funds-transfer-initiation' => 'fti', + 'application/vnd.antix.game-component' => 'atx', + 'application/vnd.apple.installer+xml' => 'mpkg', + 'application/vnd.apple.mpegurl' => 'm3u8', + 'application/vnd.aristanetworks.swi' => 'swi', + 'application/vnd.astraea-software.iota' => 'iota', + 'application/vnd.audiograph' => 'aep', + 'application/vnd.blueice.multipass' => 'mpm', + 'application/vnd.bmi' => 'bmi', + 'application/vnd.businessobjects' => 'rep', + 'application/vnd.chemdraw+xml' => 'cdxml', + 'application/vnd.chipnuts.karaoke-mmd' => 'mmd', + 'application/vnd.cinderella' => 'cdy', + 'application/vnd.claymore' => 'cla', + 'application/vnd.cloanto.rp9' => 'rp9', + 'application/vnd.clonk.c4group' => 'c4g', + 'application/vnd.cluetrust.cartomobile-config' => 'c11amc', + 'application/vnd.cluetrust.cartomobile-config-pkg' => 'c11amz', + 'application/vnd.commonspace' => 'csp', + 'application/vnd.contact.cmsg' => 'cdbcmsg', + 'application/vnd.cosmocaller' => 'cmc', + 'application/vnd.crick.clicker' => 'clkx', + 'application/vnd.crick.clicker.keyboard' => 'clkk', + 'application/vnd.crick.clicker.palette' => 'clkp', + 'application/vnd.crick.clicker.template' => 'clkt', + 'application/vnd.crick.clicker.wordbank' => 'clkw', + 'application/vnd.criticaltools.wbs+xml' => 'wbs', + 'application/vnd.ctc-posml' => 'pml', + 'application/vnd.cups-ppd' => 'ppd', + 'application/vnd.curl.car' => 'car', + 'application/vnd.curl.pcurl' => 'pcurl', + 'application/vnd.dart' => 'dart', + 'application/vnd.data-vision.rdz' => 'rdz', + 'application/vnd.dece.data' => 'uvf', + 'application/vnd.dece.ttml+xml' => 'uvt', + 'application/vnd.dece.unspecified' => 'uvx', + 'application/vnd.dece.zip' => 'uvz', + 'application/vnd.denovo.fcselayout-link' => 'fe_launch', + 'application/vnd.dna' => 'dna', + 'application/vnd.dolby.mlp' => 'mlp', + 'application/vnd.dpgraph' => 'dpg', + 'application/vnd.dreamfactory' => 'dfac', + 'application/vnd.ds-keypoint' => 'kpxx', + 'application/vnd.dvb.ait' => 'ait', + 'application/vnd.dvb.service' => 'svc', + 'application/vnd.dynageo' => 'geo', + 'application/vnd.ecowin.chart' => 'mag', + 'application/vnd.enliven' => 'nml', + 'application/vnd.epson.esf' => 'esf', + 'application/vnd.epson.msf' => 'msf', + 'application/vnd.epson.quickanime' => 'qam', + 'application/vnd.epson.salt' => 'slt', + 'application/vnd.epson.ssf' => 'ssf', + 'application/vnd.eszigno3+xml' => 'es3', + 'application/vnd.ezpix-album' => 'ez2', + 'application/vnd.ezpix-package' => 'ez3', + 'application/vnd.fdf' => 'fdf', + 'application/vnd.fdsn.mseed' => 'mseed', + 'application/vnd.fdsn.seed' => 'seed', + 'application/vnd.flographit' => 'gph', + 'application/vnd.fluxtime.clip' => 'ftc', + 'application/vnd.framemaker' => 'fm', + 'application/vnd.frogans.fnc' => 'fnc', + 'application/vnd.frogans.ltf' => 'ltf', + 'application/vnd.fsc.weblaunch' => 'fsc', + 'application/vnd.fujitsu.oasys' => 'oas', + 'application/vnd.fujitsu.oasys2' => 'oa2', + 'application/vnd.fujitsu.oasys3' => 'oa3', + 'application/vnd.fujitsu.oasysgp' => 'fg5', + 'application/vnd.fujitsu.oasysprs' => 'bh2', + 'application/vnd.fujixerox.ddd' => 'ddd', + 'application/vnd.fujixerox.docuworks' => 'xdw', + 'application/vnd.fujixerox.docuworks.binder' => 'xbd', + 'application/vnd.fuzzysheet' => 'fzs', + 'application/vnd.genomatix.tuxedo' => 'txd', + 'application/vnd.geogebra.file' => 'ggb', + 'application/vnd.geogebra.tool' => 'ggt', + 'application/vnd.geometry-explorer' => 'gex', + 'application/vnd.geonext' => 'gxt', + 'application/vnd.geoplan' => 'g2w', + 'application/vnd.geospace' => 'g3w', + 'application/vnd.gmx' => 'gmx', + 'application/vnd.google-earth.kml+xml' => 'kml', + 'application/vnd.google-earth.kmz' => 'kmz', + 'application/vnd.grafeq' => 'gqf', + 'application/vnd.groove-account' => 'gac', + 'application/vnd.groove-help' => 'ghf', + 'application/vnd.groove-identity-message' => 'gim', + 'application/vnd.groove-injector' => 'grv', + 'application/vnd.groove-tool-message' => 'gtm', + 'application/vnd.groove-tool-template' => 'tpl', + 'application/vnd.groove-vcard' => 'vcg', + 'application/vnd.hal+xml' => 'hal', + 'application/vnd.handheld-entertainment+xml' => 'zmm', + 'application/vnd.hbci' => 'hbci', + 'application/vnd.hhe.lesson-player' => 'les', + 'application/vnd.hp-hpgl' => 'hpgl', + 'application/vnd.hp-hpid' => 'hpid', + 'application/vnd.hp-hps' => 'hps', + 'application/vnd.hp-jlyt' => 'jlt', + 'application/vnd.hp-pcl' => 'pcl', + 'application/vnd.hp-pclxl' => 'pclxl', + 'application/vnd.hydrostatix.sof-data' => 'sfd-hdstx', + 'application/vnd.ibm.minipay' => 'mpy', + 'application/vnd.ibm.modcap' => 'afp', + 'application/vnd.ibm.rights-management' => 'irm', + 'application/vnd.ibm.secure-container' => 'sc', + 'application/vnd.iccprofile' => 'icc', + 'application/vnd.igloader' => 'igl', + 'application/vnd.immervision-ivp' => 'ivp', + 'application/vnd.immervision-ivu' => 'ivu', + 'application/vnd.insors.igm' => 'igm', + 'application/vnd.intercon.formnet' => 'xpw', + 'application/vnd.intergeo' => 'i2g', + 'application/vnd.intu.qbo' => 'qbo', + 'application/vnd.intu.qfx' => 'qfx', + 'application/vnd.ipunplugged.rcprofile' => 'rcprofile', + 'application/vnd.irepository.package+xml' => 'irp', + 'application/vnd.is-xpr' => 'xpr', + 'application/vnd.isac.fcs' => 'fcs', + 'application/vnd.jam' => 'jam', + 'application/vnd.jcp.javame.midlet-rms' => 'rms', + 'application/vnd.jisp' => 'jisp', + 'application/vnd.joost.joda-archive' => 'joda', + 'application/vnd.kahootz' => 'ktz', + 'application/vnd.kde.karbon' => 'karbon', + 'application/vnd.kde.kchart' => 'chrt', + 'application/vnd.kde.kformula' => 'kfo', + 'application/vnd.kde.kivio' => 'flw', + 'application/vnd.kde.kontour' => 'kon', + 'application/vnd.kde.kpresenter' => 'kpr', + 'application/vnd.kde.kspread' => 'ksp', + 'application/vnd.kde.kword' => 'kwd', + 'application/vnd.kenameaapp' => 'htke', + 'application/vnd.kidspiration' => 'kia', + 'application/vnd.kinar' => 'kne', + 'application/vnd.koan' => 'skp', + 'application/vnd.kodak-descriptor' => 'sse', + 'application/vnd.las.las+xml' => 'lasxml', + 'application/vnd.llamagraphics.life-balance.desktop' => 'lbd', + 'application/vnd.llamagraphics.life-balance.exchange+xml' => 'lbe', + 'application/vnd.lotus-1-2-3' => '123', + 'application/vnd.lotus-approach' => 'apr', + 'application/vnd.lotus-freelance' => 'pre', + 'application/vnd.lotus-notes' => 'nsf', + 'application/vnd.lotus-organizer' => 'org', + 'application/vnd.lotus-screencam' => 'scm', + 'application/vnd.lotus-wordpro' => 'lwp', + 'application/vnd.macports.portpkg' => 'portpkg', + 'application/vnd.mcd' => 'mcd', + 'application/vnd.medcalcdata' => 'mc1', + 'application/vnd.mediastation.cdkey' => 'cdkey', + 'application/vnd.mfer' => 'mwf', + 'application/vnd.mfmp' => 'mfm', + 'application/vnd.micrografx.flo' => 'flo', + 'application/vnd.micrografx.igx' => 'igx', + 'application/vnd.mif' => 'mif', + 'application/vnd.mobius.daf' => 'daf', + 'application/vnd.mobius.dis' => 'dis', + 'application/vnd.mobius.mbk' => 'mbk', + 'application/vnd.mobius.mqy' => 'mqy', + 'application/vnd.mobius.msl' => 'msl', + 'application/vnd.mobius.plc' => 'plc', + 'application/vnd.mobius.txf' => 'txf', + 'application/vnd.mophun.application' => 'mpn', + 'application/vnd.mophun.certificate' => 'mpc', + 'application/vnd.mozilla.xul+xml' => 'xul', + 'application/vnd.ms-artgalry' => 'cil', + 'application/vnd.ms-cab-compressed' => 'cab', + 'application/vnd.ms-excel' => 'xls', + 'application/vnd.ms-excel.addin.macroenabled.12' => 'xlam', + 'application/vnd.ms-excel.sheet.binary.macroenabled.12' => 'xlsb', + 'application/vnd.ms-excel.sheet.macroenabled.12' => 'xlsm', + 'application/vnd.ms-excel.template.macroenabled.12' => 'xltm', + 'application/vnd.ms-fontobject' => 'eot', + 'application/vnd.ms-htmlhelp' => 'chm', + 'application/vnd.ms-ims' => 'ims', + 'application/vnd.ms-lrm' => 'lrm', + 'application/vnd.ms-officetheme' => 'thmx', + 'application/vnd.ms-pki.seccat' => 'cat', + 'application/vnd.ms-pki.stl' => 'stl', + 'application/vnd.ms-powerpoint' => 'ppt', + 'application/vnd.ms-powerpoint.addin.macroenabled.12' => 'ppam', + 'application/vnd.ms-powerpoint.presentation.macroenabled.12' => 'pptm', + 'application/vnd.ms-powerpoint.slide.macroenabled.12' => 'sldm', + 'application/vnd.ms-powerpoint.slideshow.macroenabled.12' => 'ppsm', + 'application/vnd.ms-powerpoint.template.macroenabled.12' => 'potm', + 'application/vnd.ms-project' => 'mpp', + 'application/vnd.ms-word.document.macroenabled.12' => 'docm', + 'application/vnd.ms-word.template.macroenabled.12' => 'dotm', + 'application/vnd.ms-works' => 'wps', + 'application/vnd.ms-wpl' => 'wpl', + 'application/vnd.ms-xpsdocument' => 'xps', + 'application/vnd.mseq' => 'mseq', + 'application/vnd.musician' => 'mus', + 'application/vnd.muvee.style' => 'msty', + 'application/vnd.mynfc' => 'taglet', + 'application/vnd.neurolanguage.nlu' => 'nlu', + 'application/vnd.nitf' => 'ntf', + 'application/vnd.noblenet-directory' => 'nnd', + 'application/vnd.noblenet-sealer' => 'nns', + 'application/vnd.noblenet-web' => 'nnw', + 'application/vnd.nokia.n-gage.data' => 'ngdat', + 'application/vnd.nokia.n-gage.symbian.install' => 'n-gage', + 'application/vnd.nokia.radio-preset' => 'rpst', + 'application/vnd.nokia.radio-presets' => 'rpss', + 'application/vnd.novadigm.edm' => 'edm', + 'application/vnd.novadigm.edx' => 'edx', + 'application/vnd.novadigm.ext' => 'ext', + 'application/vnd.oasis.opendocument.chart' => 'odc', + 'application/vnd.oasis.opendocument.chart-template' => 'otc', + 'application/vnd.oasis.opendocument.database' => 'odb', + 'application/vnd.oasis.opendocument.formula' => 'odf', + 'application/vnd.oasis.opendocument.formula-template' => 'odft', + 'application/vnd.oasis.opendocument.graphics' => 'odg', + 'application/vnd.oasis.opendocument.graphics-template' => 'otg', + 'application/vnd.oasis.opendocument.image' => 'odi', + 'application/vnd.oasis.opendocument.image-template' => 'oti', + 'application/vnd.oasis.opendocument.presentation' => 'odp', + 'application/vnd.oasis.opendocument.presentation-template' => 'otp', + 'application/vnd.oasis.opendocument.spreadsheet' => 'ods', + 'application/vnd.oasis.opendocument.spreadsheet-template' => 'ots', + 'application/vnd.oasis.opendocument.text' => 'odt', + 'application/vnd.oasis.opendocument.text-master' => 'odm', + 'application/vnd.oasis.opendocument.text-template' => 'ott', + 'application/vnd.oasis.opendocument.text-web' => 'oth', + 'application/vnd.olpc-sugar' => 'xo', + 'application/vnd.oma.dd2+xml' => 'dd2', + 'application/vnd.openofficeorg.extension' => 'oxt', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.slide' => 'sldx', + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => 'ppsx', + 'application/vnd.openxmlformats-officedocument.presentationml.template' => 'potx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => 'xltx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => 'dotx', + 'application/vnd.osgeo.mapguide.package' => 'mgp', + 'application/vnd.osgi.dp' => 'dp', + 'application/vnd.osgi.subsystem' => 'esa', + 'application/vnd.palm' => 'pdb', + 'application/vnd.pawaafile' => 'paw', + 'application/vnd.pg.format' => 'str', + 'application/vnd.pg.osasli' => 'ei6', + 'application/vnd.picsel' => 'efif', + 'application/vnd.pmi.widget' => 'wg', + 'application/vnd.pocketlearn' => 'plf', + 'application/vnd.powerbuilder6' => 'pbd', + 'application/vnd.previewsystems.box' => 'box', + 'application/vnd.proteus.magazine' => 'mgz', + 'application/vnd.publishare-delta-tree' => 'qps', + 'application/vnd.pvi.ptid1' => 'ptid', + 'application/vnd.quark.quarkxpress' => 'qxd', + 'application/vnd.realvnc.bed' => 'bed', + 'application/vnd.recordare.musicxml' => 'mxl', + 'application/vnd.recordare.musicxml+xml' => 'musicxml', + 'application/vnd.rig.cryptonote' => 'cryptonote', + 'application/vnd.rim.cod' => 'cod', + 'application/vnd.rn-realmedia' => 'rm', + 'application/vnd.rn-realmedia-vbr' => 'rmvb', + 'application/vnd.route66.link66+xml' => 'link66', + 'application/vnd.sailingtracker.track' => 'st', + 'application/vnd.seemail' => 'see', + 'application/vnd.sema' => 'sema', + 'application/vnd.semd' => 'semd', + 'application/vnd.semf' => 'semf', + 'application/vnd.shana.informed.formdata' => 'ifm', + 'application/vnd.shana.informed.formtemplate' => 'itp', + 'application/vnd.shana.informed.interchange' => 'iif', + 'application/vnd.shana.informed.package' => 'ipk', + 'application/vnd.simtech-mindmapper' => 'twd', + 'application/vnd.smaf' => 'mmf', + 'application/vnd.smart.teacher' => 'teacher', + 'application/vnd.solent.sdkm+xml' => 'sdkm', + 'application/vnd.spotfire.dxp' => 'dxp', + 'application/vnd.spotfire.sfs' => 'sfs', + 'application/vnd.stardivision.calc' => 'sdc', + 'application/vnd.stardivision.draw' => 'sda', + 'application/vnd.stardivision.impress' => 'sdd', + 'application/vnd.stardivision.math' => 'smf', + 'application/vnd.stardivision.writer' => 'sdw', + 'application/vnd.stardivision.writer-global' => 'sgl', + 'application/vnd.stepmania.package' => 'smzip', + 'application/vnd.stepmania.stepchart' => 'sm', + 'application/vnd.sun.xml.calc' => 'sxc', + 'application/vnd.sun.xml.calc.template' => 'stc', + 'application/vnd.sun.xml.draw' => 'sxd', + 'application/vnd.sun.xml.draw.template' => 'std', + 'application/vnd.sun.xml.impress' => 'sxi', + 'application/vnd.sun.xml.impress.template' => 'sti', + 'application/vnd.sun.xml.math' => 'sxm', + 'application/vnd.sun.xml.writer' => 'sxw', + 'application/vnd.sun.xml.writer.global' => 'sxg', + 'application/vnd.sun.xml.writer.template' => 'stw', + 'application/vnd.sus-calendar' => 'sus', + 'application/vnd.svd' => 'svd', + 'application/vnd.symbian.install' => 'sis', + 'application/vnd.syncml+xml' => 'xsm', + 'application/vnd.syncml.dm+wbxml' => 'bdm', + 'application/vnd.syncml.dm+xml' => 'xdm', + 'application/vnd.tao.intent-module-archive' => 'tao', + 'application/vnd.tcpdump.pcap' => 'pcap', + 'application/vnd.tmobile-livetv' => 'tmo', + 'application/vnd.trid.tpt' => 'tpt', + 'application/vnd.triscape.mxs' => 'mxs', + 'application/vnd.trueapp' => 'tra', + 'application/vnd.ufdl' => 'ufd', + 'application/vnd.uiq.theme' => 'utz', + 'application/vnd.umajin' => 'umj', + 'application/vnd.unity' => 'unityweb', + 'application/vnd.uoml+xml' => 'uoml', + 'application/vnd.vcx' => 'vcx', + 'application/vnd.visio' => 'vsd', + 'application/vnd.visionary' => 'vis', + 'application/vnd.vsf' => 'vsf', + 'application/vnd.wap.wbxml' => 'wbxml', + 'application/vnd.wap.wmlc' => 'wmlc', + 'application/vnd.wap.wmlscriptc' => 'wmlsc', + 'application/vnd.webturbo' => 'wtb', + 'application/vnd.wolfram.player' => 'nbp', + 'application/vnd.wordperfect' => 'wpd', + 'application/vnd.wqd' => 'wqd', + 'application/vnd.wt.stf' => 'stf', + 'application/vnd.xara' => 'xar', + 'application/vnd.xfdl' => 'xfdl', + 'application/vnd.yamaha.hv-dic' => 'hvd', + 'application/vnd.yamaha.hv-script' => 'hvs', + 'application/vnd.yamaha.hv-voice' => 'hvp', + 'application/vnd.yamaha.openscoreformat' => 'osf', + 'application/vnd.yamaha.openscoreformat.osfpvg+xml' => 'osfpvg', + 'application/vnd.yamaha.smaf-audio' => 'saf', + 'application/vnd.yamaha.smaf-phrase' => 'spf', + 'application/vnd.yellowriver-custom-menu' => 'cmp', + 'application/vnd.zul' => 'zir', + 'application/vnd.zzazz.deck+xml' => 'zaz', + 'application/voicexml+xml' => 'vxml', + 'application/widget' => 'wgt', + 'application/winhlp' => 'hlp', + 'application/wsdl+xml' => 'wsdl', + 'application/wspolicy+xml' => 'wspolicy', + 'application/x-7z-compressed' => '7z', + 'application/x-abiword' => 'abw', + 'application/x-ace-compressed' => 'ace', + 'application/x-apple-diskimage' => 'dmg', + 'application/x-authorware-bin' => 'aab', + 'application/x-authorware-map' => 'aam', + 'application/x-authorware-seg' => 'aas', + 'application/x-bcpio' => 'bcpio', + 'application/x-bittorrent' => 'torrent', + 'application/x-blorb' => 'blb', + 'application/x-bzip' => 'bz', + 'application/x-bzip2' => 'bz2', + 'application/x-cbr' => 'cbr', + 'application/x-cdlink' => 'vcd', + 'application/x-cfs-compressed' => 'cfs', + 'application/x-chat' => 'chat', + 'application/x-chess-pgn' => 'pgn', + 'application/x-conference' => 'nsc', + 'application/x-cpio' => 'cpio', + 'application/x-csh' => 'csh', + 'application/x-debian-package' => 'deb', + 'application/x-dgc-compressed' => 'dgc', + 'application/x-director' => 'dir', + 'application/x-doom' => 'wad', + 'application/x-dtbncx+xml' => 'ncx', + 'application/x-dtbook+xml' => 'dtb', + 'application/x-dtbresource+xml' => 'res', + 'application/x-dvi' => 'dvi', + 'application/x-envoy' => 'evy', + 'application/x-eva' => 'eva', + 'application/x-font-bdf' => 'bdf', + 'application/x-font-ghostscript' => 'gsf', + 'application/x-font-linux-psf' => 'psf', + 'application/x-font-otf' => 'otf', + 'application/x-font-pcf' => 'pcf', + 'application/x-font-snf' => 'snf', + 'application/x-font-ttf' => 'ttf', + 'application/x-font-type1' => 'pfa', + 'application/x-font-woff' => 'woff', + 'application/x-freearc' => 'arc', + 'application/x-futuresplash' => 'spl', + 'application/x-gca-compressed' => 'gca', + 'application/x-glulx' => 'ulx', + 'application/x-gnumeric' => 'gnumeric', + 'application/x-gramps-xml' => 'gramps', + 'application/x-gtar' => 'gtar', + 'application/x-hdf' => 'hdf', + 'application/x-install-instructions' => 'install', + 'application/x-iso9660-image' => 'iso', + 'application/x-java-jnlp-file' => 'jnlp', + 'application/x-latex' => 'latex', + 'application/x-lzh-compressed' => 'lzh', + 'application/x-mie' => 'mie', + 'application/x-mobipocket-ebook' => 'prc', + 'application/x-ms-application' => 'application', + 'application/x-ms-shortcut' => 'lnk', + 'application/x-ms-wmd' => 'wmd', + 'application/x-ms-wmz' => 'wmz', + 'application/x-ms-xbap' => 'xbap', + 'application/x-msaccess' => 'mdb', + 'application/x-msbinder' => 'obd', + 'application/x-mscardfile' => 'crd', + 'application/x-msclip' => 'clp', + 'application/x-msdownload' => 'exe', + 'application/x-msmediaview' => 'mvb', + 'application/x-msmetafile' => 'wmf', + 'application/x-msmoney' => 'mny', + 'application/x-mspublisher' => 'pub', + 'application/x-msschedule' => 'scd', + 'application/x-msterminal' => 'trm', + 'application/x-mswrite' => 'wri', + 'application/x-netcdf' => 'nc', + 'application/x-nzb' => 'nzb', + 'application/x-pkcs12' => 'p12', + 'application/x-pkcs7-certificates' => 'p7b', + 'application/x-pkcs7-certreqresp' => 'p7r', + 'application/x-rar-compressed' => 'rar', + 'application/x-rar' => 'rar', + 'application/x-research-info-systems' => 'ris', + 'application/x-sh' => 'sh', + 'application/x-shar' => 'shar', + 'application/x-shockwave-flash' => 'swf', + 'application/x-silverlight-app' => 'xap', + 'application/x-sql' => 'sql', + 'application/x-stuffit' => 'sit', + 'application/x-stuffitx' => 'sitx', + 'application/x-subrip' => 'srt', + 'application/x-sv4cpio' => 'sv4cpio', + 'application/x-sv4crc' => 'sv4crc', + 'application/x-t3vm-image' => 't3', + 'application/x-tads' => 'gam', + 'application/x-tar' => 'tar', + 'application/x-tcl' => 'tcl', + 'application/x-tex' => 'tex', + 'application/x-tex-tfm' => 'tfm', + 'application/x-texinfo' => 'texinfo', + 'application/x-tgif' => 'obj', + 'application/x-ustar' => 'ustar', + 'application/x-wais-source' => 'src', + 'application/x-x509-ca-cert' => 'der', + 'application/x-xfig' => 'fig', + 'application/x-xliff+xml' => 'xlf', + 'application/x-xpinstall' => 'xpi', + 'application/x-xz' => 'xz', + 'application/x-zmachine' => 'z1', + 'application/xaml+xml' => 'xaml', + 'application/xcap-diff+xml' => 'xdf', + 'application/xenc+xml' => 'xenc', + 'application/xhtml+xml' => 'xhtml', + 'application/xml' => 'xml', + 'application/xml-dtd' => 'dtd', + 'application/xop+xml' => 'xop', + 'application/xproc+xml' => 'xpl', + 'application/xslt+xml' => 'xslt', + 'application/xspf+xml' => 'xspf', + 'application/xv+xml' => 'mxml', + 'application/yang' => 'yang', + 'application/yin+xml' => 'yin', + 'application/zip' => 'zip', + 'audio/adpcm' => 'adp', + 'audio/basic' => 'au', + 'audio/midi' => 'mid', + 'audio/mp3' => 'mp3', + 'audio/mp4' => 'mp4a', + 'audio/mpeg' => 'mpga', + 'audio/ogg' => 'oga', + 'audio/s3m' => 's3m', + 'audio/silk' => 'sil', + 'audio/vnd.dece.audio' => 'uva', + 'audio/vnd.digital-winds' => 'eol', + 'audio/vnd.dra' => 'dra', + 'audio/vnd.dts' => 'dts', + 'audio/vnd.dts.hd' => 'dtshd', + 'audio/vnd.lucent.voice' => 'lvp', + 'audio/vnd.ms-playready.media.pya' => 'pya', + 'audio/vnd.nuera.ecelp4800' => 'ecelp4800', + 'audio/vnd.nuera.ecelp7470' => 'ecelp7470', + 'audio/vnd.nuera.ecelp9600' => 'ecelp9600', + 'audio/vnd.rip' => 'rip', + 'audio/webm' => 'weba', + 'audio/x-aac' => 'aac', + 'audio/x-aiff' => 'aif', + 'audio/x-caf' => 'caf', + 'audio/x-flac' => 'flac', + 'audio/x-matroska' => 'mka', + 'audio/x-mpegurl' => 'm3u', + 'audio/x-ms-wax' => 'wax', + 'audio/x-ms-wma' => 'wma', + 'audio/x-pn-realaudio' => 'ram', + 'audio/x-pn-realaudio-plugin' => 'rmp', + 'audio/x-wav' => 'wav', + 'audio/xm' => 'xm', + 'chemical/x-cdx' => 'cdx', + 'chemical/x-cif' => 'cif', + 'chemical/x-cmdf' => 'cmdf', + 'chemical/x-cml' => 'cml', + 'chemical/x-csml' => 'csml', + 'chemical/x-xyz' => 'xyz', + 'image/bmp' => 'bmp', + 'image/x-ms-bmp' => 'bmp', + 'image/cgm' => 'cgm', + 'image/g3fax' => 'g3', + 'image/gif' => 'gif', + 'image/ief' => 'ief', + 'image/jpeg' => 'jpeg', + 'image/pjpeg' => 'jpeg', + 'image/ktx' => 'ktx', + 'image/png' => 'png', + 'image/prs.btif' => 'btif', + 'image/sgi' => 'sgi', + 'image/svg+xml' => 'svg', + 'image/tiff' => 'tiff', + 'image/vnd.adobe.photoshop' => 'psd', + 'image/vnd.dece.graphic' => 'uvi', + 'image/vnd.dvb.subtitle' => 'sub', + 'image/vnd.djvu' => 'djvu', + 'image/vnd.dwg' => 'dwg', + 'image/vnd.dxf' => 'dxf', + 'image/vnd.fastbidsheet' => 'fbs', + 'image/vnd.fpx' => 'fpx', + 'image/vnd.fst' => 'fst', + 'image/vnd.fujixerox.edmics-mmr' => 'mmr', + 'image/vnd.fujixerox.edmics-rlc' => 'rlc', + 'image/vnd.ms-modi' => 'mdi', + 'image/vnd.ms-photo' => 'wdp', + 'image/vnd.net-fpx' => 'npx', + 'image/vnd.wap.wbmp' => 'wbmp', + 'image/vnd.xiff' => 'xif', + 'image/webp' => 'webp', + 'image/x-3ds' => '3ds', + 'image/x-cmu-raster' => 'ras', + 'image/x-cmx' => 'cmx', + 'image/x-freehand' => 'fh', + 'image/x-icon' => 'ico', + 'image/x-mrsid-image' => 'sid', + 'image/x-pcx' => 'pcx', + 'image/x-pict' => 'pic', + 'image/x-portable-anymap' => 'pnm', + 'image/x-portable-bitmap' => 'pbm', + 'image/x-portable-graymap' => 'pgm', + 'image/x-portable-pixmap' => 'ppm', + 'image/x-rgb' => 'rgb', + 'image/x-tga' => 'tga', + 'image/x-xbitmap' => 'xbm', + 'image/x-xpixmap' => 'xpm', + 'image/x-xwindowdump' => 'xwd', + 'message/rfc822' => 'eml', + 'model/iges' => 'igs', + 'model/mesh' => 'msh', + 'model/vnd.collada+xml' => 'dae', + 'model/vnd.dwf' => 'dwf', + 'model/vnd.gdl' => 'gdl', + 'model/vnd.gtw' => 'gtw', + 'model/vnd.mts' => 'mts', + 'model/vnd.vtu' => 'vtu', + 'model/vrml' => 'wrl', + 'model/x3d+binary' => 'x3db', + 'model/x3d+vrml' => 'x3dv', + 'model/x3d+xml' => 'x3d', + 'text/cache-manifest' => 'appcache', + 'text/calendar' => 'ics', + 'text/css' => 'css', + 'text/csv' => 'csv', + 'text/html' => 'html', + 'text/n3' => 'n3', + 'text/plain' => 'txt', + 'text/prs.lines.tag' => 'dsc', + 'text/richtext' => 'rtx', + 'text/rtf' => 'rtf', + 'text/sgml' => 'sgml', + 'text/tab-separated-values' => 'tsv', + 'text/troff' => 't', + 'text/turtle' => 'ttl', + 'text/uri-list' => 'uri', + 'text/vcard' => 'vcard', + 'text/vnd.curl' => 'curl', + 'text/vnd.curl.dcurl' => 'dcurl', + 'text/vnd.curl.scurl' => 'scurl', + 'text/vnd.curl.mcurl' => 'mcurl', + 'text/vnd.dvb.subtitle' => 'sub', + 'text/vnd.fly' => 'fly', + 'text/vnd.fmi.flexstor' => 'flx', + 'text/vnd.graphviz' => 'gv', + 'text/vnd.in3d.3dml' => '3dml', + 'text/vnd.in3d.spot' => 'spot', + 'text/vnd.sun.j2me.app-descriptor' => 'jad', + 'text/vnd.wap.wml' => 'wml', + 'text/vnd.wap.wmlscript' => 'wmls', + 'text/x-asm' => 's', + 'text/x-c' => 'c', + 'text/x-fortran' => 'f', + 'text/x-pascal' => 'p', + 'text/x-java-source' => 'java', + 'text/x-opml' => 'opml', + 'text/x-nfo' => 'nfo', + 'text/x-setext' => 'etx', + 'text/x-sfv' => 'sfv', + 'text/x-uuencode' => 'uu', + 'text/x-vcalendar' => 'vcs', + 'text/x-vcard' => 'vcf', + 'video/3gpp' => '3gp', + 'video/3gpp2' => '3g2', + 'video/h261' => 'h261', + 'video/h263' => 'h263', + 'video/h264' => 'h264', + 'video/jpeg' => 'jpgv', + 'video/jpm' => 'jpm', + 'video/mj2' => 'mj2', + 'video/mp4' => 'mp4', + 'video/mpeg' => 'mpeg', + 'video/ogg' => 'ogv', + 'video/quicktime' => 'qt', + 'video/vnd.dece.hd' => 'uvh', + 'video/vnd.dece.mobile' => 'uvm', + 'video/vnd.dece.pd' => 'uvp', + 'video/vnd.dece.sd' => 'uvs', + 'video/vnd.dece.video' => 'uvv', + 'video/vnd.dvb.file' => 'dvb', + 'video/vnd.fvt' => 'fvt', + 'video/vnd.mpegurl' => 'mxu', + 'video/vnd.ms-playready.media.pyv' => 'pyv', + 'video/vnd.uvvu.mp4' => 'uvu', + 'video/vnd.vivo' => 'viv', + 'video/webm' => 'webm', + 'video/x-f4v' => 'f4v', + 'video/x-fli' => 'fli', + 'video/x-flv' => 'flv', + 'video/x-m4v' => 'm4v', + 'video/x-matroska' => 'mkv', + 'video/x-mng' => 'mng', + 'video/x-ms-asf' => 'asf', + 'video/x-ms-vob' => 'vob', + 'video/x-ms-wm' => 'wm', + 'video/x-ms-wmv' => 'wmv', + 'video/x-ms-wmx' => 'wmx', + 'video/x-ms-wvx' => 'wvx', + 'video/x-msvideo' => 'avi', + 'video/x-sgi-movie' => 'movie', + 'video/x-smv' => 'smv', + 'x-conference/x-cooltalk' => 'ice' + ]; + + /** + * Get extension by mime type + * + * @param string $mimeType + * @return string|null + */ + public function getExtension(string $mimeType) + { + return isset($this->mimeTypes[$mimeType])? $this->mimeTypes[$mimeType] : null; + } + + /** + * Get mime type by extension + * + * @param string $extension + * @return string|null + */ + public function getMimeType(string $extension) + { + $key = array_search($extension, $this->mimeTypes); + return $key ?: null; + } +} diff --git a/src/Security/Validator/Rules/In.php b/src/Security/Validator/Rules/In.php new file mode 100644 index 0000000..ab17fc2 --- /dev/null +++ b/src/Security/Validator/Rules/In.php @@ -0,0 +1,61 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + if (count($params) == 1 && is_array($params[0])) { + $params = $params[0]; + } + $this->params['allowed_values'] = $params; + return clone($this); + } + + /** + * Set strict value + * + * @param bool $strict + * @return void + */ + public function strict(bool $strict = true) + { + $this->strict = $strict; + } + + /** + * Check $value is existed + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['allowed_values']); + + $allowedValues = $this->parameter('allowed_values'); + + $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; + $allowedValuesText = Helper::join(Helper::wraps($allowedValues, "'"), ', ', ", {$or} "); + $this->setParameterText('allowed_values', $allowedValuesText); + + return in_array($value, $allowedValues, $this->strict); + } +} diff --git a/src/Security/Validator/Rules/Integer.php b/src/Security/Validator/Rules/Integer.php new file mode 100644 index 0000000..f97869d --- /dev/null +++ b/src/Security/Validator/Rules/Integer.php @@ -0,0 +1,44 @@ +requireParameters($this->fillableParams); + + $max = $this->getBytesSize($this->parameter('max')); + $valueSize = $this->getValueSize($value); + + if (!is_numeric($valueSize)) { + return false; + } + + return $valueSize <= $max; + } +} diff --git a/src/Security/Validator/Rules/Mimes.php b/src/Security/Validator/Rules/Mimes.php new file mode 100644 index 0000000..c28072d --- /dev/null +++ b/src/Security/Validator/Rules/Mimes.php @@ -0,0 +1,95 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->allowTypes($params); + return clone($this); + } + + /** + * Given $types and assign $this->params + * + * @param mixed $types + * @return self + */ + public function allowTypes($types): Rule + { + if (is_string($types)) { + $types = explode('|', $types); + } + + $this->params['allowed_types'] = $types; + + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $allowedTypes = $this->parameter('allowed_types'); + + if ($allowedTypes) { + $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; + $this->setParameterText('allowed_types', Helper::join(Helper::wraps($allowedTypes, "'"), ', ', ", {$or} ")); + } + + // below is Required rule job + if (!$this->isValueFromUploadedFiles($value) or $value['error'] == UPLOAD_ERR_NO_FILE) { + return true; + } + + if (!$this->isUploadedFile($value)) { + return false; + } + + // just make sure there is no error + if ($value['error']) { + return false; + } + + if (!empty($allowedTypes)) { + $guesser = new MimeTypeGuesser; + $ext = $guesser->getExtension($value['type']); + unset($guesser); + + if (!in_array($ext, $allowedTypes)) { + return false; + } + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/Min.php b/src/Security/Validator/Rules/Min.php new file mode 100644 index 0000000..c84eee6 --- /dev/null +++ b/src/Security/Validator/Rules/Min.php @@ -0,0 +1,57 @@ +requireParameters($this->fillableParams); + + $min = $this->getBytesSize($this->parameter('min')); + $valueSize = $this->getValueSize($value); + + if (!is_numeric($valueSize)) { + return false; + } + + return $valueSize >= $min; + } +} diff --git a/src/Security/Validator/Rules/NotIn.php b/src/Security/Validator/Rules/NotIn.php new file mode 100644 index 0000000..4011c1d --- /dev/null +++ b/src/Security/Validator/Rules/NotIn.php @@ -0,0 +1,61 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + if (count($params) == 1 and is_array($params[0])) { + $params = $params[0]; + } + $this->params['disallowed_values'] = $params; + return clone($this); + } + + /** + * Set strict value + * + * @param bool $strict + * @return void + */ + public function strict($strict = true) + { + $this->strict = $strict; + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['disallowed_values']); + + $disallowedValues = (array) $this->parameter('disallowed_values'); + + $and = $this->validation ? $this->validation->getTranslation('and') : 'and'; + $disallowedValuesText = Helper::join(Helper::wraps($disallowedValues, "'"), ', ', ", {$and} "); + $this->setParameterText('disallowed_values', $disallowedValuesText); + + return !in_array($value, $disallowedValues, $this->strict); + } +} diff --git a/src/Security/Validator/Rules/Nullable.php b/src/Security/Validator/Rules/Nullable.php new file mode 100644 index 0000000..7b51bfb --- /dev/null +++ b/src/Security/Validator/Rules/Nullable.php @@ -0,0 +1,40 @@ +setAttributeAsRequired(); + + return $this->validation->hasValue($this->attribute->getKey()); + } + + /** + * Set attribute is required if $this->attribute is set + * + * @return void + */ + protected function setAttributeAsRequired() + { + if ($this->attribute) { + $this->attribute->setRequired(true); + } + } +} diff --git a/src/Security/Validator/Rules/Regex.php b/src/Security/Validator/Rules/Regex.php new file mode 100644 index 0000000..227243f --- /dev/null +++ b/src/Security/Validator/Rules/Regex.php @@ -0,0 +1,49 @@ +requireParameters($this->fillableParams); + $regex = $this->parameter('regex'); + return preg_match($regex, $value) > 0; + } +} diff --git a/src/Security/Validator/Rules/Required.php b/src/Security/Validator/Rules/Required.php new file mode 100644 index 0000000..7ae2d63 --- /dev/null +++ b/src/Security/Validator/Rules/Required.php @@ -0,0 +1,72 @@ +setAttributeAsRequired(); + + if ($this->attribute and $this->attribute->hasRule('uploaded_file')) { + return $this->isValueFromUploadedFiles($value) and $value['error'] != UPLOAD_ERR_NO_FILE; + } + + if (is_string($value)) { + return mb_strlen(trim($value), 'UTF-8') > 0; + } + if (is_array($value)) { + return count($value) > 0; + } + return !is_null($value); + } + + /** + * Set attribute is required if $this->attribute is set + * + * @return void + */ + protected function setAttributeAsRequired() + { + if ($this->attribute) { + $this->attribute->setRequired(true); + } + } +} diff --git a/src/Security/Validator/Rules/RequiredIf.php b/src/Security/Validator/Rules/RequiredIf.php new file mode 100644 index 0000000..f71e707 --- /dev/null +++ b/src/Security/Validator/Rules/RequiredIf.php @@ -0,0 +1,73 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->params['field'] = array_shift($params); + $this->params['values'] = $params; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['field', 'values']); + + $anotherAttribute = $this->parameter('field'); + $definedValues = $this->parameter('values'); + $anotherValue = $this->getAttribute()->getValue($anotherAttribute); + + $validator = $this->validation->getValidator(); + $requiredValidator = $validator('required'); + + if (in_array($anotherValue, $definedValues)) { + $this->setAttributeAsRequired(); + return $requiredValidator->check($value, []); + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/RequiredUnless.php b/src/Security/Validator/Rules/RequiredUnless.php new file mode 100644 index 0000000..a94a34d --- /dev/null +++ b/src/Security/Validator/Rules/RequiredUnless.php @@ -0,0 +1,73 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->params['field'] = array_shift($params); + $this->params['values'] = $params; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['field', 'values']); + + $anotherAttribute = $this->parameter('field'); + $definedValues = $this->parameter('values'); + $anotherValue = $this->getAttribute()->getValue($anotherAttribute); + + $validator = $this->validation->getValidator(); + $requiredValidator = $validator('required'); + + if (!in_array($anotherValue, $definedValues)) { + $this->setAttributeAsRequired(); + return $requiredValidator->check($value, []); + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/RequiredWith.php b/src/Security/Validator/Rules/RequiredWith.php new file mode 100644 index 0000000..dc9ac81 --- /dev/null +++ b/src/Security/Validator/Rules/RequiredWith.php @@ -0,0 +1,70 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->params['fields'] = $params; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['fields']); + $fields = $this->parameter('fields'); + $validator = $this->validation->getValidator(); + $requiredValidator = $validator('required'); + + foreach ($fields as $field) { + if ($this->validation->hasValue($field)) { + $this->setAttributeAsRequired(); + return $requiredValidator->check($value, []); + } + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/RequiredWithAll.php b/src/Security/Validator/Rules/RequiredWithAll.php new file mode 100644 index 0000000..2ad3858 --- /dev/null +++ b/src/Security/Validator/Rules/RequiredWithAll.php @@ -0,0 +1,70 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->params['fields'] = $params; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['fields']); + $fields = $this->parameter('fields'); + $validator = $this->validation->getValidator(); + $requiredValidator = $validator('required'); + + foreach ($fields as $field) { + if (!$this->validation->hasValue($field)) { + return true; + } + } + + $this->setAttributeAsRequired(); + return $requiredValidator->check($value, []); + } +} diff --git a/src/Security/Validator/Rules/RequiredWithout.php b/src/Security/Validator/Rules/RequiredWithout.php new file mode 100644 index 0000000..f8588ed --- /dev/null +++ b/src/Security/Validator/Rules/RequiredWithout.php @@ -0,0 +1,70 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->params['fields'] = $params; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['fields']); + $fields = $this->parameter('fields'); + $validator = $this->validation->getValidator(); + $requiredValidator = $validator('required'); + + foreach ($fields as $field) { + if (!$this->validation->hasValue($field)) { + $this->setAttributeAsRequired(); + return $requiredValidator->check($value, []); + } + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/RequiredWithoutAll.php b/src/Security/Validator/Rules/RequiredWithoutAll.php new file mode 100644 index 0000000..6fa7b2b --- /dev/null +++ b/src/Security/Validator/Rules/RequiredWithoutAll.php @@ -0,0 +1,70 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->params['fields'] = $params; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $this->requireParameters(['fields']); + $fields = $this->parameter('fields'); + $validator = $this->validation->getValidator(); + $requiredValidator = $validator('required'); + + foreach ($fields as $field) { + if ($this->validation->hasValue($field)) { + return true; + } + } + + $this->setAttributeAsRequired(); + return $requiredValidator->check($value, []); + } +} diff --git a/src/Security/Validator/Rules/Same.php b/src/Security/Validator/Rules/Same.php new file mode 100644 index 0000000..bcdea89 --- /dev/null +++ b/src/Security/Validator/Rules/Same.php @@ -0,0 +1,52 @@ +requireParameters($this->fillableParams); + + $field = $this->parameter('field'); + $anotherValue = $this->getAttribute()->getValue($field); + + return $value == $anotherValue; + } +} diff --git a/src/Security/Validator/Rules/Sometimes.php b/src/Security/Validator/Rules/Sometimes.php new file mode 100644 index 0000000..503b1de --- /dev/null +++ b/src/Security/Validator/Rules/Sometimes.php @@ -0,0 +1,43 @@ +implicit = false; + + return true; + } + } + +} diff --git a/src/Security/Validator/Rules/Str.php b/src/Security/Validator/Rules/Str.php new file mode 100644 index 0000000..3ad5829 --- /dev/null +++ b/src/Security/Validator/Rules/Str.php @@ -0,0 +1,37 @@ +isValueFromUploadedFiles($value) && is_uploaded_file($value['tmp_name']); + } + + /** + * Resolve uploaded file value + * + * @param mixed $value + * @return array|null + */ + public function resolveUploadedFileValue($value) + { + if (!$this->isValueFromUploadedFiles($value)) { + return null; + } + + // Here $value should be an array: + // [ + // 'name' => string|array, + // 'type' => string|array, + // 'size' => int|array, + // 'tmp_name' => string|array, + // 'error' => string|array, + // ] + + // Flatten $value to it's array dot format, + // so our array must be something like: + // ['name' => string, 'type' => string, 'size' => int, ...] + // or for multiple values: + // ['name.0' => string, 'name.1' => string, 'type.0' => string, 'type.1' => string, ...] + // or for nested array: + // ['name.foo.bar' => string, 'name.foo.baz' => string, 'type.foo.bar' => string, 'type.foo.baz' => string, ...] + $arrayDots = Helper::arrayDot($value); + + $results = []; + foreach ($arrayDots as $key => $val) { + // Move first key to last key + // name.foo.bar -> foo.bar.name + $splits = explode(".", $key); + $firstKey = array_shift($splits); + $key = count($splits) ? implode(".", $splits) . ".{$firstKey}" : $firstKey; + + Helper::arraySet($results, $key, $val); + } + return $results; + } +} diff --git a/src/Security/Validator/Rules/Traits/SizeTrait.php b/src/Security/Validator/Rules/Traits/SizeTrait.php new file mode 100644 index 0000000..e1b59eb --- /dev/null +++ b/src/Security/Validator/Rules/Traits/SizeTrait.php @@ -0,0 +1,129 @@ +getAttribute() + && ($this->getAttribute()->hasRule('numeric') || $this->getAttribute()->hasRule('integer')) + && is_numeric($value) + ) { + $value = (float) $value; + } + + if (is_int($value) || is_float($value)) { + return (float) $value; + } elseif (is_string($value)) { + return (float) mb_strlen($value, 'UTF-8'); + } elseif ($this->isUploadedFileValue($value)) { + return (float) $value['size']; + } elseif (is_array($value)) { + return (float) count($value); + } else { + return false; + } + } + + /** + * Given $size and get the bytes + * + * @param string|int $size + * @return float + * @throws InvalidArgumentException + */ + protected function getBytesSize($size) + { + if (is_numeric($size)) { + return (float) $size; + } + + if (!is_string($size)) { + throw new InvalidArgumentException("Size must be string or numeric Bytes", 1); + } + + if (!preg_match("/^(?((\d+)?\.)?\d+)(?(B|K|M|G|T|P)B?)?$/i", $size, $match)) { + throw new InvalidArgumentException("Size is not valid format", 1); + } + + $number = (float) $match['number']; + $format = isset($match['format']) ? $match['format'] : ''; + + switch (strtoupper($format)) { + case "KB": + case "K": + return $number * 1024; + + case "MB": + case "M": + return $number * pow(1024, 2); + + case "GB": + case "G": + return $number * pow(1024, 3); + + case "TB": + case "T": + return $number * pow(1024, 4); + + case "PB": + case "P": + return $number * pow(1024, 5); + + default: + return $number; + } + } + + /** + * Check whether value is from $_FILES + * + * @param mixed $value + * @return bool + */ + public function isUploadedFileValue($value): bool + { + if (!is_array($value)) { + return false; + } + + $keys = ['name', 'type', 'tmp_name', 'size', 'error']; + foreach ($keys as $key) { + if (!array_key_exists($key, $value)) { + return false; + } + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/TypeArray.php b/src/Security/Validator/Rules/TypeArray.php new file mode 100644 index 0000000..2abff88 --- /dev/null +++ b/src/Security/Validator/Rules/TypeArray.php @@ -0,0 +1,44 @@ +pdo = $pdo; + } + + public function check($value): bool + { + // make sure required parameters exists + $this->requireParameters(['table', 'column']); + + // getting parameters + $table = $this->parameter('table'); + $column = $this->parameter('column'); + $except = $this->parameter('except'); + + if(isset($except) && in_array($value, $except)){ + return true; + } + + if ($except AND $except == $value) { + return true; + } + + // do query + $stmt = $this->pdo->prepare("select count(*) as count from {$table} where {$column} = :value"); + $stmt->bindParam(':value', $value); + $stmt->execute(); + $data = $stmt->fetch(\PDO::FETCH_ASSOC); + + // true for valid, false for invalid + return intval($data['count']) === 0; + } +} diff --git a/src/Security/Validator/Rules/UploadedFile.php b/src/Security/Validator/Rules/UploadedFile.php new file mode 100644 index 0000000..f89385b --- /dev/null +++ b/src/Security/Validator/Rules/UploadedFile.php @@ -0,0 +1,184 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + $this->minSize(array_shift($params)); + $this->maxSize(array_shift($params)); + $this->fileTypes($params); + + return clone($this); + } + + /** + * Given $size and set the max size + * + * @param string|int $size + * @return self + */ + public function maxSize($size): Rule + { + $this->params['max_size'] = $size; + return clone($this); + } + + /** + * Given $size and set the min size + * + * @param string|int $size + * @return self + */ + public function minSize($size): Rule + { + $this->params['min_size'] = $size; + return clone($this); + } + + /** + * Given $min and $max then set the range size + * + * @param string|int $min + * @param string|int $max + * @return self + */ + public function sizeBetween($min, $max): Rule + { + $this->minSize($min); + $this->maxSize($max); + + return clone($this); + } + + /** + * Given $types and assign $this->params + * + * @param mixed $types + * @return self + */ + public function fileTypes($types): Rule + { + if (is_string($types)) { + $types = explode('|', $types); + } + + $this->params['allowed_types'] = $types; + + return clone($this); + } + + /** + * {@inheritDoc} + */ + public function beforeValidate() + { + $attribute = $this->getAttribute(); + + // We only resolve uploaded file value + // from complex attribute such as 'files.photo', 'images.*', 'images.foo.bar', etc. + if (!$attribute->isUsingDotNotation()) { + return; + } + + $keys = explode(".", $attribute->getKey()); + $firstKey = array_shift($keys); + $firstKeyValue = $this->validation->getValue($firstKey); + + $resolvedValue = $this->resolveUploadedFileValue($firstKeyValue); + + // Return original value if $value can't be resolved as uploaded file value + if (!$resolvedValue) { + return; + } + + $this->validation->setValue($firstKey, $resolvedValue); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $minSize = $this->parameter('min_size'); + $maxSize = $this->parameter('max_size'); + $allowedTypes = $this->parameter('allowed_types'); + + if ($allowedTypes) { + $or = $this->validation ? $this->validation->getTranslation('or') : 'or'; + $this->setParameterText('allowed_types', Helper::join(Helper::wraps($allowedTypes, "'"), ', ', ", {$or} ")); + } + + // below is Required rule job + if (!$this->isValueFromUploadedFiles($value) or $value['error'] == UPLOAD_ERR_NO_FILE) { + return true; + } + + if (!$this->isUploadedFile($value)) { + return false; + } + + // just make sure there is no error + if ($value['error']) { + return false; + } + + if ($minSize) { + $bytesMinSize = $this->getBytesSize($minSize); + if ($value['size'] < $bytesMinSize) { + $this->setMessage('The :attribute file is too small, minimum size is :min_size'); + return false; + } + } + + if ($maxSize) { + $bytesMaxSize = $this->getBytesSize($maxSize); + if ($value['size'] > $bytesMaxSize) { + $this->setMessage('The :attribute file is too large, maximum size is :max_size'); + return false; + } + } + + if (!empty($allowedTypes)) { + $guesser = new MimeTypeGuesser; + $ext = $guesser->getExtension($value['type']); + unset($guesser); + + if (!in_array($ext, $allowedTypes)) { + $this->setMessage('The :attribute file type must be :allowed_types'); + return false; + } + } + + return true; + } +} diff --git a/src/Security/Validator/Rules/Uppercase.php b/src/Security/Validator/Rules/Uppercase.php new file mode 100644 index 0000000..1c292e5 --- /dev/null +++ b/src/Security/Validator/Rules/Uppercase.php @@ -0,0 +1,44 @@ +params + * + * @param array $params + * @return self + */ + public function fillParameters(array $params): Rule + { + if (count($params) == 1 and is_array($params[0])) { + $params = $params[0]; + } + return $this->forScheme($params); + } + + /** + * Given $schemes and assign $this->params + * + * @param array $schemes + * @return self + */ + public function forScheme($schemes): Rule + { + $this->params['schemes'] = (array) $schemes; + return clone($this); + } + + /** + * Check the $value is valid + * + * @param mixed $value + * @return bool + */ + public function check($value): bool + { + $schemes = $this->parameter('schemes'); + + if (!$schemes) { + return $this->validateCommonScheme($value); + } else { + foreach ($schemes as $scheme) { + $method = 'validate' . ucfirst($scheme) .'Scheme'; + if (method_exists($this, $method)) { + if ($this->{$method}($value)) { + return true; + } + } elseif ($this->validateCommonScheme($value, $scheme)) { + return true; + } + } + + return false; + } + } + + /** + * Validate $value is valid URL format + * + * @param mixed $value + * @return bool + */ + public function validateBasic($value): bool + { + return filter_var($value, FILTER_VALIDATE_URL) !== false; + } + + /** + * Validate $value is correct $scheme format + * + * @param mixed $value + * @param null $scheme + * @return bool + */ + public function validateCommonScheme($value, $scheme = null): bool + { + if (!$scheme) { + return $this->validateBasic($value) && (bool) preg_match("/^\w+:\/\//i", $value); + } else { + return $this->validateBasic($value) && (bool) preg_match("/^{$scheme}:\/\//", $value); + } + } + + /** + * Validate the $value is mailto scheme format + * + * @param mixed $value + * @return bool + */ + public function validateMailtoScheme($value): bool + { + return $this->validateBasic($value) && preg_match("/^mailto:/", $value); + } + + /** + * Validate the $value is jdbc scheme format + * + * @param mixed $value + * @return bool + */ + public function validateJdbcScheme($value): bool + { + return (bool) preg_match("/^jdbc:\w+:\/\//", $value); + } +} diff --git a/src/Security/Validator/Traits/MessagesTrait.php b/src/Security/Validator/Traits/MessagesTrait.php new file mode 100644 index 0000000..10c7a1e --- /dev/null +++ b/src/Security/Validator/Traits/MessagesTrait.php @@ -0,0 +1,70 @@ +messages[$key] = $message; + } + + /** + * Given $messages and set multiple messages + * + * @param array $messages + * @return void + */ + public function setMessages(array $messages) + { + $this->messages = array_merge($this->messages, $messages); + } + + /** + * Given message from given $key + * + * @param string $key + * @return string + */ + public function getMessage(string $key): string + { + return array_key_exists($key, $this->messages) ? $this->messages[$key] : $key; + } + + /** + * Get all $messages + * + * @return array + */ + public function getMessages(): array + { + return $this->messages; + } +} diff --git a/src/Security/Validator/Traits/PdoTrait.php b/src/Security/Validator/Traits/PdoTrait.php new file mode 100644 index 0000000..af17861 --- /dev/null +++ b/src/Security/Validator/Traits/PdoTrait.php @@ -0,0 +1,47 @@ +pdo; + } + + /** + * Set PDO + * + * @return array + */ + public function setPdo($pdo) + { + return $this->pdo = $pdo; + } + +} diff --git a/src/Security/Validator/Validation.php b/src/Security/Validator/Validation.php new file mode 100644 index 0000000..4286294 --- /dev/null +++ b/src/Security/Validator/Validation.php @@ -0,0 +1,744 @@ +validator = $validator; + $this->inputs = $this->resolveInputAttributes($inputs); + $this->messages = $messages; + $this->errors = new Errors; + + foreach ($rules as $attributeKey => $rules) { + // Aliases + $alias = explode(":", $attributeKey)[1] ?? null; + + if(!is_null($alias)){ + $this->setAlias($attributeKey, $alias); + } + + // Attribute + $attributeKey = explode(":", $attributeKey)[0]; + $this->addAttribute($attributeKey, $rules); + } + } + + /** + * Add attribute rules + * + * @param string $attributeKey + * @param string|array $rules + * @return void + */ + public function addAttribute(string $attributeKey, $rules) + { + $resolvedRules = $this->resolveRules($rules); + $attribute = new Attribute($this, $attributeKey, $this->getAlias($attributeKey), $resolvedRules); + $this->attributes[$attributeKey] = $attribute; + } + + /** + * Get attribute by key + * + * @param string $attributeKey + * @return null|\use Kiaan\Security\Attribute + */ + public function getAttribute(string $attributeKey) + { + return isset($this->attributes[$attributeKey])? $this->attributes[$attributeKey] : null; + } + + /** + * Run validation + * + * @param array $inputs + * @return void + */ + public function validate(array $inputs = []) + { + $this->errors = new Errors; // reset errors + $this->inputs = array_merge($this->inputs, $this->resolveInputAttributes($inputs)); + + // Before validation hooks + foreach ($this->attributes as $attributeKey => $attribute) { + foreach ($attribute->getRules() as $rule) { + if ($rule instanceof BeforeValidate) { + $rule->beforeValidate(); + } + } + } + + foreach ($this->attributes as $attributeKey => $attribute) { + $this->validateAttribute($attribute); + } + } + + /** + * Get Errors instance + * + * @return \use Kiaan\Security\Validator\Errors + */ + public function errors(): Errors + { + return $this->errors; + } + + /** + * Validate attribute + * + * @param \use Kiaan\Security\Attribute $attribute + * @return void + */ + protected function validateAttribute(Attribute $attribute) + { + if ($this->isArrayAttribute($attribute)) { + $attributes = $this->parseArrayAttribute($attribute); + foreach ($attributes as $i => $attr) { + $this->validateAttribute($attr); + } + return; + } + + $attributeKey = $attribute->getKey(); + $rules = $attribute->getRules(); + + $value = $this->getValue($attributeKey); + $isEmptyValue = $this->isEmptyValue($value); + + if ($attribute->hasRule('nullable') && $isEmptyValue) { + $rules = []; + } + + $isValid = true; + foreach ($rules as $ruleValidator) { + $ruleValidator->setAttribute($attribute); + + if ($ruleValidator instanceof ModifyValue) { + $value = $ruleValidator->modifyValue($value); + $isEmptyValue = $this->isEmptyValue($value); + } + + $valid = $ruleValidator->check($value); + + if ($isEmptyValue and $this->ruleIsOptional($attribute, $ruleValidator)) { + continue; + } + + if (!$valid) { + $isValid = false; + $this->addError($attribute, $value, $ruleValidator); + if ($ruleValidator->isImplicit()) { + break; + } + } + } + + if ($isValid) { + $this->setValidData($attribute, $value); + } else { + $this->setInvalidData($attribute, $value); + } + } + + /** + * Check whether given $attribute is array attribute + * + * @param \use Kiaan\Security\Attribute $attribute + * @return bool + */ + protected function isArrayAttribute(Attribute $attribute): bool + { + $key = $attribute->getKey(); + return strpos($key, '*') !== false; + } + + /** + * Parse array attribute into it's child attributes + * + * @param \use Kiaan\Security\Attribute $attribute + * @return array + */ + protected function parseArrayAttribute(Attribute $attribute): array + { + $attributeKey = $attribute->getKey(); + $data = Helper::arrayDot($this->initializeAttributeOnData($attributeKey)); + + $pattern = str_replace('\*', '([^\.]+)', preg_quote($attributeKey)); + + $data = array_merge($data, $this->extractValuesForWildcards( + $data, + $attributeKey + )); + + $attributes = []; + + foreach ($data as $key => $value) { + if ((bool) preg_match('/^'.$pattern.'\z/', $key, $match)) { + $attr = new Attribute($this, $key, null, $attribute->getRules()); + $attr->setPrimaryAttribute($attribute); + $attr->setKeyIndexes(array_slice($match, 1)); + $attributes[] = $attr; + } + } + + // set other attributes to each attributes + foreach ($attributes as $i => $attr) { + $otherAttributes = $attributes; + unset($otherAttributes[$i]); + $attr->setOtherAttributes($otherAttributes); + } + + return $attributes; + } + + /** + * Gather a copy of the attribute data filled with any missing attributes. + * + * @param string $attribute + * @return array + */ + protected function initializeAttributeOnData(string $attributeKey): array + { + $explicitPath = $this->getLeadingExplicitAttributePath($attributeKey); + + $data = $this->extractDataFromPath($explicitPath); + + $asteriskPos = strpos($attributeKey, '*'); + + if (false === $asteriskPos || $asteriskPos === (mb_strlen($attributeKey, 'UTF-8') - 1)) { + return $data; + } + + return Helper::arraySet($data, $attributeKey, null, true); + } + + /** + * Get all of the exact attribute values for a given wildcard attribute. + * + * @param array $data + * @param string $attributeKey + * @return array + */ + public function extractValuesForWildcards(array $data, string $attributeKey): array + { + $keys = []; + + $pattern = str_replace('\*', '[^\.]+', preg_quote($attributeKey)); + + foreach ($data as $key => $value) { + if ((bool) preg_match('/^'.$pattern.'/', $key, $matches)) { + $keys[] = $matches[0]; + } + } + + $keys = array_unique($keys); + + $data = []; + + foreach ($keys as $key) { + $data[$key] = Helper::arrayGet($this->inputs, $key); + } + + return $data; + } + + /** + * Get the explicit part of the attribute name. + * + * E.g. 'foo.bar.*.baz' -> 'foo.bar' + * + * Allows us to not spin through all of the flattened data for some operations. + * + * @param string $attributeKey + * @return string|null null when root wildcard + */ + protected function getLeadingExplicitAttributePath(string $attributeKey) + { + return rtrim(explode('*', $attributeKey)[0], '.') ?: null; + } + + /** + * Extract data based on the given dot-notated path. + * + * Used to extract a sub-section of the data for faster iteration. + * + * @param string|null $attributeKey + * @return array + */ + protected function extractDataFromPath($attributeKey): array + { + $results = []; + + $value = Helper::arrayGet($this->inputs, $attributeKey, '__missing__'); + + if ($value != '__missing__') { + Helper::arraySet($results, $attributeKey, $value); + } + + return $results; + } + + /** + * Add error to the $this->errors + * + * @param \use Kiaan\Security\Attribute $attribute + * @param mixed $value + * @param \use Kiaan\Security\Rule $ruleValidator + * @return void + */ + protected function addError(Attribute $attribute, $value, Rule $ruleValidator) + { + $ruleName = $ruleValidator->getKey(); + $message = $this->resolveMessage($attribute, $value, $ruleValidator); + + $this->errors->add($attribute->getKey(), $ruleName, $message); + } + + /** + * Check $value is empty value + * + * @param mixed $value + * @return boolean + */ + protected function isEmptyValue($value): bool + { + $requiredValidator = new Required; + return false === $requiredValidator->check($value, []); + } + + /** + * Check the rule is optional + * + * @param \use Kiaan\Security\Attribute $attribute + * @param \use Kiaan\Security\Rule $rule + * @return bool + */ + protected function ruleIsOptional(Attribute $attribute, Rule $rule): bool + { + return false === $attribute->isRequired() and + false === $rule->isImplicit() and + false === $rule instanceof Required; + } + + /** + * Resolve attribute name + * + * @param \use Kiaan\Security\Attribute $attribute + * @return string + */ + protected function resolveAttributeName(Attribute $attribute): string + { + $primaryAttribute = $attribute->getPrimaryAttribute(); + if (isset($this->aliases[$attribute->getKey()])) { + return $this->aliases[$attribute->getKey()]; + } elseif ($primaryAttribute and isset($this->aliases[$primaryAttribute->getKey()])) { + return $this->aliases[$primaryAttribute->getKey()]; + } elseif ($this->validator->isUsingHumanizedKey()) { + return $attribute->getHumanizedKey(); + } else { + return $attribute->getKey(); + } + } + + /** + * Resolve message + * + * @param \use Kiaan\Security\Attribute $attribute + * @param mixed $value + * @param \use Kiaan\Security\Rule $validator + * @return mixed + */ + protected function resolveMessage(Attribute $attribute, $value, Rule $validator): string + { + $primaryAttribute = $attribute->getPrimaryAttribute(); + $params = array_merge($validator->getParameters(), $validator->getParametersTexts()); + $attributeKey = $attribute->getKey(); + $ruleKey = $validator->getKey(); + $alias = $attribute->getAlias() ?: $this->resolveAttributeName($attribute); + $message = $validator->getMessage(); // default rule message + $messageKeys = [ + $attributeKey.$this->messageSeparator.$ruleKey, + $attributeKey, + $ruleKey + ]; + + if ($primaryAttribute) { + $primaryAttributeKey = $primaryAttribute->getKey(); + array_splice($messageKeys, 1, 0, $primaryAttributeKey.$this->messageSeparator.$ruleKey); + array_splice($messageKeys, 3, 0, $primaryAttributeKey); + } + + foreach ($messageKeys as $key) { + if (isset($this->messages[$key])) { + $message = $this->messages[$key]; + break; + } + } + + // Replace message params + $vars = array_merge($params, [ + 'attribute' => $alias, + 'value' => $value, + ]); + + foreach ($vars as $key => $value) { + $value = $this->stringify($value); + $message = str_replace(':'.$key, $value, $message); + } + + // Replace key indexes + $keyIndexes = $attribute->getKeyIndexes(); + foreach ($keyIndexes as $pathIndex => $index) { + $replacers = [ + "[{$pathIndex}]" => $index, + ]; + + if (is_numeric($index)) { + $replacers["{{$pathIndex}}"] = $index + 1; + } + + $message = str_replace(array_keys($replacers), array_values($replacers), $message); + } + + return $message; + } + + /** + * Stringify $value + * + * @param mixed $value + * @return string + */ + protected function stringify($value): string + { + if (is_string($value) || is_numeric($value)) { + return $value; + } elseif (is_array($value) || is_object($value)) { + return json_encode($value); + } else { + return ''; + } + } + + /** + * Resolve $rules + * + * @param mixed $rules + * @return array + */ + protected function resolveRules($rules): array + { + if (is_string($rules)) { + $rules = explode('|', $rules); + } + + $resolvedRules = []; + $validatorFactory = $this->getValidator(); + + foreach ($rules as $i => $rule) { + if (empty($rule)) { + continue; + } + $params = []; + + if (is_string($rule)) { + list($rulename, $params) = $this->parseRule($rule); + $validator = call_user_func_array($validatorFactory, array_merge([$rulename], $params)); + } elseif ($rule instanceof Rule) { + $validator = $rule; + } elseif ($rule instanceof Closure) { + $validator = call_user_func_array($validatorFactory, ['callback', $rule]); + } elseif (is_array($rule)) { + //dd($this->parseRule($rule)); + list($rulename, $params) = array($i, $rule); + $validator = call_user_func_array($validatorFactory, array_merge([$rulename], $params)); + } else { + $ruleName = is_object($rule) ? get_class($rule) : gettype($rule); + $message = "Rule must be a string, Closure or '".Rule::class."' instance. ".$ruleName." given"; + throw new \Exception(); + } + + $resolvedRules[] = $validator; + } + + return $resolvedRules; + } + + /** + * Parse $rule + * + * @param string $rule + * @return array + */ + protected function parseRule(string $rule): array + { + $exp = explode(':', $rule, 2); + $rulename = $exp[0]; + if ($rulename !== 'regex') { + $params = isset($exp[1])? explode(',', $exp[1]) : []; + } else { + $params = [$exp[1]]; + } + + return [$rulename, $params]; + } + + /** + * Given $attributeKey and $alias then assign alias + * + * @param mixed $attributeKey + * @param mixed $alias + * @return void + */ + public function setAlias(string $attributeKey, string $alias) + { + $this->aliases[$attributeKey] = $alias; + } + + /** + * Get attribute alias from given key + * + * @param mixed $attributeKey + * @return string|null + */ + public function getAlias(string $attributeKey) + { + return isset($this->aliases[$attributeKey])? $this->aliases[$attributeKey] : null; + } + + /** + * Set attributes aliases + * + * @param array $aliases + * @return void + */ + public function setAliases(array $aliases) + { + $this->aliases = array_merge($this->aliases, $aliases); + } + + /** + * Check validations are passed + * + * @return bool + */ + public function passes(): bool + { + return $this->errors->count() == 0; + } + + /** + * Check validations are failed + * + * @return bool + */ + public function fails(): bool + { + return !$this->passes(); + } + + /** + * Given $key and get value + * + * @param string $key + * @return mixed + */ + public function getValue(string $key) + { + return Helper::arrayGet($this->inputs, $key); + } + + /** + * Set input value + * + * @param string $key + * @param mixed $value + * @return void + */ + public function setValue(string $key, $value) + { + Helper::arraySet($this->inputs, $key, $value); + } + + /** + * Given $key and check value is exsited + * + * @param string $key + * @return boolean + */ + public function hasValue(string $key): bool + { + return Helper::arrayHas($this->inputs, $key); + } + + /** + * Get Validator class instance + * + * @return \use Kiaan\Security\Validator + */ + public function getValidator(): Validator + { + return $this->validator; + } + + /** + * Given $inputs and resolve input attributes + * + * @param array $inputs + * @return array + */ + protected function resolveInputAttributes(array $inputs): array + { + $resolvedInputs = []; + foreach ($inputs as $key => $rules) { + $exp = explode(':', $key); + + if (count($exp) > 1) { + // set attribute alias + $this->aliases[$exp[0]] = $exp[1]; + } + + $resolvedInputs[$exp[0]] = $rules; + } + + return $resolvedInputs; + } + + /** + * Get validated data + * + * @return array + */ + public function validatedData(): array + { + return array_merge($this->validData, $this->invalidData); + } + + /** + * Set valid data + * + * @param \use Kiaan\Security\Attribute $attribute + * @param mixed $value + * @return void + */ + protected function setValidData(Attribute $attribute, $value) + { + $key = $attribute->getKey(); + if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) { + Helper::arraySet($this->validData, $key, $value); + Helper::arrayUnset($this->invalidData, $key); + } else { + $this->validData[$key] = $value; + } + } + + /** + * Get valid data + * + * @return array + */ + public function validData(): array + { + return $this->validData; + } + + /** + * Set invalid data + * + * @param \use Kiaan\Security\Attribute $attribute + * @param mixed $value + * @return void + */ + protected function setInvalidData(Attribute $attribute, $value) + { + $key = $attribute->getKey(); + if ($attribute->isArrayAttribute() || $attribute->isUsingDotNotation()) { + Helper::arraySet($this->invalidData, $key, $value); + Helper::arrayUnset($this->validData, $key); + } else { + $this->invalidData[$key] = $value; + } + } + + /** + * Get invalid data + * + * @return void + */ + public function invalidData(): array + { + return $this->invalidData; + } +} diff --git a/src/Session.php b/src/Session.php new file mode 100644 index 0000000..a7e2297 --- /dev/null +++ b/src/Session.php @@ -0,0 +1,41 @@ +toArray()), false); + } + + /** + * Return object for all sessions + * + */ + public function all() { + return $this->toObject(); + } +} \ No newline at end of file diff --git a/src/Store.php b/src/Store.php new file mode 100644 index 0000000..259aed6 --- /dev/null +++ b/src/Store.php @@ -0,0 +1,41 @@ +version($ip); + $netVersion = $this->version($net); + if ($ipVersion !== $netVersion) { + return false; + } + + $maxMask = $ipVersion === self::IPV4 ? self::IPV4_ADDRESS_LENGTH : self::IPV6_ADDRESS_LENGTH; + $mask = isset($mask) ? $mask : $maxMask; + $netMask = isset($netMask) ? $netMask : $maxMask; + + $binIp = $this->ip2bin($ip); + $binNet = $this->ip2bin($net); + return substr($binIp, 0, $netMask) === substr($binNet, 0, $netMask) && $mask >= $netMask; + } + + /** + * Expands an IPv6 address to it's full notation. + * + * For example `2001:db8::1` will be expanded to `2001:0db8:0000:0000:0000:0000:0000:0001` + * + * @param string $ip the original valid IPv6 address + * @return string the expanded IPv6 address + */ + public function expandIPv6($ip) + { + $hex = unpack('H*hex', inet_pton($ip)); + return substr(preg_replace('/([a-f0-9]{4})/i', '$1:', $hex['hex']), 0, -1); + } + + /** + * Converts IP address to bits representation. + * + * @param string $ip the valid IPv4 or IPv6 address + * @return string bits as a string + * @throws Exception + */ + public function ip2bin($ip) + { + $ipBinary = null; + if ($this->version($ip) === self::IPV4) { + $ipBinary = pack('N', ip2long($ip)); + } elseif (@inet_pton('::1') === false) { + throw new \Exception('IPv6 is not supported by inet_pton()!'); + } else { + $ipBinary = inet_pton($ip); + } + + $result = ''; + for ($i = 0, $iMax = strlen($ipBinary); $i < $iMax; $i += 4) { + $result .= str_pad(decbin(unpack('N', substr($ipBinary, $i, 4))[1]), 32, '0', STR_PAD_LEFT); + } + return $result; + } + + /** + * get client IP + * + * @param string $ip the valid IPv4 or IPv6 address + * @return string bits as a string + * @throws NotSupportedException + */ + public function ip(): string + { + if (\array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) { + $forwardedForItems = \explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + + if (!empty($forwardedForItems)) { + /** @noinspection ReturnNullInspection */ + return \array_pop($forwardedForItems); + } + } + + return $_SERVER['REMOTE_ADDR'] ?? ''; + } + + /* + * Get a domain name’s IP address + * + */ + public function host(string $domain) + { + return gethostbyname($domain); + } + + /* + * How to detect the MAC address of a device + * + */ + public function mac() + { + //Buffering the output + ob_start(); + + //Getting configuration details + system('ipconfig /all'); + + //Storing output in a variable + $configdata = ob_get_contents(); + + // Clear the buffer + ob_clean(); + + //Extract only the physical address or Mac address from the output + $mac = "Physical"; + $pmac = strpos($configdata, $mac); + + // Get Physical Address + $macaddr = substr($configdata,($pmac+36),17); + + //Return Mac Address + return $macaddr; + } + +} \ No newline at end of file diff --git a/src/Support/Math.php b/src/Support/Math.php new file mode 100644 index 0000000..eb72603 --- /dev/null +++ b/src/Support/Math.php @@ -0,0 +1,85 @@ + 0) { + return '+'; + } elseif ($x === 0 || $x === 0.0) { + return 0; + } else { + return NAN; + } + } + + /** + * returns the positive value of a number. + * + */ + public function positive($x) + { + return abs($x); + } + + /** + * returns the negative value of a number. + * + */ + public function negative($x) + { + return -abs($x); + } + + /** + * Changing the sign of a number. + * + */ + public function signExchange($x) + { + return $x <= 0 ? abs($x) : -$x ; + } + +} \ No newline at end of file diff --git a/src/Support/Number.php b/src/Support/Number.php new file mode 100644 index 0000000..dd8364a --- /dev/null +++ b/src/Support/Number.php @@ -0,0 +1,138 @@ + 1) { + $commas = $commas - 1; + } + return implode(' ', $words); + } + +} \ No newline at end of file diff --git a/src/Support/Random.php b/src/Support/Random.php new file mode 100644 index 0000000..504ec32 --- /dev/null +++ b/src/Support/Random.php @@ -0,0 +1,138 @@ + '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(m)ove$/i' => '\1oves', + '/(f)oot$/i' => '\1eet', + '/(h)uman$/i' => '\1umans', + '/(s)tatus$/i' => '\1tatuses', + '/(s)taff$/i' => '\1taff', + '/(t)ooth$/i' => '\1eeth', + '/(quiz)$/i' => '\1zes', + '/^(ox)$/i' => '\1\2en', + '/([m|l])ouse$/i' => '\1ice', + '/(matr|vert|ind)(ix|ex)$/i' => '\1ices', + '/(x|ch|ss|sh)$/i' => '\1es', + '/([^aeiouy]|qu)y$/i' => '\1ies', + '/(hive)$/i' => '\1s', + '/(?:([^f])fe|([lr])f)$/i' => '\1\2ves', + '/sis$/i' => 'ses', + '/([ti])um$/i' => '\1a', + '/(p)erson$/i' => '\1eople', + '/(m)an$/i' => '\1en', + '/(c)hild$/i' => '\1hildren', + '/(buffal|tomat|potat|ech|her|vet)o$/i' => '\1oes', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|vir)us$/i' => '\1i', + '/us$/i' => 'uses', + '/(alias)$/i' => '\1es', + '/(ax|cris|test)is$/i' => '\1es', + '/(currenc)y$/' => '\1ies', + '/s$/' => 's', + '/^$/' => '', + '/$/' => 's', + ]; + + /** + * @var array the rules for converting a word into its singular form. + * The keys are the regular expressions and the values are the corresponding replacements. + */ + public $singulars = [ + '/([nrlm]ese|deer|fish|sheep|measles|ois|pox|media|ss)$/i' => '\1', + '/^(sea[- ]bass)$/i' => '\1', + '/(s)tatuses$/i' => '\1tatus', + '/(f)eet$/i' => '\1oot', + '/(t)eeth$/i' => '\1ooth', + '/^(.*)(menu)s$/i' => '\1\2', + '/(quiz)zes$/i' => '\\1', + '/(matr)ices$/i' => '\1ix', + '/(vert|ind)ices$/i' => '\1ex', + '/^(ox)en/i' => '\1', + '/(alias)(es)*$/i' => '\1', + '/(alumn|bacill|cact|foc|fung|nucle|radi|stimul|syllab|termin|viri?)i$/i' => '\1us', + '/([ftw]ax)es/i' => '\1', + '/(cris|ax|test)es$/i' => '\1is', + '/(shoe|slave)s$/i' => '\1', + '/(o)es$/i' => '\1', + '/ouses$/' => 'ouse', + '/([^a])uses$/' => '\1us', + '/([m|l])ice$/i' => '\1ouse', + '/(x|ch|ss|sh)es$/i' => '\1', + '/(m)ovies$/i' => '\1\2ovie', + '/(s)eries$/i' => '\1\2eries', + '/([^aeiouy]|qu)ies$/i' => '\1y', + '/([lr])ves$/i' => '\1f', + '/(tive)s$/i' => '\1', + '/(hive)s$/i' => '\1', + '/(drive)s$/i' => '\1', + '/([^fo])ves$/i' => '\1fe', + '/(^analy)ses$/i' => '\1sis', + '/(analy|diagno|^ba|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i' => '\1\2sis', + '/([ti])a$/i' => '\1um', + '/(p)eople$/i' => '\1\2erson', + '/(m)en$/i' => '\1an', + '/(c)hildren$/i' => '\1\2hild', + '/(n)ews$/i' => '\1\2ews', + '/(n)etherlands$/i' => '\1\2etherlands', + '/eaus$/' => 'eau', + '/(currenc)ies$/' => '\1y', + '/^(.*us)$/' => '\\1', + '/s$/i' => '', + ]; + + /** + * @var array the special rules for converting a word between its plural form and singular form. + * The keys are the special words in singular form, and the values are the corresponding plural form. + */ + protected $specials = [ + 'atlas' => 'atlases', + 'beef' => 'beefs', + 'brother' => 'brothers', + 'cafe' => 'cafes', + 'child' => 'children', + 'cookie' => 'cookies', + 'corpus' => 'corpuses', + 'cow' => 'cows', + 'curve' => 'curves', + 'foe' => 'foes', + 'ganglion' => 'ganglions', + 'genie' => 'genies', + 'genus' => 'genera', + 'graffito' => 'graffiti', + 'hoof' => 'hoofs', + 'loaf' => 'loaves', + 'man' => 'men', + 'money' => 'monies', + 'mongoose' => 'mongooses', + 'move' => 'moves', + 'mythos' => 'mythoi', + 'niche' => 'niches', + 'numen' => 'numina', + 'occiput' => 'occiputs', + 'octopus' => 'octopuses', + 'opus' => 'opuses', + 'ox' => 'oxen', + 'pasta' => 'pasta', + 'penis' => 'penises', + 'sex' => 'sexes', + 'soliloquy' => 'soliloquies', + 'testis' => 'testes', + 'trilby' => 'trilbys', + 'turf' => 'turfs', + 'wave' => 'waves', + 'Amoyese' => 'Amoyese', + 'bison' => 'bison', + 'Borghese' => 'Borghese', + 'bream' => 'bream', + 'breeches' => 'breeches', + 'britches' => 'britches', + 'buffalo' => 'buffalo', + 'cantus' => 'cantus', + 'carp' => 'carp', + 'chassis' => 'chassis', + 'clippers' => 'clippers', + 'cod' => 'cod', + 'coitus' => 'coitus', + 'Congoese' => 'Congoese', + 'contretemps' => 'contretemps', + 'corps' => 'corps', + 'debris' => 'debris', + 'diabetes' => 'diabetes', + 'djinn' => 'djinn', + 'eland' => 'eland', + 'elk' => 'elk', + 'equipment' => 'equipment', + 'Faroese' => 'Faroese', + 'flounder' => 'flounder', + 'Foochowese' => 'Foochowese', + 'gallows' => 'gallows', + 'Genevese' => 'Genevese', + 'Genoese' => 'Genoese', + 'Gilbertese' => 'Gilbertese', + 'graffiti' => 'graffiti', + 'headquarters' => 'headquarters', + 'herpes' => 'herpes', + 'hijinks' => 'hijinks', + 'Hottentotese' => 'Hottentotese', + 'information' => 'information', + 'innings' => 'innings', + 'jackanapes' => 'jackanapes', + 'Kiplingese' => 'Kiplingese', + 'Kongoese' => 'Kongoese', + 'Lucchese' => 'Lucchese', + 'mackerel' => 'mackerel', + 'Maltese' => 'Maltese', + 'mews' => 'mews', + 'moose' => 'moose', + 'mumps' => 'mumps', + 'Nankingese' => 'Nankingese', + 'news' => 'news', + 'nexus' => 'nexus', + 'Niasese' => 'Niasese', + 'Pekingese' => 'Pekingese', + 'Piedmontese' => 'Piedmontese', + 'pincers' => 'pincers', + 'Pistoiese' => 'Pistoiese', + 'pliers' => 'pliers', + 'Portuguese' => 'Portuguese', + 'proceedings' => 'proceedings', + 'rabies' => 'rabies', + 'rice' => 'rice', + 'rhinoceros' => 'rhinoceros', + 'salmon' => 'salmon', + 'Sarawakese' => 'Sarawakese', + 'scissors' => 'scissors', + 'series' => 'series', + 'Shavese' => 'Shavese', + 'shears' => 'shears', + 'siemens' => 'siemens', + 'species' => 'species', + 'swine' => 'swine', + 'testes' => 'testes', + 'trousers' => 'trousers', + 'trout' => 'trout', + 'tuna' => 'tuna', + 'Vermontese' => 'Vermontese', + 'Wenchowese' => 'Wenchowese', + 'whiting' => 'whiting', + 'wildebeest' => 'wildebeest', + 'Yengeese' => 'Yengeese', + ]; + + /** + * Converts a word to its plural form. + * Note that this is for English only! + * For example, 'apple' will become 'apples', and 'child' will become 'children'. + * @param string $word the word to be pluralized + * @return string the pluralized word + */ + public function pluralize($word) + { + if (isset($this->specials[$word])) { + return $this->specials[$word]; + } + foreach ($this->plurals as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return $word; + } + + /** + * Returns the singular of the $word. + * @param string $word the english word to singularize + * @return string Singular noun. + */ + public function singularize($word) + { + $result = array_search($word, $this->specials, true); + if ($result !== false) { + return $result; + } + foreach ($this->singulars as $rule => $replacement) { + if (preg_match($rule, $word)) { + return preg_replace($rule, $replacement, $word); + } + } + + return $word; + } + + /** + * Replace Everything Inside Two Strings + */ + public function replaceBetween(string $string, string $str, string $start, string $end): string + { + return preg_replace('/'.preg_quote($start).'[\s\S]+?'.preg_quote($end).'/', $str, $string); + } + + /** + * make slug (URL string) + */ + public function slug($string, $replacement = '-') + { + return strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', $replacement, $string))); + } + + /** + * Limit the number of characters in a string. Put value of $end to the string end. + * + * ### limit + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * limit( string $string, int $limit = 100, string $end = '...' ): string + * ``` + * + * #### Example + * ```php + * $string = 'The quick brown fox jumps over the lazy dog'; + * + * limit( $string, 15 ); + * + * // The quick brown... + * ``` + * + * @param string $string + * The string to limit the characters. + * @param int $limit + * The number of characters to limit. Defaults to 100. + * @param string $end + * The string to end the cut string. Defaults to '...' + * @return string + * The limited string with $end at the end. + */ + public function limit($string, $limit = 100, $end = '...') + { + if (mb_strwidth($string, 'UTF-8') <= $limit) { + return $string; + } + + return rtrim(mb_strimwidth($string, 0, $limit, '', 'UTF-8')) . $end; + } + + /** + * Get the part of a string before a given value. + * + * ### before + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * before( string $search, string $string ): string + * ``` + * + * #### Example + * ```php + * $string = 'The quick brown fox jumps over the lazy dog'; + * + * before( 'fox' $string ); + * + * // The quick brown + * ``` + * + * @param string $search + * The string to search for. + * @param string $string + * The string to search in. + * @return string + * The found string before the search string. Whitespaces at end will be removed. + */ + public function before($string, $search) + { + return $search === '' ? $string : rtrim(explode($search, $string)[0]); + } + + /** + * Return the part of a string after a given value. + * + * ### after + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * after( string $search, string $string ): string + * ``` + * + * #### Example + * ```php + * $string = 'The quick brown fox jumps over the lazy dog'; + * + * after( 'fox' $string ); + * + * // jumps over the lazy dog + * ``` + * + * @param string $search + * The string to search for. + * @param string $string + * The string to search in. + * @return string + * The found string after the search string. Whitespaces at beginning will be removed. + */ + public function after($string, $search) + { + return $search === '' ? $string : ltrim(array_reverse(explode($search, $string, 2))[0]); + } + + /** + * Return the content in a string between a left and right element. + * + * ### between + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * between( string $left, string $right, string $string ): array + * ``` + * + * #### Example + * ```php + * $string = 'foofoobarbar' + * + * between( '', '' $string ); + * + * // ( + * // [0] => foo + * // [1] => bar + * // ) + * ``` + * + * + * @param string $left + * The left element of the string to search. + * @param string $right + * The right element of the string to search. + * @param string $string + * The string to search in. + * @return array + * A result array with all matches of the search. + */ + public function between($string, $left, $right) + { + preg_match_all('/' . preg_quote($left, '/') . '(.*?)' . preg_quote($right, '/') . '/s', $string, $matches); + $result = array_map('trim', $matches[1]); + return $result[0]; + } + + /** + * Tests if a string contains a given element + * + * ### contains + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * contains( string|array $needle, string $haystack ): boolean + * ``` + * + * #### Example + * ```php + * $string = 'The quick brown fox jumps over the lazy dog'; + * $array = [ + * 'cat', + * 'fox' + * ]; + * + * contains( $array, $string ); + * + * // bool(true) + * ``` + * + * @param string|array $needle + * A string or an array of strings. + * @param string $haystack + * The string to search in. + * @return bool + * True if $needle is found, false otherwise. + */ + public function contains($string, $needle) + { + foreach ((array)$needle as $ndl) { + if (strpos($string, $ndl) !== false) { + return true; + } + } + + return false; + } + + /** + * Tests if a string contains a given element. Ignore case sensitivity. + * + * ### icontains + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * icontains( string|array $needle, string $haystack ): boolean + * ``` + * + * #### Example + * ```php + * $string = 'The quick brown fox jumps over the lazy dog'; + * $array = [ + * 'Cat', + * 'Fox' + * ]; + * + * icontains( $array, $string ); + * + * // bool(true) + * ``` + * + * @param string|array $needle + * A string or an array of strings. + * @param string $haystack + * The string to search in. + * @return bool + * True if $needle is found, false otherwise. + */ + public function containsIgnoreCase($string, $needle) + { + foreach ((array)$needle as $ndl) { + if (stripos($string, $ndl) !== false) { + return true; + } + } + + return false; + } + + /** + * Determine if a given string starts with a given substring. Ignore case sensitivity. + * + * ### istarts_with + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * istarts_with( string|array $needle, string $haystack ): boolean + * ``` + * + * #### Example + * ```php + * $string = 'The quick brown fox jumps over the lazy dog'; + * $array = [ + * 'cat', + * 'the' + * ]; + * + * istarts_with( $array, $string ); + * + * // bool(true) + * ``` + * + * @param string|array $needle + * The string or array of strings to search for. + * @param string $string + * The string to search in. + * @return bool + * True if $needle was found, false otherwise. + */ + public function startsWithIgnoreCase($string, $needle) + { + $hs = strtolower($string); + + foreach ((array)$needle as $ndl) { + $n = strtolower($ndl); + if ($n !== '' && substr($hs, 0, strlen($n)) === (string)$n) { + return true; + } + } + + return false; + } + + /** + * Determine if a given string ends with a given substring. + * + * ### iends_with + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * iends_with( string|array $needle, string $haystack ): boolean + * ``` + * + * #### Example + * ```php + * $string = 'The quick brown fox jumps over the lazy dog'; + * $array = [ + * 'Cat', + * 'Dog' + * ]; + * + * iends_with( $array, $string ); + * + * // bool(true) + * ``` + * + * @param string|array $needle + * The string or array of strings to search for. + * @param string $string + * The string to search in. + * @return bool + * True if $needle was found, false otherwise. + */ + public function endsWithIgnoreCase($string, $needle) + { + $hs = strtolower($string); + + foreach ((array)$needle as $ndl) { + $n = strtolower($ndl); + $length = strlen($ndl); + if ($length === 0 || (substr($hs, -$length) === (string)$n)) { + return true; + } + } + + return false; + } + + /** + * Return the part of a string after the last occurrence of a given search value. + * + * ### after_last + * Related global function (description see above). + * + * > #### [( jump back )](#available-php-functions) + * + * ```php + * after_last( string $search, string $string ): string + * ``` + * + * #### Example + * ```php + * $path = "/var/www/html/public/img/image.jpg"; + * + * after_last( '/' $path ); + * + * // image.jpg + * ``` + * + * @param string $search + * The string to search for. + * @param string $string + * The string to search in. + * @return string + * The found string after the last occurrence of the search string. Whitespaces at beginning will be removed. + */ + public function afterLast($string, $search) + { + return $search === '' ? $string : ltrim(array_reverse(explode($search, $string))[0]); + } + + /** + * Convert empty to null + * @param $string + * + * @return null + */ + public function emptyToNull(&$string) + { + return empty($string) ? null : $string; + } + + /** + * Limits a phrase to a given number of words. + * $text = static::limit_words($text);. + * + * @param string $str phrase to limit words of + * @param int $limit number of words to limit to + * @param string $end_char end character or entity + * + * @return string + */ + public function limitWords($str, $limit = 100, $end_char = null) + { + $limit = (int)$limit; + $end_char = (null === $end_char) ? '…' : $end_char; + + if ('' === \trim($str)) { + return $str; + } + + if ($limit <= 0) { + return $end_char; + } + + \preg_match('/^\s*+(?:\S++\s*+){1,' . $limit . '}/u', $str, $matches); + + // Only attach the end character if the matched string is shorter + // than the starting string. + return \rtrim($matches[0]) . ((\strlen($matches[0]) === \strlen($str)) ? '' : $end_char); + } + + /** + * replace Line breaks from string + * @param $string + * @param string $replaceWith + * + * @return mixed + */ + public function replaceLineBreaks($string, $replaceWith = ' ') + { + return \preg_replace('/[\r\n]+/', $replaceWith, $string); + } + + /** + * Check if a string contains any encoded special chars + * + * @param string $text + * @return bool + */ + public function containsHtml(string $text): bool + { + return $text !== \htmlspecialchars_decode($text); + } + + /** + * Make both, first and last char of given string lowercase + * + * @param string $str + * @return string + */ + public function lowerFirstAndLast(string $str): string + { + $str = \strrev($str); + $str = \lcfirst($str); + $str = \strrev($str); + + return \lcfirst($str); + } + + /** + * Convert numbers to bytes string + * + */ + public function formatBytes(int $size): string + { + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + $base = \log($size) / \log(1024); + + return \round(1024 ** ($base - \floor($base)), 1) . ' ' . $units[(int)\floor($base)]; + } + + /** + * Generate alphabetical string from given character index: + * + * 0 = 'a', 1 = 'b', ..., + * 25 = 'z' + * 26 = 'aa' (when index > 25: use character of index mod 25, + * repeated as many times as there are modulo "wrap-arounds") + * + * @param int $characterIndex + * @return string + */ + public function alpha(int $characterIndex): string + { + if($characterIndex <= 0){$characterIndex = 1;} + $characterIndex -=1; + $letters = \range('a', 'z'); + + if ($characterIndex <= 25) { + return (string)$letters[$characterIndex]; + } + + $dividend = $characterIndex + 1; + $alphaCharacter = ''; + + while ($dividend > 0) { + $modulo = ($dividend - 1) % 26; + $alphaCharacter = $letters[$modulo] . $alphaCharacter; + $dividend = \floor(($dividend - $modulo) / 26); + } + + return $alphaCharacter; + } + +/** + * replace First string only + * + */ + public function replaceFirst(string $subject, string $search, string $replace = ''): string + { + if ('' !== $search) { + /** @noinspection ReturnFalseInspection */ + $offset = \strpos($subject, $search); + + if (false !== $offset) { + return \substr_replace($subject, $replace, $offset, \strlen($search)); + } + } + + return $subject; + } + + /** + * replace First string last + * + */ + public function replaceLast(string $subject, string $search, string $replace = ''): string + { + /** @noinspection ReturnFalseInspection */ + $offset = \strrpos($subject, $search); + + return false !== $offset + ? \substr_replace($subject, $replace, $offset, \strlen($search)) + : $subject; + } + + /** + * Get part of string. + * + * @param string $string To get substring from. + * @param int $start Character to start at. + * @param int|null $length Number of characters to get. + * @param string $encoding The encoding to use, defaults to "UTF-8". + * + * @see https://php.net/manual/en/function.mb-substr.php + * + * @return string + */ + public function substr(string $string, int $start, int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * Counts words in a string. + * + * @param string $string + * @return int + */ + public function words($string) + { + return count(preg_split('/\s+/u', $string, -1, PREG_SPLIT_NO_EMPTY)); + } + + /** + * Get string length. + * + * @param string $string String to calculate length for. + * @param string $encoding The encoding to use, defaults to "UTF-8". + * + * @see https://php.net/manual/en/function.mb-strlen.php + * + * @return int + */ + public function length(string $string): int + { + return mb_strlen($string, 'UTF-8'); + } + + /** + * Converts string to lowercase using a specified character encoding. + * + * See: https://www.php.net/manual/en/mbstring.supported-encodings.php + * + * @param string $string + * @param string $encoding + * + * @return string + */ + + public function lowercase(string $string): string + { + return mb_convert_case($string, MB_CASE_LOWER, 'UTF-8'); + } + + /** + * Converts string to uppercase using a specified character encoding. + * + * See: https://www.php.net/manual/en/mbstring.supported-encodings.php + * + * @param string $string + * @param string $encoding + * + * @return string + */ + + public function uppercase(string $string, string $encoding = 'UTF-8'): string + { + return mb_convert_case($string, MB_CASE_UPPER, $encoding); + } + + /** + * Converts string to snake case, replacing any non-alpha + * and non-numeric characters with an underscore. + * + * @param string $string + * @param bool $lowercase (Convert string to lowercase) + * + * @return string + */ + public function snakeCase(string $string, bool $lowercase = true): string + { + + // Replace non letter or digit with underscore (_) + $string = preg_replace('/[^a-z0-9]+/i', '_', $string); + + // Transliterate + $string = iconv('utf-8', 'us-ascii//TRANSLIT', $string); + + // Trim + $string = trim($string, '_'); + + // Remove duplicate _ + $string = preg_replace('/_+/', '_', $string); + + if (true === $lowercase) { + + return $this->lowercase($string); + + } + + return $string; + + } + + /** + * Converts string to title case using a specified character encoding. + * + * @param string $string + * @param string $encoding + * + * @return string + */ + + public function titleCase(string $string, string $encoding = 'UTF-8'): string + { + return mb_convert_case($string, MB_CASE_TITLE, $encoding); + } + + /** + * Converts string to camel case, removing any non-alpha and non-numeric characters. + * + * @param string $string + * + * @return string + */ + + public function camelCase(string $string): string + { + + // Non-alpha and non-numeric characters become spaces + + $string = preg_replace("/[^a-z0-9]+/i", " ", $string); + + $string = ucwords(strtolower(trim($string))); + + return lcfirst(str_replace(" ", "", $string)); + + } + + /** + * Converts string to kebab case (URL-friendly slug), replacing any non-alpha + * and non-numeric characters with a hyphen. + * + * @param string $string + * @param bool $lowercase (Convert string to lowercase) + * + * @return string + */ + + public function kebabCase(string $string, bool $lowercase = false): string + { + + // Replace non letter or digit with hyphen (-) + $string = preg_replace('/[^a-z0-9]+/i', '-', $string); + + // Transliterate + $string = iconv('utf-8', 'us-ascii//TRANSLIT', $string); + + // Trim + $string = trim($string, '-'); + + // Remove duplicate - + $string = preg_replace('/-+/', '-', $string); + + if (true === $lowercase) { + + return $this->lowercase($string); + + } + + return $string; + + } + + /** + * Checks if a string starts with a given case-sensitive string. + * + * @param string $string + * @param string $starts_with + * + * @return bool + */ + + public function startsWith(string $string, string $starts_with = ''): bool + { + return (substr($string, 0, strlen($starts_with)) === $starts_with); + } + + /** + * Checks if a string ends with a given case-sensitive string. + * + * @param string $string + * @param string $ends_with + * + * @return bool + */ + + public function endsWith(string $string, string $ends_with = ''): bool + { + + $length = strlen($ends_with); + + return $length === 0 || (substr($string, -$length) === $ends_with); + + } + + /** + * Converts number to its ordinal English form. For example, converts 13 to 13th, 2 to 2nd ... + * @param int $number the number to get its ordinal value + * @return string + */ + public function ordinalize($number) + { + if (in_array($number % 100, range(11, 13))) { + return $number . 'th'; + } + switch ($number % 10) { + case 1: + return $number . 'st'; + case 2: + return $number . 'nd'; + case 3: + return $number . 'rd'; + default: + return $number . 'th'; + } + } + + /* + * Make URL inside text clickable link + * + */ + public function toLink(string $plaintext, $attributes='') + { + // Find and replace link + $str = preg_replace('@((https?://)?([-\w]+\.[-\w\.]+)+\w(:\d+)?(/([-\w/_\.~]*(\?\S+)?)?)*)@', '$1', $plaintext); + + // Add "http://" if not set + $str = preg_replace('/]*href\s*=\s*"((?!https?:\/\/)[^"]*)"[^>]*>/i', '', $str); + + return $str; + } + + /** + * String to lower + * + */ + public function lower(string $string) + { + return strtolower($string); + } + + /** + * String to upper + * + */ + public function upper(string $string) + { + return strtoupper($string); + } + +} \ No newline at end of file diff --git a/src/Support/Time.php b/src/Support/Time.php new file mode 100644 index 0000000..ebf8f92 --- /dev/null +++ b/src/Support/Time.php @@ -0,0 +1,89 @@ + 'year', + 30 * 24 * 60 * 60 => 'month', + 24 * 60 * 60 => 'day', + 60 * 60 => 'hour', + 60 => 'minute', + 1 => 'second' + ); + + $a_plural = array( 'year' => 'years', + 'month' => 'months', + 'day' => 'days', + 'hour' => 'hours', + 'minute' => 'minutes', + 'second' => 'seconds' + ); + + foreach ($a as $secs => $str) + { + $d = $etime / $secs; + if ($d >= 1) + { + $r = round($d); + return $r . ' ' . ($r > 1 ? $a_plural[$str] : $str) . ' ago'; + } + } + } + +} \ No newline at end of file diff --git a/src/Support/Validation.php b/src/Support/Validation.php new file mode 100644 index 0000000..46d2c88 --- /dev/null +++ b/src/Support/Validation.php @@ -0,0 +1,336 @@ + 0; + } + + /** + * Checks if given value is of type "float". + * + * @param $value + * + * @return bool + */ + public function float($value) + { + if (\is_float($value)) { + return true; + } elseif (!\is_string($value)) { + return false; + } + + return \preg_match('/^[0-9]+\.[0-9]+$/', $value) > 0; + } + + /** + * Check if value is boolen. + * @param $value + */ + public function bool($value): bool + { + return \is_bool($value); + } + + /** + * Check if value is TRUE. + * + * @param $value + * + * @return bool + */ + public function true($value) + { + return \is_bool($value) && true === $value; + } + + /** + * * Check if value is FALSE. + * + * @param $value + * + * @return bool + */ + public function false($value) + { + return \is_bool($value) && false === $value; + } + + /** + * Check if it's an array + * @param $value + */ + public function array($value): bool + { + return \is_array($value); + } + + /** + * Check if it's an double + * @param $value + */ + public function double($value): bool + { + return \is_double($value); + } + + /** + * Check if it's an integer + * @param $value + */ + public function int($value): bool + { + return \is_int($value); + } + + /** + * Check if it's an null + * @param $value + */ + public function null($value): bool + { + return \is_null($value); + } + + /** + * Check if it's an numeric + * @param $value + */ + public function numeric($value): bool + { + return \is_numeric($value); + } + + /** + * Check if it's an string + * @param $value + */ + public function string($value): bool + { + return \is_string($value); + } + + /** + * Check if it's an url + * @param $value + */ + public function url($value): bool + { + return \filter_var($value, FILTER_VALIDATE_URL); + } + + /** + * Checks if a string is blank. " " is considered as such. + * + * @param $string + * + * @return bool + */ + public function blank($value) + { + return !\strlen(\trim((string)$value)) > 0; + } + + /** + * Check if the client is mobile device + * + * @return bool + */ + public function mobile(): bool + { + return preg_match("/(android|avantgo|blackberry|bolt|boost|cricket|docomo|fone|hiptop|mini|mobi|palm|phone|pie|tablet|up\.browser|up\.link|webos|wos)/i", $_SERVER["HTTP_USER_AGENT"]); + } + + /** + * Check if the client is Android + * + * @return bool + */ + public function android(): bool + { + return (strpos($_SERVER['HTTP_USER_AGENT'], 'Android') !== false); + } + + /** + * Check if the client is Windows + * + * @return bool + */ + public function windows(): bool + { + /** @noinspection ReturnFalseInspection */ + return false !== \strpos($_SERVER['HTTP_USER_AGENT'], 'Win'); + } + + /** + * Check if the client is Macintosh + * + * @return bool + */ + public function mac(): bool + { + /** @noinspection ReturnFalseInspection */ + return false !== \strpos($_SERVER['HTTP_USER_AGENT'], 'mac'); + } + + /** + * Returns a value indicating whether the given array is an associative array. + * + * An array is associative if all its keys are strings. If `$allStrings` is false, + * then an array will be treated as associative if at least one of its keys is a string. + * + * Note that an empty array will NOT be considered associative. + * + * @param array $array the array being checked + * @param bool $allStrings whether the array keys must be all strings in order for + * the array to be treated as associative. + * @return bool whether the array is associative + */ + public function arrayAssoc($array) + { + $allStrings = true; + + if (!is_array($array) || empty($array)) { + return false; + } + + if ($allStrings) { + foreach ($array as $key => $value) { + if (!is_string($key)) { + return false; + } + } + + return true; + } + + foreach ($array as $key => $value) { + if (is_string($key)) { + return true; + } + } + + return false; + } + + /** + * Returns a value indicating whether the given array is an indexed array. + * + * An array is indexed if all its keys are integers. If `$consecutive` is true, + * then the array keys must be a consecutive sequence starting from 0. + * + * Note that an empty array will be considered indexed. + * + * @param array $array the array being checked + * @param bool $consecutive whether the array keys must be a consecutive sequence + * in order for the array to be treated as indexed. + * @return bool whether the array is indexed + */ + public function arrayIndexed($array) + { + $consecutive = false; + + if (!is_array($array)) { + return false; + } + + if (empty($array)) { + return true; + } + + if ($consecutive) { + return array_keys($array) === range(0, count($array) - 1); + } + + foreach ($array as $key => $value) { + if (!is_int($key)) { + return false; + } + } + + return true; + } + +} \ No newline at end of file diff --git a/src/Time.php b/src/Time.php new file mode 100644 index 0000000..a37666a --- /dev/null +++ b/src/Time.php @@ -0,0 +1,41 @@ +dataGlobal)){ + if(isset($this->dataGlobal['*'])){ + $global[] = $this->dataGlobal['*']; + } + + if(isset($this->dataGlobal[$path])){ + $global[] = $this->dataGlobal[$path]; + } + + $pathGlobal = explode('.', $path); + if(count($pathGlobal) > 1){ + array_pop($pathGlobal); + $pathGlobal = implode('.', $pathGlobal); + + if(isset($this->dataGlobal["$pathGlobal.*"])){ + $global[] = $this->dataGlobal["$pathGlobal.*"]; + } + } + + $my_global = array(); + foreach($global as $global){ + $my_global = array_merge($my_global, $global); + } + }else{ + $my_global = array(); + } + + // Data + $data = array_merge($this->data, $my_global, $data); + $this->data = array(); + + // Render + echo $this->clean(); + echo $this->run($path, $data); + die(); + } + + /** + * Render + * + * Render string + */ + public function render($path, $data=[]) { + // Global + if(isset($this->dataGlobal['*'])){ + $global = $this->dataGlobal['*']; + }else{ + $global = array(); + } + + // Data + $data = array_merge($this->data, $global, $data); + $this->data = array(); + + // Render + echo $this->clean(); + echo $this->run($path, $data); + die(); + } + + /** + * HTML page + * + * Get html code for page + */ + public function html($path, $data=[]) { + // Global + if(isset($this->dataGlobal['*'])){ + $global = $this->dataGlobal['*']; + }else{ + $global = array(); + } + + // Data + $data = array_merge($this->data, $global, $data); + $this->data = array(); + + // Render + $this->clean(); + $content = $this->run($path, $data); + return $this->runString($content, $data); + } + + /** + * Data + * + */ + public function data($variables, $value = null) { + if (is_array($variables)) { + $this->data = array_merge($variables, $this->data); + } else { + $this->data = array_merge([$variables => $value], $this->data); + } + + return clone($this); + } + + /** + * Returns true if the template exists. Otherwise it returns false + * + * @param $templateName + * @return bool + */ + public function exists($templateName) + { + $file = $this->getTemplateFile($templateName); + return file_exists($file); + } + + /** + * Directive + * + * Register a handler for custom directives, helper function & fillter. + * @param string $name + * @param callable $handler + * @return void + */ + public function directive($name, callable $handler) + { + $this->directiveRT($name, $handler); + } + + /** + * Adds a global variable. If $varname is an array then it merges all the values. + * Example: + *
    +     * $this->global('variable',10.5);
    +     * $this->global('variable2','hello');
    +     * // or we could add the two variables as:
    +     * $this->global(['variable'=>10.5,'variable2'=>'hello']);
    +     * 
    + * + * @param string|array $varname It is the name of the variable or it is an associative array + * @param mixed $value + * @return $this + */ + public function global($varname, $value = null, $target = '*') + { + if (is_array($varname)) { + // Target + $target = str_replace(["/", "\\"], ".", (is_null($value) ? '*' : trim(trim($value, '/')))); + + // Value + $value = $varname; + } else { + // Target + $target = str_replace(["/", "\\"], ".", trim(trim($target, '/'))); + + // Value + $value = [$varname => $value]; + } + + // Set + $this->dataGlobal[$target] = $value; + $this->variablesGlobal[$target] = $value; + + return clone($this); + } + + /** + * Get SCRF(input, value) + */ + public function getCsrf() + { + return $this->csrf; + } + + /** + * Set SCRF(input, value) + */ + public function setCsrf($input, $value) + { + $this->csrf = ["input" => $input, "value" => $value]; + } + + /** + * Get the file extension for template files. + * + * @return string + */ + public function getFileExtension() + { + return $this->fileExtension; + } + + /** + * Set the file extension for the template files. + * It must includes the leading dot e.g. .html.php + * + * @param string $fileExtension Example: .prefix.ext + */ + public function setFileExtension($fileExtension) + { + $this->fileExtension = $fileExtension; + } + + /** + * Get method + * Get method input name + */ + public function getMethod() + { + return $this->method; + } + + /** + * Set method + * Set method input name + */ + public function setMethod($name) + { + $this->method = $name; + } + + /** + * Get roots path + */ + public function getRootsPath() + { + return $this->rootsPath; + } + + /** + * Set roots path + */ + public function setRootsPath($root, $public) + { + return $this->rootsPath = ["root" => $root, "public" => $public, "asset" => $public]; + } + + /** + * Clean cache + * @return null + */ + public function clean() + { + $compiledPath = $this->compiledPath; + + // Clean + $files = glob("$compiledPath/*"); // get all file names + foreach($files as $file){ // iterate files + if(is_file($file)) { + unlink($file); // delete file + } + } + } + +} \ No newline at end of file diff --git a/src/Views/View/ClassesTrait.php b/src/Views/View/ClassesTrait.php new file mode 100644 index 0000000..db7361f --- /dev/null +++ b/src/Views/View/ClassesTrait.php @@ -0,0 +1,49 @@ +aliasClasses; + } + + /** + * Set alias classes + */ + public function setClasses(array $list) + { + $list = array_map('trim', $list); + return $this->aliasClasses = str_replace(['/', '//', '.'], '\\', $list); + } + + /** + * Add alias classes + */ + public function classes(array $list) + { + $list = array_map('trim', $list); + return $this->aliasClasses = str_replace(['/', '//', '.'], '\\', array_merge($this->aliasClasses, $list)); + } + +} \ No newline at end of file diff --git a/src/Views/View/CommentsTrait.php b/src/Views/View/CommentsTrait.php new file mode 100644 index 0000000..080107c --- /dev/null +++ b/src/Views/View/CommentsTrait.php @@ -0,0 +1,36 @@ +contentTags[0], $this->contentTags[1]); + return \preg_replace($pattern, $this->phpTag . '/*$1*/ ?>', $value); + } + +} + diff --git a/src/Views/View/Engine.php b/src/Views/View/Engine.php new file mode 100644 index 0000000..facef71 --- /dev/null +++ b/src/Views/View/Engine.php @@ -0,0 +1,2384 @@ +"", "value"=>""]; + + /* Method */ + public $method = "_method"; + + /* Echo tags */ + /** @var array Array of opening and closing tags for raw echos. */ + protected $rawTags = ['{!!', '!!}']; // stored for historical purpose. + /** @var array Array of opening and closing tags for regular echos. */ + protected $contentTags = ['{{', '}}']; + /** @var array Array of opening and closing tags for escaped echos. */ + protected $escapedTags = ['{{{', '}}}']; + /** @var string The "regular" / legacy echo string format. */ + protected $echoFormat = '\htmlentities(%s, ENT_QUOTES, \'UTF-8\', false)'; + protected $echoFormatOld = 'static::e(%s)'; + + /* + * File extension for the template files. + */ + protected $fileExtension = '.html'; + + /* + * Roots path + */ + protected $rootsPath = ["root" => "", "public" => "", "asset" => ""]; + + /* Options */ + /** @var string The path to the missing translations log file. If empty then every missing key is not saved. */ + public $missingLog = ''; + public $pipeEnable = true; + /** @var array Alias (with or without namespace) of the classes) */ + public $aliasClasses = []; + /** @var array All of the registered extensions. */ + protected $extensions = []; + /** @var array All of the finished, captured sections. */ + protected $sections = []; + /** @var string The template currently being compiled. For example "folder.template" */ + protected $fileName; + protected $currentView; + protected $notFoundPath; + /** @var array The stack of in-progress sections. */ + protected $sectionStack = []; + /** @var array The stack of in-progress loops. */ + protected $loopsStack = []; + /** @var array Dictionary of data */ + protected $data = []; + /** @var array Dictionary of global data */ + protected $dataGlobal = []; + /** @var array Dictionary of variables */ + protected $variables = []; + /** @var null Dictionary of global variables */ + protected $variablesGlobal = []; + /** @var array All of the available compiler functions. */ + protected $compilers = [ + 'Extensions', + 'Statements', + 'Comments', + 'Echos', + ]; + /** @var string|null it allows to sets the stack */ + protected $viewStack; + /** @var array used by $this->composer() */ + protected $composerStack = []; + /** @var array The stack of in-progress push sections. */ + protected $pushStack = []; + /** @var array All of the finished, captured push sections. */ + protected $pushes = []; + /** @var int The number of active rendering operations. */ + protected $renderCount = 0; + /** @var string[] Get the template path for the compiled views. */ + protected $templatePath; + /** @var string Get the compiled path for the compiled views. If null then it uses the default path */ + protected $compiledPath; + /** @var string the extension of the compiled file. */ + protected $compileExtension = '.htmlfinal'; + /** @var array Custom "directive" dictionary. Those directives run at compile time. */ + protected $customDirectives = []; + /** @var bool[] Custom directive dictionary. Those directives run at runtime. */ + protected $customDirectivesRT = []; + /** @var array Used for conditional if. */ + protected $conditions = []; + /** @var int Unique counter. It's used for extends */ + protected $uidCounter = 0; + /** @var bool if true then it removes tabs and unneeded spaces */ + protected $optimize = true; + /** @var bool if false, then the template is not compiled (but executed on memory). */ + protected $isCompiled = true; + /** @var bool */ + protected $isRunFast = false; + /** @var array Lines that will be added at the footer of the template */ + protected $footer = []; + /** @var string Placeholder to temporary mark the position of escape blocks. */ + protected $escapePlaceholder = '$__escape__$'; + /** @var array Array to temporary store the escape blocks found in the template. */ + protected $escapeBlocks = []; + /** @var int Counter to keep track of nested forelse statements. */ + protected $forelseCounter = 0; + /** @var string tag unique */ + protected $PARENTKEY = '@parentXYZABC'; + /** mode */ + protected $mode; + /** @var int Indicates the number of open switches */ + private $switchCount = 0; + /** @var bool Indicates if the switch is recently open */ + private $firstCaseInSwitch = true; + + /** + * Bob the constructor. + * The folder at $compiledPath is created in case it doesn't exist. + * + * @param string|array $templatePath If null then it uses (caller_folder)/views + * @param string $compiledPath If null then it uses (caller_folder)/compiles + * @param int $mode =[TemplatingEngine::MODE_AUTO,TemplatingEngine::MODE_DEBUG,TemplatingEngine::MODE_FAST,TemplatingEngine::MODE_SLOW][$i] + */ + public function __construct($templatePath = null, $compiledPath = null, $mode = 0) + { + if ($templatePath === null) { + $templatePath = __DIR__ . '/Resources/Temp'; + } + if ($compiledPath === null) { + $compiledPath = __DIR__ . '/Resources/Temp'; + } + + $templatePath = $this->prepare_path($templatePath); + $compiledPath = $this->prepare_path($compiledPath); + + $this->templatePath = (is_array($templatePath)) ? $templatePath : [$templatePath]; + $this->compiledPath = $compiledPath; + + $this->setMode($mode); + + if (!\file_exists($this->compiledPath)) { + $ok = @\mkdir($this->compiledPath, 0777, true); + if ($ok === false) { + $this->showError( + 'Constructing', + "Unable to create the compile folder [{$this->compiledPath}].", + true + ); + } + } + // If the traits has "Constructors", then we call them. + // Requisites. + // 1- the method must be public or protected + // 2- it must doesn't have arguments + // 3- It must have the name of the trait. i.e. trait=MyTrait, method=MyTrait() + $traits = get_declared_traits(); + if ($traits !== null) { + foreach ($traits as $trait) { + $r = explode('\\', $trait); + $name = end($r); + if (is_callable([$this, $name]) && method_exists($this, $name)) { + $this->{$name}(); + } + } + } + } + + /** + * Prepare path + * + */ + protected function prepare_path($path) + { + return str_replace(['/', '//', '.'], '\\', $path); + } + + /** + * Show an error in the web. + * + * @param string $id Title of the error + * @param string $text Message of the error + * @param bool $critic if true then the compilation is ended, otherwise it continues + * @return string + */ + public function showError($id, $text, $critic = false) + { + \ob_get_clean(); + echo "
    "; + echo "Templating engine Error [{$id}]:
    "; + echo "$text
    \n"; + if ($critic) { + die(1); + } + return ''; + } + + /** + * Escape HTML entities in a string. + * + * @param string $value + * @return string + */ + public static function e($value) + { + return (\is_array($value) || \is_object($value)) + ? \htmlentities(\print_r($value, true), ENT_QUOTES, 'UTF-8', false) + : \htmlentities($value, ENT_QUOTES, 'UTF-8', false); + } + + protected static function convertArgCallBack($k, $v) + { + return $k . "='{$v}' "; + } + + /** + * @param mixed|\DateTime $variable + * @param string|null $format + * @return string + */ + public function format($variable, $format = null) + { + if ($variable instanceof \DateTime) { + $format = $format === null ? 'Y/m/d' : $format; + return $variable->format($format); + } + $format = $format === null ? '%s' : $format; + return sprintf($format, $variable); + } + + /** + * It converts a text into a php code with echo
    + * Example:
    + *
    +     * $this->wrapPHP('$hello'); // "< ?php echo $this->e($hello); ? >"
    +     * $this->wrapPHP('$hello',''); // < ?php echo $this->e($hello); ? >
    +     * $this->wrapPHP('$hello','',false); // < ?php echo $hello; ? >
    +     * $this->wrapPHP('"hello"'); // "< ?php echo $this->e("hello"); ? >"
    +     * $this->wrapPHP('hello()'); // "< ?php echo $this->e(hello()); ? >"
    +     * 
    + * + * @param string $input The input value + * @param string $quote The quote used (to quote the result) + * @param bool $parse If the result will be parsed or not. If false then it's returned without $this->e + * @return string + */ + public function wrapPHP($input, $quote = '"', $parse = true) + { + if (strpos($input, '(') !== false && !$this->isQuoted($input)) { + if ($parse) { + return $quote . $this->phpTagEcho . '$this->e(' . $input . ');?>' . $quote; + } + + return $quote . $this->phpTagEcho . $input . ';?>' . $quote; + } + if (strpos($input, '$') === false) { + if ($parse) { + return self::enq($input); + } + + return $input; + } + if ($parse) { + return $quote . $this->phpTagEcho . '$this->e(' . $input . ');?>' . $quote; + } + return $quote . $this->phpTagEcho . $input . ';?>' . $quote; + } + + /** + * Returns true if the text is surrounded by quotes (double or single quote) + * + * @param string|null $text + * @return bool + */ + public function isQuoted($text) + { + if (!$text || strlen($text) < 2) { + return false; + } + if ($text[0] === '"' && substr($text, -1) === '"') { + return true; + } + return ($text[0] === "'" && substr($text, -1) === "'"); + } + + /** + * Escape HTML entities in a string. + * + * @param string $value + * @return string + */ + public static function enq($value) + { + if (\is_array($value) || \is_object($value)) { + return \htmlentities(\print_r($value, true), ENT_NOQUOTES, 'UTF-8', false); + } + return \htmlentities($value, ENT_NOQUOTES, 'UTF-8', false); + } + + /** + * @param string $view example "folder.template" + * @param string|null $alias example "mynewop". If null then it uses the name of the template. + */ + public function addInclude($view, $alias = null) + { + if (!isset($alias)) { + $alias = \explode('.', $view); + $alias = \end($alias); + } + $this->directive($alias, function ($expression) use ($view) { + $expression = $this->stripParentheses($expression) ?: '[]'; + return "{$this->phpTag} echo \$this->runChild('{$view}', {$expression}); ?>"; + }); + } + + /** + * Register a handler for custom directives. + * + * @param string $name + * @param callable $handler + * @return void + */ + public function directive($name, callable $handler) + { + $this->customDirectives[$name] = $handler; + $this->customDirectivesRT[$name] = false; + } + + /** + * Strip the parentheses from the given expression. + * + * @param string $expression + * @return string + */ + public function stripParentheses($expression) + { + if (static::startsWith($expression, '(')) { + $expression = \substr($expression, 1, -1); + } + return $expression; + } + + /** + * Determine if a given string starts with a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith($haystack, $needles) + { + foreach ((array)$needles as $needle) { + if ($needle != '') { + if (\function_exists('mb_strpos')) { + if (\mb_strpos($haystack, $needle) === 0) { + return true; + } + } elseif (\strpos($haystack, $needle) === 0) { + return true; + } + } + } + + return false; + } + + /** + * If false then the file is not compiled and it is executed directly from the memory.
    + * By default the value is true
    + * It also sets the mode to MODE_SLOW + * + * @param bool $bool + * @return TemplatingEngine + * @see \kiaan\TemplatingEngine\TemplatingEngine::setMode + */ + public function setIsCompiled($bool = false) + { + $this->isCompiled = $bool; + if (!$bool) { + $this->setMode(self::MODE_SLOW); + } + return clone($this); + } + + /** + * It sets the template and compile path (without trailing slash). + *

    Example:setPath("somefolder","otherfolder"); + * + * @param null|string|string[] $templatePath If null then it uses the current path /views folder + * @param null|string $compiledPath If null then it uses the current path /views folder + */ + public function setPath($templatePath=null, $compiledPath=null) + { + if ($templatePath === null) { + $templatePath = __DIR__ . '/Resources/Temp'; + } + if ($compiledPath === null) { + $compiledPath = __DIR__ . '/Resources/Temp'; + } + + $templatePath = $this->prepare_path($templatePath); + $compiledPath = $this->prepare_path($compiledPath); + + $this->templatePath = (is_array($templatePath)) ? $templatePath : [$templatePath]; + $this->compiledPath = $compiledPath; + } + + /** + * run the html engine. It returns the result of the code. + * + * @param string HTML to parse + * @param array $data + * @return string + * @throws Exception + */ + public function runString($string, $data = []) + { + $php = $this->compileString($string); + + $obLevel = \ob_get_level(); + \ob_start(); + \extract($data, EXTR_SKIP); + + $previousError = \error_get_last(); + + try { + @eval('?' . '>' . $php); + } catch (Exception $e) { + while (\ob_get_level() > $obLevel) { + \ob_end_clean(); + } + throw $e; + } catch (\ParseError $e) { // PHP 7 + while (\ob_get_level() > $obLevel) { + \ob_end_clean(); + } + throw new Exception($e->getMessage(), $e->getCode()); + } + + $lastError = \error_get_last(); // PHP 5.6 + if ($previousError != $lastError && $lastError['type'] == E_PARSE) { + while (\ob_get_level() > $obLevel) { + \ob_end_clean(); + } + throw new Exception($lastError['message'], $lastError['type']); + } + + return \ob_get_clean(); + } + + /** + * Compile the given html template contents. + * + * @param string $value + * @return string + */ + protected function compileString($value) + { + $result = ''; + if (\strpos($value, '@escape') !== false) { + $value = $this->storeEscapeBlocks($value); + } + $this->footer = []; + // Here we will loop through all of the tokens returned by the Zend lexer and + // parse each one into the corresponding valid PHP. We will then have this + // template as the correctly rendered PHP that can be rendered natively. + foreach (\token_get_all($value) as $token) { + $result .= \is_array($token) ? $this->parseToken($token) : $token; + } + if (!empty($this->escapeBlocks)) { + $result = $this->restoreEscapeBlocks($result); + } + // If there are any footer lines that need to get added to a template we will + // add them here at the end of the template. This gets used mainly for the + // template inheritance via the extends keyword that should be appended. + if (\count($this->footer) > 0) { + $result = \ltrim($result, PHP_EOL) + . PHP_EOL . \implode(PHP_EOL, \array_reverse($this->footer)); + } + return $result; + } + + /** + * Store the escape blocks and replace them with a temporary placeholder. + * + * @param string $value + * @return string + */ + protected function storeEscapeBlocks($value) + { + return \preg_replace_callback('/(?escapeBlocks[] = $matches[1]; + return $this->escapePlaceholder; + }, $value); + } + + /** + * Parse the tokens from the template. + * + * @param array $token + * + * @return string + * + * @see \kiaan\TemplatingEngine\TemplatingEngine::compileStatements + * @see \kiaan\TemplatingEngine\TemplatingEngine::compileExtends + * @see \kiaan\TemplatingEngine\TemplatingEngine::compileComments + * @see \kiaan\TemplatingEngine\TemplatingEngine::compileEchos + */ + protected function parseToken($token) + { + list($id, $content) = $token; + if ($id == T_INLINE_HTML) { + foreach ($this->compilers as $type) { + $content = $this->{"compile{$type}"}($content); + } + } + return $content; + } + + /** + * Replace the raw placeholders with the original code stored in the raw blocks. + * + * @param string $result + * @return string + */ + protected function restoreEscapeBlocks($result) + { + $result = \preg_replace_callback('/' . \preg_quote($this->escapePlaceholder) . '/', function () { + return \array_shift($this->escapeBlocks); + }, $result); + $this->escapeBlocks = []; + return $result; + } + + /** + * Start injecting content into a push section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startPush($section, $content = '') + { + if ($content === '') { + if (\ob_start()) { + $this->pushStack[] = $section; + } + } else { + $this->extendPush($section, $content); + } + } + + /* + * endswitch tag + */ + + /** + * Append content to a given push section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendPush($section, $content) + { + if (!isset($this->pushes[$section])) { + $this->pushes[$section] = []; // start an empty section + } + if (!isset($this->pushes[$section][$this->renderCount])) { + $this->pushes[$section][$this->renderCount] = $content; + } else { + $this->pushes[$section][$this->renderCount] .= $content; + } + } + + /** + * Start injecting content into a push section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startPrepend($section, $content = '') + { + if ($content === '') { + if (\ob_start()) { + \array_unshift($this->pushStack[], $section); + } + } else { + $this->extendPush($section, $content); + } + } + + /** + * Stop injecting content into a push section. + * + * @return string + */ + public function stopPush() + { + if (empty($this->pushStack)) { + $this->showError('stopPush', 'Cannot end a section without first starting one', true); + } + $last = \array_pop($this->pushStack); + $this->extendPush($last, \ob_get_clean()); + return $last; + } + + /** + * Stop injecting content into a push section. + * + * @return string + */ + public function stopPrepend() + { + if (empty($this->pushStack)) { + $this->showError('stopPrepend', 'Cannot end a section without first starting one', true); + } + $last = \array_shift($this->pushStack); + $this->extendStartPush($last, \ob_get_clean()); + return $last; + } + + /** + * Append content to a given push section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendStartPush($section, $content) + { + if (!isset($this->pushes[$section])) { + $this->pushes[$section] = []; // start an empty section + } + if (!isset($this->pushes[$section][$this->renderCount])) { + $this->pushes[$section][$this->renderCount] = $content; + } else { + $this->pushes[$section][$this->renderCount] = $content . $this->pushes[$section][$this->renderCount]; + } + } + + /** + * Get the string contents of a push section. + * + * @param string $section + * @param string $default + * @return string + */ + public function contentPushContent($section, $default = '') + { + if (!isset($this->pushes[$section])) { + return $default; + } + return \implode(\array_reverse($this->pushes[$section])); + } + + /** + * Get the string contents of a push section. + * + * @param int|string $each if int, then it split the foreach every $each numbers.
    + * if string, "c3" it means that it will split in 3 columns
    + * @param string $splitText + * @param string $splitEnd + * @return string + */ + public function splitForeach($each = 1, $splitText = ',', $splitEnd = '') + { + $loopStack = static::last($this->loopsStack); // array(7) { ["index"]=> int(0) ["remaining"]=> int(6) ["count"]=> int(5) ["first"]=> bool(true) ["last"]=> bool(false) ["depth"]=> int(1) ["parent"]=> NULL } + if (($loopStack['index']) == $loopStack['count'] - 1) { + return $splitEnd; + } + $eachN = 0; + if (is_numeric($each)) { + $eachN = $each; + } elseif (strlen($each) > 1) { + if ($each[0] === 'c') { + $eachN = $loopStack['count'] / substr($each, 1); + } + } else { + $eachN = PHP_INT_MAX; + } + + if (($loopStack['index'] + 1) % $eachN === 0) { + return $splitText; + } + return ''; + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback = null, $default = null) + { + if (\is_null($callback)) { + return empty($array) ? static::value($default) : \end($array); + } + return static::first(\array_reverse($array), $callback, $default); + } + + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + public static function value($value) + { + return $value instanceof Closure ? $value() : $value; + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback = null, $default = null) + { + if (\is_null($callback)) { + return empty($array) ? static::value($default) : \reset($array); + } + foreach ($array as $key => $value) { + if ($callback($key, $value)) { + return $value; + } + } + return static::value($default); + } + + /** + * @param string $name + * @param $args [] + * @return mixed + * @throws BadMethodCallException + */ + public function __call($name, $args) + { + if ($name === 'if') { + return $this->registerIfStatement(isset($args[0]) ? $args[0] : null, isset($args[1]) ? $args[1] : null); + } + throw new BadMethodCallException("function $name is not defined
    "); + } + + /** + * Register an "if" statement directive. + * + * @param string $name + * @param callable $callback + * @return string + */ + public function registerIfStatement($name, callable $callback) + { + $this->conditions[$name] = $callback; + + $this->directive($name, function ($expression) use ($name) { + $tmp = $this->stripParentheses($expression); + return $expression !== '' + ? $this->phpTag . " if (\$this->check('{$name}', {$tmp})): ?>" + : $this->phpTag . " if (\$this->check('{$name}')): ?>"; + }); + + $this->directive('else' . $name, function ($expression) use ($name) { + $tmp = $this->stripParentheses($expression); + return $expression !== '' + ? $this->phpTag . " elseif (\$this->check('{$name}', {$tmp})): ?>" + : $this->phpTag . " elseif (\$this->check('{$name}')): ?>"; + }); + + $this->directive('end' . $name, function () { + return $this->phpTag . ' endif; ?>'; + }); + return ''; + } + + /** + * Check the result of a condition. + * + * @param string $name + * @param array $parameters + * @return bool + */ + public function check($name, ...$parameters) + { + return \call_user_func($this->conditions[$name], ...$parameters); + } + + /** + * @param bool $bool + * @param string $view name of the view + * @param array $value arrays of values + * @return string + * @throws Exception + */ + public function includeWhen($bool = false, $view = '', $value = []) + { + if ($bool) { + return $this->runChild($view, $value); + } + return ''; + } + + /** + * @param bool $bool + * @param string $view name of the view + * @param array $value arrays of values + * @return string + * @throws Exception + */ + public function includeUnless($bool = false, $view = '', $value = []) + { + if (!$bool) { + return $this->runChild($view, $value); + } + return ''; + } + + /** + * Macro of function run + * + * @param $view + * @param array $variables + * @return string + * @throws Exception + */ + public function runChild($view, $variables = []) + { + if (\is_array($variables)) { + $newVariables = \array_merge($this->variables, $variables); + } else { + $this->showError('run/include', "Include/run variables should be defined as array ['idx'=>'value']", true); + return ''; + } + return $this->runInternal($view, $newVariables, false, false, $this->isRunFast); + } + + /** + * run the html engine. It returns the result of the code. + * + * @param string $view + * @param array $variables + * @param bool $forced if true then it recompiles no matter if the compiled file exists or not. + * @param bool $isParent + * @param bool $runFast if true then the code is not compiled neither checked and it runs directly the compiled + * version. + * @return string + * @throws Exception + * @noinspection PhpUnusedParameterInspection + */ + private function runInternal($view, $variables = [], $forced = false, $isParent = true, $runFast = false) + { + $this->currentView = $view; + if (@\count($this->composerStack)) { + $this->evalComposer($view); + } + if (@\count($this->variablesGlobal) > 0) { + $this->variables = \array_merge($variables, $this->variablesGlobal); + $this->variablesGlobal = []; // used so we delete it. + } else { + $this->variables = $variables; + } + if (!$runFast) { + // a) if the compile is forced then we compile the original file, then save the file. + // b) if the compile is not forced then we read the datetime of both file and we compared. + // c) in both cases, if the compiled doesn't exist then we compile. + if ($view) { + $this->fileName = $view; + } + $result = $this->compile($view, $forced); + if (!$this->isCompiled) { + return $this->evaluateText($result, $this->variables); + } + } elseif ($view) { + $this->fileName = $view; + } + $this->isRunFast = $runFast; + return $this->evaluatePath($this->getCompiledFile(), $this->variables); + } + + protected function evalComposer($view) + { + foreach ($this->composerStack as $viewKey => $fn) { + if ($this->wildCardComparison($view, $viewKey)) { + if (is_callable($fn)) { + $fn($this); + } elseif ($this->methodExistsStatic($fn, 'composer')) { + // if the method exists statically then $fn is the class and 'composer' is the name of the method + $fn::composer($this); + } elseif (is_object($fn) || class_exists($fn)) { + // if $fn is an object or it is a class and the class exists. + $instance = (is_object($fn)) ? $fn : new $fn(); + if (method_exists($instance, 'composer')) { + // and the method exists inside the instance. + $instance->composer($this); + } else { + if ($this->mode === self::MODE_DEBUG) { + throw new \RuntimeException('TemplatingEngine: composer() added an incorrect method [$fn]'); + } + throw new \RuntimeException('TemplatingEngine: composer() added an incorrect method'); + } + } else { + throw new \RuntimeException('TemplatingEngine: composer() added an incorrect method'); + } + } + } + } + + /** + * It compares with wildcards (*) and returns true if both strings are equals
    + * The wildcards only works at the beginning and/or at the end of the string.
    + * Example:
    + *

    +     * Text::wildCardComparison('abcdef','abc*'); // true
    +     * Text::wildCardComparison('abcdef','*def'); // true
    +     * Text::wildCardComparison('abcdef','*abc*'); // true
    +     * Text::wildCardComparison('abcdef','*cde*'); // true
    +     * Text::wildCardComparison('abcdef','*cde'); // false
    +     *
    +     * 
    + * + * @param string $text + * @param string|null $textWithWildcard + * + * @return bool + */ + protected function wildCardComparison($text, $textWithWildcard) + { + if (($textWithWildcard === null && $textWithWildcard === '') + || strpos($textWithWildcard, '*') === false) { + // if the text with wildcard is null or empty or it contains two ** or it contains no * then.. + return $text == $textWithWildcard; + } + if ($textWithWildcard === '*' || $textWithWildcard === '**') { + return true; + } + $c0 = $textWithWildcard[0]; + $c1 = substr($textWithWildcard, -1); + $textWithWildcardClean = str_replace('*', '', $textWithWildcard); + $p0 = strpos($text, $textWithWildcardClean); + if ($p0 === false) { + // no matches. + return false; + } + if ($c0 === '*' && $c1 === '*') { + // $textWithWildcard='*asasasas*' + return true; + } + if ($c1 === '*') { + // $textWithWildcard='asasasas*' + return $p0 === 0; + } + // $textWithWildcard='*asasasas' + $len = strlen($textWithWildcardClean); + return (substr($text, -$len) === $textWithWildcardClean); + } + + protected function methodExistsStatic($class, $method) + { + try { + $mc = new \ReflectionMethod($class, $method); + return $mc->isStatic(); + } catch (\ReflectionException $e) { + return false; + } + } + + /** + * Compile the view at the given path. + * + * @param string $templateName The name of the template. Example folder.template + * @param bool $forced If the compilation will be forced (always compile) or not. + * @return boolean|string True if the operation was correct, or false (if not exception) + * if it fails. It returns a string (the content compiled) if isCompiled=false + * @throws Exception + */ + public function compile($templateName = null, $forced = false) + { + $compiled = $this->getCompiledFile($templateName); + $template = $this->getTemplateFile($templateName); + if (!$this->isCompiled) { + return $this->compileString($this->getFile($template)); + } + if ($forced || $this->isExpired($templateName)) { + // compile the original file + $contents = $this->compileString($this->getFile($template)); + $dir = \dirname($compiled); + if (!\file_exists($dir)) { + $ok = @\mkdir($dir, 0777, true); + if ($ok === false) { + $this->showError( + 'Compiling', + "Unable to create the compile folder [{$dir}]. Check the permissions of it's parent folder.", + true + ); + return false; + } + } + if ($this->optimize) { + // removes space and tabs and replaces by a single space + $contents = \preg_replace('/^ {2,}/m', ' ', $contents); + $contents = \preg_replace('/^\t{2,}/m', ' ', $contents); + } + $ok = @\file_put_contents($compiled, $contents); + if ($ok === false) { + $this->showError( + 'Compiling', + "Unable to save the file [{$compiled}]. Check the compile folder is defined and has the right permission" + ); + return false; + } + } + return true; + } + + /** + * Get the full path of the compiled file. + * + * @param string $templateName + * @return string + */ + public function getCompiledFile($templateName = '') + { + $templateName = (empty($templateName)) ? $this->fileName : $templateName; + if ($this->getMode() == self::MODE_DEBUG) { + return $this->compiledPath . '/' . $templateName . $this->compileExtension; + } + + return $this->compiledPath . '/' . \sha1($templateName) . $this->compileExtension; + } + + /** + * Get the mode of the engine.See TemplatingEngine::MODE_* constants + * + * @return int=[self::MODE_AUTO,self::MODE_DEBUG,self::MODE_FAST,self::MODE_SLOW][$i] + */ + public function getMode() + { + if (\defined('TemplatingEngine_MODE')) { + $this->mode = TemplatingEngine_MODE; + } + return $this->mode; + } + + /** + * Set the compile mode + * + * @param $mode int=[self::MODE_AUTO,self::MODE_DEBUG,self::MODE_FAST,self::MODE_SLOW][$i] + * @return void + */ + public function setMode($mode) + { + $this->mode = $mode; + } + + /** + * Get the full path of the template file. + *

    Example: getTemplateFile('.abc.def')

    + * + * @param string $templateName template name. If not template is set then it uses the base template. + * @return string + */ + public function getTemplateFile($templateName = '') + { + $templateName = (empty($templateName)) ? $this->fileName : $templateName; + if (\strpos($templateName, '/') !== false) { + return $this->locateTemplate($templateName); // it's a literal + } + $arr = \explode('.', $templateName); + $c = \count($arr); + if ($c == 1) { + // its in the root of the template folder. + return $this->locateTemplate($templateName . $this->fileExtension); + } + + $file = $arr[$c - 1]; + \array_splice($arr, $c - 1, $c - 1); // delete the last element + $path = \implode('/', $arr); + return $this->locateTemplate($path . '/' . $file . $this->fileExtension); + } + + /** + * Find template file with the given name in all template paths in the order the paths were written + * + * @param string $name Filename of the template (without path) + * @return string template file + */ + private function locateTemplate($name) + { + $this->notFoundPath = ''; + foreach ($this->templatePath as $dir) { + $path = $dir . '/' . $name; + if (\file_exists($path)) { + return $path; + } + + $this->notFoundPath .= $path . ","; + } + return ''; + } + + /** + * Get the contents of a file. + * + * @param string $fullFileName It gets the content of a filename or returns ''. + * + * @return string + */ + public function getFile($fullFileName) + { + if (\is_file($fullFileName)) { + return \file_get_contents($fullFileName); + } + $this->showError('getFile', "File does not exist at paths (separated by comma) [{$this->notFoundPath}] or permission denied", true); + return ''; + } + + /** + * Determine if the view has expired. + * + * @param string|null $fileName + * @return bool + */ + public function isExpired($fileName) + { + $compiled = $this->getCompiledFile($fileName); + $template = $this->getTemplateFile($fileName); + if (!\file_exists($template)) { + if ($this->mode == self::MODE_DEBUG) { + $this->showError('Read file', 'Template not found :' . $this->fileName . " on file: $template", true); + } else { + $this->showError('Read file', 'Template not found :' . $this->fileName, true); + } + } + // If the compiled file doesn't exist we will indicate that the view is expired + // so that it can be re-compiled. Else, we will verify the last modification + // of the views is less than the modification times of the compiled views. + if (!$this->compiledPath || !\file_exists($compiled)) { + return true; + } + return \filemtime($compiled) < \filemtime($template); + } + + /** + * Evaluates a text (string) using the current variables + * + * @param string $content + * @param array $variables + * @return string + * @throws Exception + */ + protected function evaluateText($content, $variables) + { + \ob_start(); + \extract($variables); + // We'll evaluate the contents of the view inside a try/catch block so we can + // flush out any stray output that might get out before an error occurs or + // an exception is thrown. This prevents any partial views from leaking. + try { + eval(' ?>' . $content . $this->phpTag); + } catch (Exception $e) { + $this->handleViewException($e); + } + return \ltrim(\ob_get_clean()); + } + + /** + * Handle a view exception. + * + * @param Exception $e + * @return void + * @throws $e + */ + protected function handleViewException($e) + { + \ob_get_clean(); + throw $e; + } + + /** + * Evaluates a compiled file using the current variables + * + * @param string $compiledFile full path of the compile file. + * @param array $variables + * @return string + * @throws Exception + */ + protected function evaluatePath($compiledFile, $variables) + { + \ob_start(); + // note, the variables are extracted locally inside this method, + // they are not global variables :-3 + \extract($variables); + // We'll evaluate the contents of the view inside a try/catch block so we can + // flush out any stray output that might get out before an error occurs or + // an exception is thrown. This prevents any partial views from leaking. + try { + /** @noinspection PhpIncludeInspection */ + include $compiledFile; + } catch (Exception $e) { + $this->handleViewException($e); + } + return \ltrim(\ob_get_clean()); + } + + /** + * @param array $views array of views + * @param array $value + * @return string + * @throws Exception + */ + public function includeFirst($views = [], $value = []) + { + foreach ($views as $view) { + if ($this->templateExist($view)) { + return $this->runChild($view, $value); + } + } + return ''; + } + + /** + * Returns true if the template exists. Otherwise it returns false + * + * @param $templateName + * @return bool + */ + private function templateExist($templateName) + { + $file = $this->getTemplateFile($templateName); + return \file_exists($file); + } + + /** + * Convert an array such as ["class1"=>"myclass","style="mystyle"] to class1='myclass' style='mystyle' string + * + * @param array|string $array array to convert + * @return string + */ + public function convertArg($array) + { + if (!\is_array($array)) { + return $array; // nothing to convert. + } + return \implode(' ', \array_map('static::convertArgCallBack', \array_keys($array), $array)); + } + + public function ipClient() + { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR']) + && \preg_match('/^([d]{1,3}).([d]{1,3}).([d]{1,3}).([d]{1,3})$/', $_SERVER['HTTP_X_FORWARDED_FOR'])) { + return $_SERVER['HTTP_X_FORWARDED_FOR']; + } + return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : ''; + } + + /** + * Stop injecting content into a section and return its contents. + * + * @return string + */ + public function contentSection() + { + $sc = $this->stopSection(); + return isset($this->sections[$sc]) ? $this->sections[$sc] : null; + } + + /** + * Stop injecting content into a section. + * + * @param bool $overwrite + * @return string + */ + public function stopSection($overwrite = false) + { + if (empty($this->sectionStack)) { + throw new InvalidArgumentException('Cannot end a section without first starting one.'); + } + $last = \array_pop($this->sectionStack); + if ($overwrite) { + $this->sections[$last] = \ob_get_clean(); + } else { + $this->extendSection($last, \ob_get_clean()); + } + return $last; + } + + /** + * Append content to a given section. + * + * @param string $section + * @param string $content + * @return void + */ + protected function extendSection($section, $content) + { + if (isset($this->sections[$section])) { + $content = \str_replace($this->PARENTKEY, $content, $this->sections[$section]); + $this->sections[$section] = $content; + } else { + $this->sections[$section] = $content; + } + } + + /** + * Start injecting content into a section. + * + * @param string $section + * @param string $content + * @return void + */ + public function startSection($section, $content = '') + { + if ($content === '') { + \ob_start() && $this->sectionStack[] = $section; + } else { + $this->extendSection($section, $content); + } + } + + /** + * Stop injecting content into a section and append it. + * + * @return string + * @throws InvalidArgumentException + */ + public function appendSection() + { + if (empty($this->sectionStack)) { + throw new InvalidArgumentException('Cannot end a section without first starting one.'); + } + $last = \array_pop($this->sectionStack); + if (isset($this->sections[$last])) { + $this->sections[$last] .= \ob_get_clean(); + } else { + $this->sections[$last] = \ob_get_clean(); + } + return $last; + } + + /** + * Get the string contents of a section. + * + * @param string $section + * @param string $default + * @return string + */ + public function contentContent($section, $default = '') + { + if (isset($this->sections[$section])) { + return \str_replace($this->PARENTKEY, $default, $this->sections[$section]); + } + + return $default; + } + + /** + * Register a custom html compiler. + * + * @param callable $compiler + * @return void + */ + public function extend(callable $compiler) + { + $this->extensions[] = $compiler; + } + + /** + * Register a handler for custom directives for run at runtime + * + * @param string $name + * @param callable $handler + * @return void + */ + public function directiveRT($name, callable $handler) + { + $this->customDirectives[$name] = $handler; + $this->customDirectivesRT[$name] = true; + } + + /** + * Sets the escaped content tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @return void + */ + public function setEscapedContentTags($openTag, $closeTag) + { + $this->setContentTags($openTag, $closeTag, true); + } + + /** + * Gets the content tags used for the compiler. + * + * @return array + */ + public function getContentTags() + { + return $this->getTags(); + } + + /** + * Sets the content tags used for the compiler. + * + * @param string $openTag + * @param string $closeTag + * @param bool $escaped + * @return void + */ + public function setContentTags($openTag, $closeTag, $escaped = false) + { + $property = ($escaped === true) ? 'escapedTags' : 'contentTags'; + $this->{$property} = [\preg_quote($openTag), \preg_quote($closeTag)]; + } + + /** + * Gets the tags used for the compiler. + * + * @param bool $escaped + * @return array + */ + protected function getTags($escaped = false) + { + $tags = $escaped ? $this->escapedTags : $this->contentTags; + return \array_map('stripcslashes', $tags); + } + + /** + * Gets the escaped content tags used for the compiler. + * + * @return array + */ + public function getEscapedContentTags() + { + return $this->getTags(true); + } + + + /** + * Get the file extension for template files. + * + * @return string + */ + public function getCompiledExtension() + { + return $this->compileExtension; + } + + /** + * Set the file extension for the compiled files. + * Including the leading dot for the extension is required, e.g. .htmlc + * + * @param $fileExtension + */ + public function setCompiledExtension($fileExtension) + { + $this->compileExtension = $fileExtension; + } + + /** + * Add new loop to the stack. + * + * @param array|Countable $data + * @return void + */ + public function addLoop($data) + { + $length = \is_array($data) || $data instanceof Countable ? \count($data) : null; + $parent = static::last($this->loopsStack); + $this->loopsStack[] = [ + 'index' => -1, + 'iteration' => 0, + 'remaining' => isset($length) ? $length + 1 : null, + 'count' => $length, + 'first' => true, + 'even' => true, + 'odd' => false, + 'last' => isset($length) ? $length == 1 : null, + 'depth' => \count($this->loopsStack) + 1, + 'parent' => $parent ? (object)$parent : null, + ]; + } + + /** + * Increment the top loop's indices. + * + * @return object + */ + public function incrementLoopIndices() + { + $c = \count($this->loopsStack) - 1; + $loop = &$this->loopsStack[$c]; + + $loop['index']++; + $loop['iteration']++; + $loop['first'] = $loop['index'] == 0; + $loop['even'] = $loop['index'] % 2 == 0; + $loop['odd'] = !$loop['even']; + if (isset($loop['count'])) { + $loop['remaining']--; + $loop['last'] = $loop['index'] == $loop['count'] - 1; + } + return (object)$loop; + } + + /** + * Pop a loop from the top of the loop stack. + * + * @return void + */ + public function popLoop() + { + \array_pop($this->loopsStack); + } + + /** + * Get an instance of the first loop in the stack. + * + * @return array|object + */ + public function getFirstLoop() + { + return ($last = static::last($this->loopsStack)) ? (object)$last : null; + } + + /** + * Get the rendered contents of a partial from a loop. + * + * @param string $view + * @param array $data + * @param string $iterator + * @param string $empty + * @return string + * @throws Exception + */ + public function renderEach($view, $data, $iterator, $empty = 'raw|') + { + $result = ''; + + if (\count($data) > 0) { + // If is actually data in the array, we will loop through the data and append + // an instance of the partial view to the final result HTML passing in the + // iterated value of this data array, allowing the views to access them. + foreach ($data as $key => $value) { + $data = ['key' => $key, $iterator => $value]; + $result .= $this->runChild($view, $data); + } + } elseif (static::startsWith($empty, 'raw|')) { + $result = \substr($empty, 4); + } else { + $result = $this->run($empty, []); + } + return $result; + } + + /** + * Run the html engine. It returns the result of the code. + * + * @param string|null $view The name of the cache. Ex: "folder.folder.view" ("/folder/folder/view.html") + * @param array $variables An associative arrays with the values to display. + * @return string + * @throws Exception + */ + public function run($view = null, $variables = []) + { + $mode = $this->getMode(); + + if ($view === null) { + $view = $this->viewStack; + } + $this->viewStack = null; + if ($view === null) { + throw new \RuntimeException('TemplatingEngine: view not set'); + } + + $forced = $mode & 1; // mode=1 forced:it recompiles no matter if the compiled file exists or not. + $runFast = $mode & 2; // mode=2 runfast: the code is not compiled neither checked and it runs directly the compiled + $this->sections = []; + if ($mode == 3) { + $this->showError('run', "we can't force and run fast at the same time", true); + } + return $this->runInternal($view, $variables, $forced, true, $runFast); + } + + /** + * It sets the current view
    + * This value is cleared when it is used (method run).
    + * Example:
    + *
    +     * $this->setView('folder.view')->share(['var1'=>20])->run(); // or $this->run('folder.view',['var1'=>20]);
    +     * 
    + * + * @param string $view + * @return TemplatingEngine + */ + public function setView($view) + { + $this->viewStack = $view; + return clone($this); + } + + /** + * It injects a function, an instance, or a method class when a view is called.
    + * It could be stacked. If it sets null then it clears all definitions. + * Example:
    + *
    +     * $this->composer('folder.view',function($TemplatingEngine) { $TemplatingEngine->share('newvalue','hi there'); });
    +     * $this->composer('folder.view','namespace1\namespace2\SomeClass'); // SomeClass must exists and it must has the
    +     *                                                                   // method 'composer'
    +     * $this->composer('folder.*',$instance); // $instance must has the method called 'composer'
    +     * $this->composer(); // clear all composer.
    +     * 
    + * + * @param string|array|null $view It could contains wildcards (*). Example: 'aa.bb.cc','*.bb.cc','aa.bb.*','*.bb.*' + * + * @param callable|string|null $functionOrClass + * @return TemplatingEngine + */ + public function composer($view = null, $functionOrClass = null) + { + if ($view === null && $functionOrClass === null) { + $this->composerStack = []; + return clone($this); + } + if (is_array($view)) { + foreach ($view as $v) { + $this->composerStack[$v] = $functionOrClass; + } + } else { + $this->composerStack[$view] = $functionOrClass; + } + + return clone($this); + } + + /** + * @return string + */ + public function getPhpTag() + { + return $this->phpTag; + } + + /** + * @param string $phpTag + */ + public function setPhpTag($phpTag) + { + $this->phpTag = $phpTag; + } + + /** + * If true then it optimizes the result (it removes tab and extra spaces). + * + * @param bool $bool + * @return TemplatingEngine + */ + public function setOptimize($bool = false) + { + $this->optimize = $bool; + return clone($this); + } + + /** + * Get the entire loop stack. + * + * @return array + */ + public function getLoopStack() + { + return $this->loopsStack; + } + + /** + * It adds a string inside a quoted string
    + * example:
    + *
    +     * $this->addInsideQuote("'hello'"," world"); // 'hello world'
    +     * $this->addInsideQuote("hello"," world"); // hello world
    +     * 
    + * + * @param $quoted + * @param $newFragment + * @return string + */ + public function addInsideQuote($quoted, $newFragment) + { + if ($this->isQuoted($quoted)) { + return substr($quoted, 0, -1) . $newFragment . substr($quoted, -1); + } + return $quoted . $newFragment; + } + + /** + * Return true if the string is a php variable (it starts with $) + * + * @param string|null $text + * @return bool + */ + public function isVariablePHP($text) + { + if (!$text || strlen($text) < 2) { + return false; + } + return $text[0] === '$'; + } + + /** + * Its the same than @_e, however it parses the text (using sprintf). + * If the operation fails then, it returns the original expression without translation. + * + * @param $phrase + * + * @return string + */ + public function _ef($phrase) + { + $argv = \func_get_args(); + $r = $this->_e($phrase); + $argv[0] = $r; // replace the first argument with the translation. + $result = @sprintf(...$argv); + $result = ($result === false) ? $r : $result; + return $result; + } + + /** + * Tries to translate the word if its in the array defined by TemplatingEngineLang::$dictionary + * If the operation fails then, it returns the original expression without translation. + * + * @param $phrase + * + * @return string + */ + public function _e($phrase) + { + if ((!\array_key_exists($phrase, static::$dictionary))) { + $this->missingTranslation($phrase); + return $phrase; + } + + return static::$dictionary[$phrase]; + } + + /** + * Log a missing translation into the file $this->missingLog.
    + * If the file is not defined, then it doesn't write the log. + * + * @param string $txt Message to write on. + */ + private function missingTranslation($txt) + { + if (!$this->missingLog) { + return; // if there is not a file assigned then it skips saving. + } + $fz = @\filesize($this->missingLog); + if (\is_object($txt) || \is_array($txt)) { + $txt = \print_r($txt, true); + } + // Rewrite file if more than 100000 bytes + $mode = ($fz > 100000) ? 'w' : 'a'; + $fp = \fopen($this->missingLog, $mode); + \fwrite($fp, $txt . "\n"); + \fclose($fp); + } + + /** + * if num is more than one then it returns the phrase in plural, otherwise the phrase in singular. + * Note: the translation should be as follow: $msg['Person']='Person' $msg=['Person']['p']='People' + * + * @param string $phrase + * @param string $phrases + * @param int $num + * + * @return string + */ + public function _n($phrase, $phrases, $num = 0) + { + if ((!\array_key_exists($phrase, static::$dictionary))) { + $this->missingTranslation($phrase); + return ($num <= 1) ? $phrase : $phrases; + } + + return ($num <= 1) ? $this->_e($phrase) : $this->_e($phrases); + } + + /** + * Remove first and end quote from a quoted string of text + * + * @param mixed $text + * @return null|string|string[] + */ + public function stripQuotes($text) + { + if (!$text || strlen($text) < 2) { + return $text; + } + $text=trim($text); + $p0=$text[0]; + $p1=\substr($text, -1); + if ($p0===$p1 && ($p0==='"' || $p0==="'")) { + return \substr($text, 1, -1); + } + return $text; + } + + /** + * Execute the user defined extensions. + * + * @param string $value + * @return string + */ + protected function compileExtensions($value) + { + foreach ($this->extensions as $compiler) { + $value = $compiler($value, $this); + } + return $value; + } + + /** + * Compile html echos into valid PHP. + * + * @param string $value + * @return string + */ + protected function compileEchos($value) + { + foreach ($this->getEchoMethods() as $method => $length) { + $value = $this->$method($value); + } + return $value; + } + + /** + * Get the echo methods in the proper order for compilation. + * + * @return array + */ + protected function getEchoMethods() + { + $methods = [ + 'compileRawEchos' => \strlen(\stripcslashes($this->rawTags[0])), + 'compileEscapedEchos' => \strlen(\stripcslashes($this->escapedTags[0])), + 'compileRegularEchos' => \strlen(\stripcslashes($this->contentTags[0])), + ]; + \uksort($methods, static function ($method1, $method2) use ($methods) { + // Ensure the longest tags are processed first + if ($methods[$method1] > $methods[$method2]) { + return -1; + } + if ($methods[$method1] < $methods[$method2]) { + return 1; + } + // Otherwise give preference to raw tags (assuming they've overridden) + if ($method1 === 'compileRawEchos') { + return -1; + } + if ($method2 === 'compileRawEchos') { + return 1; + } + if ($method1 === 'compileEscapedEchos') { + return -1; + } + if ($method2 === 'compileEscapedEchos') { + return 1; + } + throw new BadMethodCallException('Method not defined'); + }); + return $methods; + } + + /** + * Compile html statements that start with "@". + * + * @param string $value + * + * @return mixed + */ + protected function compileStatements($value) + { + /** + * @param array $match + * [0]=full expression with @ and parenthesis + * [1]=expression without @ and argument + * [2]=???? + * [3]=argument with parenthesis and without the first @ + * [4]=argument without parenthesis. + * + * @return mixed|string + */ + $callback = function ($match) { + if (static::contains($match[1], $this->escapedTag)) { + // @@escaped tag + $match[0] = isset($match[3]) ? $match[1] . $match[3] : $match[1]; + } else { + if (strpos($match[1], '::') !== false) { + // Someclass::method + return $this->compileStatementClass($match); + } + if (isset($this->customDirectivesRT[$match[1]])) { + if ($this->customDirectivesRT[$match[1]] == true) { + $match[0] = $this->compileStatementCustom($match); + } else { + $match[0] = \call_user_func( + $this->customDirectives[$match[1]], + $this->stripParentheses(static::get($match, 3)) + ); + } + } elseif (\method_exists($this, $method = 'compile' . \ucfirst($match[1]))) { + // it calls the function compile + $match[0] = $this->$method(static::get($match, 3)); + } else { + //todo: $this->showError("@compile", "Operation not defined:@".$match[1], true); + return $match[0]; + } + } + return isset($match[3]) ? $match[0] : $match[0] . $match[2]; + }; + /* return \preg_replace_callback('/\B@(@?\w+)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value); */ + return preg_replace_callback('/\B@(@?\w+(?:::\w+)?)([ \t]*)(\( ( (?>[^()]+) | (?3) )* \))?/x', $callback, $value); + } + + /** + * Determine if a given string contains a given substring. + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains($haystack, $needles) + { + foreach ((array)$needles as $needle) { + if ($needle != '') { + if (\function_exists('mb_strpos')) { + if (\mb_strpos($haystack, $needle) !== false) { + return true; + } + } elseif (\strpos($haystack, $needle) !== false) { + return true; + } + } + } + + return false; + } + + private function compileStatementClass($match) + { + if (isset($match[3])) { + return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . $match[3] . '; ?>'; + } + + return $this->phpTagEcho . $this->fixNamespaceClass($match[1]) . '(); ?>'; + } + + /** + * Util method to fix namespace of a class
    + * Example: "SomeClass::method()" -> "\namespace\SomeClass::method()"
    + * + * @param string $text + * + * @return string + * @see \kiaan\TemplatingEngine\TemplatingEngine::$aliasClasses + */ + private function fixNamespaceClass($text) + { + if (strpos($text, '::') === false) { + return $text; + } + $classPart = explode('::', $text, 2); + if (isset($this->aliasClasses[$classPart[0]])) { + $classPart[0] = $this->aliasClasses[$classPart[0]]; + } + return $classPart[0] . '::' . $classPart[1]; + } + + /** + * For compile custom directive at runtime. + * + * @param $match + * @return string + */ + protected function compileStatementCustom($match) + { + $v = $this->stripParentheses(static::get($match, 3)); + $v = ($v == '') ? '' : ',' . $v; + return $this->phpTag . 'echo call_user_func($this->customDirectives[\'' . $match[1] . '\']' . $v . '); ?>'; + } + + /** + * Get an item from an array using "dot" notation. + * + * @param ArrayAccess|array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + $accesible = \is_array($array) || $array instanceof ArrayAccess; + if (!$accesible) { + return static::value($default); + } + if (\is_null($key)) { + return $array; + } + if (static::existsView($array, $key)) { + return $array[$key]; + } + foreach (\explode('.', $key) as $segment) { + if (static::existsView($array, $segment)) { + $array = $array[$segment]; + } else { + return static::value($default); + } + } + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function existsView($array, $key) + { + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + return \array_key_exists($key, $array); + } + + /** + * This method removes the parenthesis of the expression and parse the arguments. + * @param string $expression + * @return array + */ + protected function getArgs($expression) + { + return $this->parseArgs($this->stripParentheses($expression), ' '); + } + + /** + * It separates a string using a separator and excluding quotes and double quotes. + * + * @param string $text + * @param string $separator + * @return array + */ + public function parseArgs($text, $separator = ',') + { + if ($text === null || $text === '') { + return []; //nothing to convert. + } + $chars = str_split($text); + $parts = []; + $nextpart = ''; + $strL = count($chars); + /** @noinspection ForeachInvariantsInspection */ + for ($i = 0; $i < $strL; $i++) { + $char = $chars[$i]; + if ($char === '"' || $char === "'") { + $inext = strpos($text, $char, $i + 1); + $inext = $inext === false ? $strL : $inext; + $nextpart .= substr($text, $i, $inext - $i + 1); + $i = $inext; + } else { + $nextpart .= $char; + } + if ($char === $separator) { + $parts[] = substr($nextpart, 0, -1); + $nextpart = ''; + } + } + if ($nextpart !== '') { + $parts[] = $nextpart; + } + $result = []; + foreach ($parts as $part) { + $r = explode('=', $part, 2); + $result[trim($r[0])] = count($r) === 2 ? trim($r[1]) : null; + } + return $result; + } + + /** + * Compile the "raw" echo statements. + * + * @param string $value + * @return string + */ + protected function compileRawEchos($value) + { + $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->rawTags[0], $this->rawTags[1]); + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; + return $matches[1] ? \substr( + $matches[0], + 1 + ) : $this->phpTagEcho . $this->compileEchoDefaults($matches[2]) . '; ?>' . $whitespace; + }; + return \preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the default values for the echo statement. + * + * @param string $value + * @return string + */ + protected function compileEchoDefaults($value) + { + $result = \preg_replace('/^(?=\$)(.+?)(?:\s+or\s+)(.+?)$/s', 'isset($1) ? $1 : $2', $value); + if (!$this->pipeEnable) { + return $this->fixNamespaceClass($result); + } + return $this->pipeDream($this->fixNamespaceClass($result)); + } + + /** + * It converts a string separated by pipes | into an filtered expression.
    + * If the method exists (as directive), then it is used
    + * If the method exists (in this class) then it is used
    + * Otherwise, it uses a global function.
    + * If you want to escape the "|", then you could use "/|"
    + * Note: It only works if $this->pipeEnable=true and by default it is false
    + * Example:
    + *
    +     * $this->pipeDream('$name | strtolower | substr:0,4'); // strtolower(substr($name ,0,4)
    +     * $this->pipeDream('$name| getMode') // $this->getMode($name)
    +     * 
    + * + * @param string $result + * @return string + * @\kiaan\TemplatingEngine\TemplatingEngine::$pipeEnable + */ + protected function pipeDream($result) + { + $array = preg_split('~\\\\.(*SKIP)(*FAIL)|\|~s', $result); + $c = count($array) - 1; // base zero. + if ($c === 0) { + return $result; + } + + $prev = ''; + for ($i = $c; $i >= 1; $i--) { + $r = @explode(':', $array[$i], 2); + $fnName = trim($r[0]); + $fnNameF=$fnName[0]; // first character + if ($fnNameF === '"' || $fnNameF === '\'' || $fnNameF === '$' || is_numeric($fnNameF)) { + $fnName='!isset('.$array[0].') ? '.$fnName.' : '; + } else { + if (isset($this->customDirectives[$fnName])) { + $fnName = '$this->customDirectives[\'' . $fnName . '\']'; + } elseif (method_exists($this, $fnName)) { + $fnName = '$this->' . $fnName; + } + } + if ($i === 1) { + $prev = $fnName . '(' . $array[0]; + if (count($r) === 2) { + $prev .= ',' . $r[1]; + } + $prev .= ')'; + } else { + $prev = $fnName . '(' . $prev; + if (count($r) === 2) { + if ($i === 2) { + $prev .= ','; + } + $prev .= $r[1] . ')'; + } + } + } + return $prev; + } + + /** + * Compile the "regular" echo statements. {{ }} + * + * @param string $value + * @return string + */ + protected function compileRegularEchos($value) + { + $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->contentTags[0], $this->contentTags[1]); + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; + $wrapped = \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2])); + return $matches[1] ? \substr($matches[0], 1) : $this->phpTagEcho . $wrapped . '; ?>' . $whitespace; + }; + return \preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the escaped echo statements. {!! !!} + * + * @param string $value + * @return string + */ + protected function compileEscapedEchos($value) + { + $pattern = \sprintf('/(@)?%s\s*(.+?)\s*%s(\r?\n)?/s', $this->escapedTags[0], $this->escapedTags[1]); + $callback = function ($matches) { + $whitespace = empty($matches[3]) ? '' : $matches[3] . $matches[3]; + + return $matches[1] ? $matches[0] : $this->phpTag + . \sprintf($this->echoFormat, $this->compileEchoDefaults($matches[2])) . '; ?>' + . $whitespace; + //return $matches[1] ? $matches[0] : $this->phpTag + // . 'echo static::e(' . $this->compileEchoDefaults($matches[2]) . '); ? >' . $whitespace; + }; + return \preg_replace_callback($pattern, $callback, $value); + } + + /** + * Compile the each statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEach($expression) + { + return $this->phpTagEcho . "\$this->renderEach{$expression}; ?>"; + } + + protected function compileSet($expression) + { + $segments = \explode('=', \preg_replace("/[()\\\']/", '', $expression)); + $value = (\count($segments) >= 2) ? ' =@' . $segments[1] : '++'; + return $this->phpTag . \trim($segments[0]) . $value . '; ?>'; + } + + /** + * Compile the {@}compilestamp statement. + * + * @param string $expression + * + * @return false|string + */ + protected function compileCompileStamp($expression) + { + $expression = $this->stripQuotes($this->stripParentheses($expression)); + $expression = ($expression === '') ? 'Y-m-d H:i:s' : $expression; + return date($expression); + } + + /** + * compile the {@}viewname statement
    + * {@}viewname('compiled') returns the full compiled path + * {@}viewname('template') returns the full template path + * {@}viewname('') returns the view name. + * + * @param mixed $expression + * + * @return string + */ + protected function compileViewName($expression) + { + $expression = $this->stripQuotes($this->stripParentheses($expression)); + switch ($expression) { + case 'compiled': + return $this->getCompiledFile($this->fileName); + case 'template': + return $this->getTemplateFile($this->fileName); + default: + return $this->fileName; + } + } + + /** + * Used for @_e directive. + * + * @param $expression + * + * @return string + */ + protected function compile_e($expression) + { + return $this->phpTagEcho . "\$this->_e{$expression}; ?>"; + } + + /** + * Used for @_ef directive. + * + * @param $expression + * + * @return string + */ + protected function compile_ef($expression) + { + return $this->phpTagEcho . "\$this->_ef{$expression}; ?>"; + } + + /** + * Used for @_n directive. + * + * @param $expression + * + * @return string + */ + protected function compile_n($expression) + { + return $this->phpTagEcho . "\$this->_n{$expression}; ?>"; + } + + /** + * Compile a split of a foreach cycle. Used for example when we want to separate limites each "n" elements. + * + * @param string $expression + * @return string + */ + protected function compileSplitForeach($expression) + { + return $this->phpTagEcho . '$this::splitForeach' . $expression . '; ?>'; + } + +} \ No newline at end of file diff --git a/src/Views/View/FunctionsTrait.php b/src/Views/View/FunctionsTrait.php new file mode 100644 index 0000000..757da1e --- /dev/null +++ b/src/Views/View/FunctionsTrait.php @@ -0,0 +1,212 @@ +phpTag . "{$expression}; ?>" : $this->phpTag . ''; + } + + /** + * Compile end-php statement into valid PHP. + * + * @return string + */ + protected function compileEndphp() + { + return ' ?>'; + } + + /** + * Compile the unless statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileUnless($expression) + { + return $this->phpTag . "if ( ! $expression): ?>"; + } + + /** + * Compile the endunless statements into valid PHP. + * + * @return string + */ + protected function compileEndunless() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the unset statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileUnset($expression) + { + return $this->phpTag . "unset{$expression}; ?>"; + } + + /* + * Isset + */ + protected function compileIsset($expression) + { + return $this->phpTag . "if(isset{$expression}): ?>"; + } + + protected function compileEndIsset() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the forelse statements into valid PHP. + * + * @param string $expression empty if it's inside a for loop. + * @return string + */ + protected function compileEmpty($expression = '') + { + if ($expression == '') { + $empty = '$__empty_' . $this->forelseCounter--; + return $this->phpTag . "endforeach; if ({$empty}): ?>"; + } + return $this->phpTag . "if (empty{$expression}): ?>"; + } + + /* + * end-empty + */ + protected function compileEndEmpty() + { + return $this->phpTag . 'endif; ?>'; + } + + /* + * Use + */ + protected function compileUse($expression) + { + $value = $this->stripQuotes($this->stripParentheses($expression)); + + return $this->phpTag . 'use ' . $value . '; ?>'; + } + + /* + * CSRF + */ + protected function compilecsrf() + { + $input = $this->csrf['input']; + $value = $this->csrf['value']; + return ""; + } + + /* + * Method + */ + protected function compileMethod($expression) + { + $name = $this->method; + $value = $this->stripParentheses($expression); + + return ""; + } + + /* + * Root + * Get root path + */ + protected function compileRoot($expression) + { + $root = $this->rootsPath['root']; + $root = trim(trim($root, "\\"), "/"); + + $path = $this->stripQuotes($this->stripParentheses($expression)); + $path = trim(trim($path, "\\"), "/"); + + return "$root\\$path"; + } + + /* + * WWW + */ + protected function compileWww($expression) + { + $path = $this->stripQuotes($this->stripParentheses($expression)); + $path = trim(trim($path, "\\"), "/"); + + $protocol = ($_SERVER['REQUEST_SCHEME'] ?? 'http') . '://'; + $host = $_SERVER['HTTP_HOST'] ?? null; + + $script_name = str_replace('\\', '', dirname($_SERVER['SCRIPT_NAME'])); + $script_name = str_replace('/public', '', $script_name).'/'.$path; + + $base_url = $protocol . $host . $script_name; + $base_url = rtrim($base_url, "/"); + + return $base_url; + } + + /* + * Public + * + * Get public url + */ + protected function compilePublic($expression) + { + $path = $this->stripQuotes($this->stripParentheses($expression)); + $path = trim(trim($path, "\\"), "/"); + $public = $this->prepare_path($this->getRootsPath()['public']); + + $protocol = ($_SERVER['REQUEST_SCHEME'] ?? 'http') . '://'; + $host = $_SERVER['HTTP_HOST'] ?? null; + + $script_name = str_replace('\\', '', dirname($_SERVER['SCRIPT_NAME'])); + $script_name = str_replace("/$public", '', $script_name)."/$public/".$path; + + $base_url = $protocol . $host . $script_name; + $base_url = rtrim($base_url, "/"); + + return $base_url; + } + + /* + * Json + * + * Get asset url + */ + protected function compileJson($array) + { + $array = $this->stripParentheses($array); + return ""; + } + +} \ No newline at end of file diff --git a/src/Views/View/InheritanceTrait.php b/src/Views/View/InheritanceTrait.php new file mode 100644 index 0000000..6a74070 --- /dev/null +++ b/src/Views/View/InheritanceTrait.php @@ -0,0 +1,359 @@ +phpTagEcho . "\$this->contentContent{$expression}; ?>"; + } + + /** + * Compile the show statements into valid PHP. + * + * @return string + */ + protected function compileShow() + { + return $this->phpTagEcho . '$this->contentSection(); ?>'; + } + + /** + * Compile the content statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileContent($expression) + { + if(!$this->isSpaAjax()){ + return $this->phpTag . "\$this->startSection{$expression}; ?>"; + }else{ + return null; + } + } + + /** + * Compile the section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileVsection($expression) + { + return $this->compileContent($expression); + } + + /** + * Compile the append statements into valid PHP. + * + * @return string + */ + protected function compileAppend() + { + return $this->phpTag . '$this->appendSection(); ?>'; + } + + /** + * Compile the end-content statements into valid PHP. + * + * @return string + */ + protected function compileEndcontent() + { + if(!$this->isSpaAjax()){ + return $this->phpTag . '$this->stopSection(); ?>'; + }else{ + return null; + } + } + + /** + * Compile the end-Vsection statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileEndVsection($expression) + { + return $this->compileEndcontent(); + } + + /** + * Compile the extends statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileExtends($expression) + { + $expression = $this->stripParentheses($expression); + // $_shouldextend avoids to runchild if it's not evaluated. + // For example @if(something) @extends('aaa.bb') @endif() + // If something is false then it's not rendered at the end (footer) of the script. + $this->uidCounter++; + $data = $this->phpTag . 'if (isset($_shouldextend[' . $this->uidCounter . '])) { echo $this->runChild(' . $expression . '); } ?>'; + $this->footer[] = $data; + + if(!$this->isSpaAjax()){ + return $this->phpTag . '$_shouldextend[' . $this->uidCounter . ']=1; ?>'; + }else{ + return null; + } + } + + /** + * Execute the @parent command. This operation works in tandem with extendSection + * + * @return string + * @see extendSection + */ + protected function compileParent() + { + return $this->PARENTKEY; + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileInclude($expression) + { + $expression = $this->stripParentheses($expression); + return $this->phpTagEcho . '$this->runChild(' . $expression . '); ?>'; + } + + /** + * It loads an compiled template and paste inside the code.
    + * It uses more disk space but it decreases the number of includes
    + * + * @param $expression + * @return string + * @throws Exception + */ + protected function compileIncludeFast($expression) + { + $expression = $this->stripParentheses($expression); + $ex = $this->stripParentheses($expression); + $exp = \explode(',', $ex); + $file = $this->stripQuotes(isset($exp[0]) ? $exp[0] : null); + $fileC = $this->getCompiledFile($file); + if (!@\file_exists($fileC)) { + // if the file doesn't exist then it's created + $this->compile($file, true); + } + return $this->getFile($fileC); + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeIf($expression) + { + return $this->phpTag . 'if ($this->templateExist' . $expression . ') echo $this->runChild' . $expression . '; ?>'; + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeWhen($expression) + { + $expression = $this->stripParentheses($expression); + return $this->phpTagEcho . '$this->includeWhen(' . $expression . '); ?>'; + } + + /** + * Compile the include statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIncludeUnless($expression) + { + $expression = $this->stripParentheses($expression); + return $this->phpTagEcho . '$this->includeUnless(' . $expression . '); ?>'; + } + + /** + * Compile the includefirst statement + * + * @param string $expression + * @return string + */ + protected function compileIncludeFirst($expression) + { + $expression = $this->stripParentheses($expression); + return $this->phpTagEcho . '$this->includeFirst(' . $expression . '); ?>'; + } + + /** + * Compile the block statements into the content. + * + * @param string $expression + * @return string + */ + protected function compileBlock($expression) + { + return $this->phpTagEcho . "\$this->contentPushContent{$expression}; ?>"; + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compilePush($expression) + { + return $this->phpTag . "\$this->startPush{$expression}; ?>"; + } + + /** + * Compile the v-block statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compileVblock ($expression) + { + return $this->compilePush($expression); + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compilePushOnce($expression) + { + $key = '$__pushonce__' . \trim(\substr($expression, 2, -2)); + return $this->phpTag . "if(!isset($key)): $key=1; \$this->startPush{$expression}; ?>"; + } + + /** + * Compile the push statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compilePrepend($expression) + { + return $this->phpTag . "\$this->startPush{$expression}; ?>"; + } + + /** + * Compile the p-block statements into valid PHP. + * + * @param string $expression + * @return string + */ + public function compilePblock($expression) + { + return $this->compilePrepend($expression); + } + + /** + * Compile the endpush statements into valid PHP. + * + * @return string + */ + protected function compileEndpush() + { + return $this->phpTag . '$this->stopPush(); ?>'; + } + + /** + * Compile the end-v-block statements into valid PHP. + * + * @return string + */ + protected function compileEndVblock() + { + return $this->compileEndpush(); + } + + /** + * Compile the endpushonce statements into valid PHP. + * + * @return string + */ + protected function compileEndpushOnce() + { + return $this->phpTag . '$this->stopPush(); endif; ?>'; + } + + /** + * Compile the endpush statements into valid PHP. + * + * @return string + */ + protected function compileEndPrepend() + { + return $this->phpTag . '$this->stopPrepend(); ?>'; + } + + /** + * Compile the end-p-block statements into valid PHP. + * + * @return string + */ + protected function compileEndpblock() + { + return $this->compileEndPrepend(); + } + + /** + * Compile the has section statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileHasSection($expression) + { + return $this->phpTag . "if (! empty(trim(\$this->contentContent{$expression}))): ?>"; + } + + /** + * Compile the overwrite statements into valid PHP. + * + * @return string + */ + protected function compileOverwrite() + { + return $this->phpTag . '$this->stopSection(true); ?>'; + } + +} + diff --git a/src/Views/View/Resources/Temp/ec2d4d07e0c46ad15817a7cf8eeef0171a0e06e2.htmlfinal b/src/Views/View/Resources/Temp/ec2d4d07e0c46ad15817a7cf8eeef0171a0e06e2.htmlfinal new file mode 100644 index 0000000..746b061 --- /dev/null +++ b/src/Views/View/Resources/Temp/ec2d4d07e0c46ad15817a7cf8eeef0171a0e06e2.htmlfinal @@ -0,0 +1,107 @@ + + + Hello + + + + + + +
    +
    /home
    +
    /about
    + +
    /contact
    +
    /help
    +
    + +
    Content loading...
    + + + + \ No newline at end of file diff --git a/src/Views/View/StatementsTrait.php b/src/Views/View/StatementsTrait.php new file mode 100644 index 0000000..ac20c6c --- /dev/null +++ b/src/Views/View/StatementsTrait.php @@ -0,0 +1,223 @@ +switchCount++; + $this->firstCaseInSwitch = true; + return $this->phpTag . "switch $expression {"; + } + + /** + * Execute the case tag. + * + * @param $expression + * @return string + */ + protected function compileCase($expression) + { + if ($this->firstCaseInSwitch) { + $this->firstCaseInSwitch = false; + return 'case ' . $expression . ': ?>'; + } + return $this->phpTag . "case $expression: ?>"; + } + + /** + * Compile the while statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileWhile($expression) + { + return $this->phpTag . "while{$expression}: ?>"; + } + + /** + * default tag used for switch/case + * + * @return string + */ + protected function compileDefault() + { + if ($this->firstCaseInSwitch) { + return $this->showError('@default', '@switch without any @case', true); + } + return $this->phpTag . 'default: ?>'; + } + + protected function compileEndSwitch() + { + --$this->switchCount; + if ($this->switchCount < 0) { + return $this->showError('@endswitch', 'Missing @switch', true); + } + return $this->phpTag . '} // end switch ?>'; + } + + /** + * Compile the else statements into valid PHP. + * + * @return string + */ + protected function compileElse() + { + return $this->phpTag . 'else: ?>'; + } + + /** + * Compile the for statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileFor($expression) + { + return $this->phpTag . "for{$expression}: ?>"; + } + + /** + * Compile the foreach statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileForeach($expression) + { + //\preg_match('/\( *(.*) * as *([^\)]*)/', $expression, $matches); + \preg_match('/\( *(.*) * as *([^)]*)/', $expression, $matches); + $iteratee = \trim($matches[1]); + $iteration = \trim($matches[2]); + $initLoop = "\$__currentLoopData = {$iteratee}; \$this->addLoop(\$__currentLoopData);\$this->getFirstLoop();\n"; + $iterateLoop = '$loop = $this->incrementLoopIndices(); '; + return $this->phpTag . "{$initLoop} foreach(\$__currentLoopData as {$iteration}): {$iterateLoop} ?>"; + } + + /** + * Compile the break statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileBreak($expression) + { + return $expression ? $this->phpTag . "if{$expression} break; ?>" : $this->phpTag . 'break; ?>'; + } + + /** + * Compile the continue statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileContinue($expression) + { + return $expression ? $this->phpTag . "if{$expression} continue; ?>" : $this->phpTag . 'continue; ?>'; + } + + /** + * Compile the forelse statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileForelse($expression) + { + $empty = '$__empty_' . ++$this->forelseCounter; + return $this->phpTag . "{$empty} = true; foreach{$expression}: {$empty} = false; ?>"; + } + + /** + * Compile the if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileIf($expression) + { + return $this->phpTag . "if{$expression}: ?>"; + } + + /** + * Compile the else-if statements into valid PHP. + * + * @param string $expression + * @return string + */ + protected function compileElseif($expression) + { + return $this->phpTag . "elseif{$expression}: ?>"; + } + + /** + * Compile the end-while statements into valid PHP. + * + * @return string + */ + protected function compileEndwhile() + { + return $this->phpTag . 'endwhile; ?>'; + } + + /** + * Compile the end-for statements into valid PHP. + * + * @return string + */ + protected function compileEndfor() + { + return $this->phpTag . 'endfor; ?>'; + } + + /** + * Compile the end-for-each statements into valid PHP. + * + * @return string + */ + protected function compileEndforeach() + { + return $this->phpTag . 'endforeach; $this->popLoop(); $loop = $this->getFirstLoop(); ?>'; + } + + /** + * Compile the end-if statements into valid PHP. + * + * @return string + */ + protected function compileEndif() + { + return $this->phpTag . 'endif; ?>'; + } + + /** + * Compile the end-for-else statements into valid PHP. + * + * @return string + */ + protected function compileEndforelse() + { + return $this->phpTag . 'endif; ?>'; + } + +} +