diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..837857a --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.php] +indent_style = space diff --git a/.gitignore b/.gitignore index 3738c60..9c979e4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,12 @@ cache/ /vendor/ composer.lock -node_modules +/node_modules/ +/logs/* +!/logs/.gitkeep +/src/Modules/* +!/src/Modules/ModuleName/ +/tmp/ # OS Related (used: https://gitignore.io/) diff --git a/.phpcs.xml b/.phpcs.xml index 39e33e8..714e87a 100644 --- a/.phpcs.xml +++ b/.phpcs.xml @@ -1,6 +1,6 @@ - - The PSR-2 coding standard. - - - + + The PSR-12 coding standard. + + src + Modules diff --git a/composer.json b/composer.json index efec081..b187857 100644 --- a/composer.json +++ b/composer.json @@ -22,10 +22,14 @@ ] }, "require": { - "twig/twig": "^2.7" + "twig/twig": "^2.7", + "psr/simple-cache": "^1.0", + "psr/log": "^1.1" }, "require-dev": { "squizlabs/php_codesniffer": "^3.4", - "symfony/var-dumper": "^4.2" + "symfony/var-dumper": "^4.2", + "phpunit/phpunit": "^8.1", + "codacy/coverage": "^1.4" } } diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..26f4d9e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,39 @@ + + + + src/AdminPanel + + + + + tests/ + + + + + + diff --git a/src/AdminPanel/Cache/CacheException.php b/src/AdminPanel/Cache/CacheException.php new file mode 100644 index 0000000..4eb5858 --- /dev/null +++ b/src/AdminPanel/Cache/CacheException.php @@ -0,0 +1,9 @@ +y * 365 + $ttl->m * 30 + $ttl->d + ) * 24 + $ttl->h + ) * 60 + $ttl->i + ) * 60 + $ttl->s; + } + } + + /** + * Cache Constructor + * + * @param string $folder + * @param integer|\DateInterval $ttl + */ + public function __construct(string $folder = "./cache", $ttl = 86400) + { + $this->folder = $folder; + if (!file_exists($this->folder)) { + mkdir($this->folder, 0777, true); + } + $this->ttl = $this->getTTl($ttl); + } + + public function get($key, $default = null) + { + if (!$this->checkKey($key)) { + throw new InvalidArgumentException("key is not correct"); + } + $file = $this->folder . DIRECTORY_SEPARATOR . $key; + if (is_file($file)) { + $res = unserialize(file_get_contents($file)); + if ($res["ttl"] > time() && $res['value'] !== null) { + return $res["value"]; + } else { + $this->delete($key); + } + } + return $default; + } + + public function set($key, $value, $ttl = null) + { + if (!$this->checkKey($key)) { + throw new InvalidArgumentException("key is not valid"); + } + $tl = $ttl != null ? $this->getTTL($ttl) : $this->ttl; + $arr = array( + "value" => $value, + 'ttl' => time() + $tl + ); + return file_put_contents($this->folder . DIRECTORY_SEPARATOR . $key, serialize($arr)) ? true : false; + } + + public function delete($key) + { + if (!$this->checkKey($key)) { + throw new InvalidArgumentException("key is not valid"); + } + return unlink($this->folder . DIRECTORY_SEPARATOR . $key); + } + + public function clear() + { + $keys = array_diff(scandir($this->folder), array("..", ".")); + foreach ($keys as $key) { + $this->delete($key); + } + } + + public function getMultiple($keys, $default = null) + { + if (!is_iterable($keys)) { + throw new InvalidArgumentException('$keys isn\'t traversable'); + } + $result = array(); + foreach ($keys as $key) { + if (!$this->checkKey($key)) { + throw new InvalidArgumentException("a key in the array is invalid"); + } + $result[$key] = $this->get($key, $default); + } + return $result; + } + + public function setMultiple($values, $ttl = null) + { + if (!is_iterable($values)) { + throw new InvalidArgumentException('$values isn\'t traversable'); + } + foreach ($values as $key => $value) { + $tmp = $this->set($key, $value, $ttl); + if (!$tmp) { + return false; + } + } + return true; + } + + public function deleteMultiple($keys) + { + foreach ($keys as $key) { + $this->delete($key); + } + } + + public function has($key) + { + return is_file($this->folder . DIRECTORY_SEPARATOR . $key); + } +} diff --git a/src/AdminPanel/Cache/InvalidArgumentException.php b/src/AdminPanel/Cache/InvalidArgumentException.php new file mode 100644 index 0000000..43538af --- /dev/null +++ b/src/AdminPanel/Cache/InvalidArgumentException.php @@ -0,0 +1,7 @@ + + * @since 1.0.0 + */ + namespace AdminPanel\Classes; use Twig\Loader\FilesystemLoader; use Twig\Environment; +use AdminPanel\Cache\FileCache; +use AdminPanel\Logger\Logger; class AdminPanel { /** @var AdminPanel $instance */ private static $instance = null; + + /** + * Undocumented function + * + * @return AdminPanel + */ public static function getInstance() { if (!isset(AdminPanel::$instance)) { + define("ROOT", dirname(dirname(__DIR__))); AdminPanel::$instance = new self(); - Cache::getInstance()->addTemplateFolder("/AdminPanel/Twig"); AdminPanel::$instance->addLoaderFolder(ROOT . "/AdminPanel/Twig"); } return AdminPanel::$instance; } - private $loader; - public function getLoader() + /** @var Logger $logger */ + private $logger; + public function setLogger(Logger $logger) { - return $this->loader; + $this->logger = $logger; } - public function addLoaderFolder(String $path, String $prefix = "AdminPanel") + public function getLogger(): Logger + { + return $this->logger; + } + + /** @var \Twig\Loader\FileSystemLoader $loader */ + private $loader; + + public function addLoaderFolder(string $path, string $prefix = "AdminPanel") { $this->loader->addPath($path, $prefix); } @@ -33,7 +55,15 @@ class AdminPanel private $twig; public function getTwig() { - return isset($this->twig) ? $this->twig : $this->twig = new Environment($this->loader); + return isset($this->twig) ? $this->twig : $this->twig = new Environment($this->loader, [ + 'cache' => false //dirname(ROOT) . '/cache/twig/' + ]); + } + + private $cache; + public function getCache() + { + return $this->cache ? $this->cache : $this->cache = new FileCache(dirname(ROOT) . "/cache/fs"); } public function __construct() diff --git a/src/AdminPanel/Classes/Cache.php b/src/AdminPanel/Classes/Cache.php deleted file mode 100644 index eb6b299..0000000 --- a/src/AdminPanel/Classes/Cache.php +++ /dev/null @@ -1,48 +0,0 @@ -cache = AdminPanel::getInstance()->getCache(); + } + public function setUrlArguments($args) { $this->urlArguments = $args; @@ -33,5 +39,21 @@ class Controller return $this->moduleRoot; } -//TODO implements functions and variables to add functionnalities to controllers + protected function getHTTPGet(string $element, $default = null, $emptyAllowed = false) + { + return isset($_GET[$element]) && (!empty($_GET[$element]) || $emptyAllowed) ? $_GET[$element] : $default; + } + + protected function getHTTPPost(string $element, $default = null, $emptyAllowed = false) + { + return isset($_POST[$element]) && (!empty($_POST[$element]) || $emptyAllowed) ? $_POST[$element] : $default; + } + + protected function render($template, $args) + { + return AdminPanel::getInstance()->getTwig()->render($template, $args); + } + + +//TODO: implements functions and variables to add functionnalities to controllers } diff --git a/src/AdminPanel/Functions.php b/src/AdminPanel/Functions.php index 161b261..04cdea8 100644 --- a/src/AdminPanel/Functions.php +++ b/src/AdminPanel/Functions.php @@ -33,14 +33,19 @@ function slugEqualToURI($slug, $uri, $options) foreach ($slug as $key => $value) { if (preg_match("/{.+}/", $value)) { $elemnt = preg_replace("/{|}/", "", $value); + // dd($options); + if (!isset($options->$elemnt)) { + $return[$elemnt] = explode("?", $uri[$key])[0]; + continue; + } $elOptions = $options->$elemnt; - if ($elOptions->regex != null && preg_match($elOptions->regex, $uri[$key])) { - $return[$elemnt] = $uri[$key]; + if (!isset($elOptions->regex) || ($elOptions->regex != null && preg_match($elOptions->regex, $uri[$key]))) { + $return[$elemnt] = explode("?", $uri[$key])[0]; continue; } else { return false; } - //TODO correspond with module settings + //TODO: correspond with module settings } else { if ($value == $uri[$key]) { continue; @@ -76,24 +81,15 @@ function initCache() $json = getModulesJSON(); foreach ($json as $moduleName => $moduleValues) { if (isset($moduleValues["routes"])) { - //TODO + //TODO: } if (isset($moduleValues["templateFolder"])) { - Cache::getInstance()->addTemplateFolder(ROOT . "/Modules/" . $moduleName . $moduleValues["templateFolder"], $moduleName); + Cache::getInstance()->addTemplateFolder( + "/Modules/" . $moduleName . $moduleValues["templateFolder"], + $moduleName + ); } // return $moduleValues; } return $json; } - -/** - * Define constant. - * (well really it's just an hack for my linter I really don't why we can't define constant in the main process) - * - * @param string $name constant name - * @param mixed $value contant value - */ -function dconst(string $name, $value) -{ - define($name, $value); -} diff --git a/src/AdminPanel/Logger/Logger.php b/src/AdminPanel/Logger/Logger.php new file mode 100644 index 0000000..b07ef67 --- /dev/null +++ b/src/AdminPanel/Logger/Logger.php @@ -0,0 +1,65 @@ +file = $file; + } + + public static function fillMessage($message, $context) + { + foreach ($context as $key => $value) { + $message = preg_replace("/\{$key\}/", $value, $message); + } + return $message; + } + + public function log($level, $message = null, array $context = array()) + { + + if (!in_array($level, $this->levels)) { + throw new InvalidArgumentException('Level not supported'); + } + $context["logger_logger"] = ucfirst($level); + $context["logger_time"] = (new \DateTime())->format("Y-m-d H:i:s"); + if ($message != null) { + file_put_contents($this->file, $this->fillMessage($this->start . $message . "\n", $context), FILE_APPEND); + } + if (isset($context["exception"]) && $context["exception"] instanceof \Exception) { + $context['code'] = $context["exception"]->getCode(); + $context['file'] = $context["exception"]->getFile(); + $context['line'] = get_class($context["exception"]); + $context['type'] = $context["exception"]->getCode(); + $context['message'] = $context["exception"]->getMessage(); + file_put_contents($this->file, $this->fillMessage($this->start . $this->exception, $context), FILE_APPEND); + } + if ($message === null && !isset($context["exception"])) { + throw new InvalidArgumentException("no exception nor message was found"); + } + } +} diff --git a/src/index.php b/src/index.php index 4d80e41..a09274f 100644 --- a/src/index.php +++ b/src/index.php @@ -1,63 +1,95 @@ getCache(); +$caches = $cache->getMultiple(array( + 'routes', + 'templates' +)); - - -/** @var string $module */ -foreach ($modules as $module) { - $moduleDIR = $modulesDIR . "/" . $module; - if (is_dir($moduleDIR)) { - $json = json_decode(file_get_contents($moduleDIR . "/" . strtolower($module) . ".json")); - foreach ($json->routes as $routeName => $routeArgs) { - $args = isset($routeArgs->args) ? $routeArgs->args : new stdClass(); - $composants = slugEqualToURI($routeArgs->path, $_SERVER["REQUEST_URI"], $args); - // dump($composants !== false); - if ($composants !== false) { - if (isset($json->templateFolder)) { - AdminPanel::getInstance()->addLoaderFolder($moduleDIR . $json->templateFolder, $module); - } - if (isset($routeArgs->file)) { - if (isset($routeArgs->type)) { - header("Content-Type: " . $routeArgs->type . "; charset=UTF-8"); - } - echo file_get_contents($moduleDIR . $routeArgs->file); - die; - } - $loader->loadClass($routeArgs->controller); - $function = $routeArgs->function; - // dump($function); - /** @var AdminPanel\Classes\Controller $controller */ - $controller = new $routeArgs->controller; - $controller->setUrlArguments($composants); - $controller->setModuleRoot($moduleDIR); - // if(isset($json->templateFolder)) $controller->loadTwig($json->templateFolder); - echo $controller->$function(); - die; +//if cache don't exist create it! +if ($caches["routes"] === null || $caches['templates'] === null) { + $modulesDIR = __DIR__ . "/Modules"; + $modules = array_diff(scandir($modulesDIR), array('..', '.')); + /** @var string $module */ + foreach ($modules as $module) { + $moduleDIR = $modulesDIR . "/" . $module; + if (is_dir($moduleDIR) && is_file($moduleDIR . "/" . strtolower($module) . ".json")) { + $json = json_decode(file_get_contents($moduleDIR . "/" . strtolower($module) . ".json")); + if (isset($json->templateFolder)) { + $cache->set( + 'templates', + array_merge($cache->get('templates', array()), array( + $module => $moduleDIR . $json->templateFolder + )) + ); + } + foreach ($json->routes as $routeName => $routeArgs) { + $cache->set('routes', array_merge( + $cache->get('routes', array()), + array( + $routeName => $routeArgs + ) + )); } } } + $caches = $cache->getMultiple(array( + 'routes', + 'templates' + )); } +//load each templates +foreach ($caches['templates'] as $key => $value) { + $ap->addLoaderFolder($value, $key); +} +foreach ($caches['routes'] as $key => $value) { + $args = isset($value->args) ? $value->args : new stdClass(); + $composants = slugEqualToURI($value->path, $_SERVER["REQUEST_URI"], $args); + // dump($composants !== false); + if ($composants !== false) { + if (isset($value->file)) { + if (isset($value->type)) { + header("Content-Type: " . $value->type . "; charset=UTF-8"); + } + echo file_get_contents($moduleDIR . $value->file); + die; + } + $loader->loadClass($value->controller); + $function = $value->function; + // dump($function); + /** @var AdminPanel\Classes\Controller $controller */ + $controller = new $value->controller(); + // dd(new $routeArgs->controller()); + if ($composants) { + $controller->setUrlArguments($composants); + } + // if(isset($json->templateFolder)) $controller->loadTwig($json->templateFolder); + echo $controller->$function(); + die; + } +} + + + http_response_code(404); // dd(); diff --git a/tests/LoggerTest.php b/tests/LoggerTest.php new file mode 100644 index 0000000..4e78139 --- /dev/null +++ b/tests/LoggerTest.php @@ -0,0 +1,22 @@ +assertFileExists($file); + + $logger->info("test"); + $this->assertStringEqualsFile( + $file, + "[Info]: " . (new \DateTime())->format("Y-m-d H:i:s") . " test\n" + ); + unlink($file); + } +}