From 19bf47d7999f5c5700532cd7b060848a9b2b9baa Mon Sep 17 00:00:00 2001 From: Ilya Bakhlin Date: Thu, 30 Dec 2021 11:47:59 +0100 Subject: [PATCH] Removing the event system from the framework as nobody uses it. Closes #8. --- .gitattributes | 6 ++ .gitignore | 16 ++-- chandler/Bootstrap.php | 4 +- .../ControlPanel/includes/verify_user.php | 6 +- chandler/Eventing/EventDispatcher.php | 49 ------------ chandler/Eventing/Events/Cancelable.php | 15 ---- chandler/Eventing/Events/Event.php | 74 ------------------ chandler/Extensions/ExtensionManager.php | 54 +++++++------ chandler/MVC/Routing/Router.php | 8 +- chandler/MVC/SimplePresenter.php | 78 +++++++++---------- chandler/Patterns/TSimpleSingleton.php | 2 +- chandler/Security/Authenticator.php | 52 ++++++------- composer.json | 8 +- install/nginx.conf | 6 +- phpunit.xml | 2 + public/index.php | 4 + .../Chandler/Eventing/EventDispatcherTest.php | 71 ----------------- tests/Chandler/Eventing/Events/EventTest.php | 46 ----------- 18 files changed, 124 insertions(+), 377 deletions(-) delete mode 100644 chandler/Eventing/EventDispatcher.php delete mode 100644 chandler/Eventing/Events/Cancelable.php delete mode 100644 chandler/Eventing/Events/Event.php delete mode 100644 tests/Chandler/Eventing/EventDispatcherTest.php delete mode 100644 tests/Chandler/Eventing/Events/EventTest.php diff --git a/.gitattributes b/.gitattributes index 0af7b94..cb37b47 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,12 @@ .gitattributes text eol=lf export-ignore .gitignore text eol=lf export-ignore +# PHP +*.php text eol=lf + # JSON *.json text eol=lf *.lock text eol=lf + +# YAML +*.yml text eol=lf diff --git a/.gitignore b/.gitignore index 1c71a1b..f39dca2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ +# Chandler +/extensions/available/ +/extensions/enabled/ +/chandler.yml + # Composer /vendor/ /composer.phar @@ -8,13 +13,6 @@ # PHPUnit /tests/cache/ -/chandler.yml - -/extensions/available/* -/extensions/enabled/* -!/extensions/available/.gitkeep -!/extensions/enabled/.gitkeep - /tmp/cache/di_* /tmp/plugin_artifacts/* /tmp/cache/database/* @@ -25,9 +23,5 @@ !/tmp/cache/templates/.gitkeep !/tmp/cache/yaml/.gitkeep -/htdocs/* -!/htdocs/.htaccess -!/htdocs/index.php - /logs/* !/logs/.gitkeep diff --git a/chandler/Bootstrap.php b/chandler/Bootstrap.php index e5361c9..e405792 100644 --- a/chandler/Bootstrap.php +++ b/chandler/Bootstrap.php @@ -42,7 +42,7 @@ class Bootstrap */ private function igniteExtensions(): void { - Chandler\Extensions\ExtensionManager::i(); + Chandler\Extensions\ExtensionManager::getInstance(); } private function loadConfig(): void @@ -103,7 +103,7 @@ class Bootstrap private function route(string $url): void { ob_start(); - $router = Chandler\MVC\Routing\Router::i(); + $router = Chandler\MVC\Routing\Router::getInstance(); if (($output = $router->execute($url, null)) !== null) echo $output; else diff --git a/chandler/ControlPanel/includes/verify_user.php b/chandler/ControlPanel/includes/verify_user.php index 445b8ee..b244373 100644 --- a/chandler/ControlPanel/includes/verify_user.php +++ b/chandler/ControlPanel/includes/verify_user.php @@ -3,9 +3,9 @@ use Chandler\Security\Authenticator; return (function(): ?bool { - $auth = Authenticator::i(); + $auth = Authenticator::getInstance(); $user = $auth->getUser(); if(!$user) return NULL; - + return $user->can("access")->model("admin")->whichBelongsTo(NULL); -}); \ No newline at end of file +}); diff --git a/chandler/Eventing/EventDispatcher.php b/chandler/Eventing/EventDispatcher.php deleted file mode 100644 index b00211a..0000000 --- a/chandler/Eventing/EventDispatcher.php +++ /dev/null @@ -1,49 +0,0 @@ -hooks[] = $hook; - return true; - } - - /** - * @param \Chandler\Eventing\Events\Event $event - * - * @return \Chandler\Eventing\Events\Event - */ - function pushEvent(Events\Event $event): Events\Event - { - foreach ($this->hooks as $hook) { - if ($event instanceof Events\Cancelable) - if ($event->isCancelled()) - break; - $method = "on" . str_replace("Event", "", get_class($event)); - if (!method_exists($hook, $method)) continue; - $hook->$method($event); - } - return $event; - } - - use TSimpleSingleton; -} diff --git a/chandler/Eventing/Events/Cancelable.php b/chandler/Eventing/Events/Cancelable.php deleted file mode 100644 index ebc16a2..0000000 --- a/chandler/Eventing/Events/Cancelable.php +++ /dev/null @@ -1,15 +0,0 @@ -code; - } - - /** - * @return string - */ - public function getData(): string - { - return $this->data; - } - - /** - * @return int - */ - public function getTime(): int - { - return $this->time; - } - - /** - * @return bool - */ - public function isTainted(): bool - { - return !$this->pristine; - } - - /** - * @param string $data - * @param float $code - */ - public function __construct(string $data = "", float $code = 0.0) - { - $this->data = $data; - $this->code = $code; - $this->time = time(); - } -} diff --git a/chandler/Extensions/ExtensionManager.php b/chandler/Extensions/ExtensionManager.php index f7dbfd0..e9e396e 100644 --- a/chandler/Extensions/ExtensionManager.php +++ b/chandler/Extensions/ExtensionManager.php @@ -1,6 +1,5 @@ in(CHANDLER_EXTENSIONS_AVAILABLE) as $directory) { $extensionName = $directory->getFilename(); $directory = $directory->getRealPath(); $config = "$directory/manifest.yml"; - + if(!file_exists($config)) { trigger_error("Skipping $extensionName for not having a valid configuration file ($config is not found)", E_USER_WARNING); continue; } - + $this->extensions[$extensionName] = (object) chandler_parse_yaml($config); $this->extensions[$extensionName]->id = $extensionName; $this->extensions[$extensionName]->rawName = $directory; $this->extensions[$extensionName]->enabled = CHANDLER_ROOT_CONF["extensions"]["allEnabled"]; } - + if(!CHANDLER_ROOT_CONF["extensions"]["allEnabled"]) { foreach(Finder::find("*")->in(CHANDLER_EXTENSIONS_ENABLED) as $directory) { #findDirectories doesn't work with symlinks if(!is_dir($directory->getRealPath())) continue; - + $extension = $directory->getFilename(); - + if(!array_key_exists($extension, $this->extensions)) { trigger_error("Extension $extension is enabled, but not available, skipping", E_USER_WARNING); continue; } - + $this->extensions[$extension]->enabled = true; } } - + if(!array_key_exists(CHANDLER_ROOT_CONF["rootApp"], $this->extensions) || !$this->extensions[CHANDLER_ROOT_CONF["rootApp"]]->enabled) { trigger_error("Selected root app is not available", E_USER_ERROR); } - + $this->rootApp = CHANDLER_ROOT_CONF["rootApp"]; - $this->eventLoop = EventDispatcher::i(); - $this->router = Router::i(); - + $this->router = Router::getInstance(); + $this->init(); } - + private function init(): void { foreach($this->getExtensions(true) as $name => $configuration) { @@ -73,60 +71,60 @@ class ExtensionManager include_once CHANDLER_EXTENSIONS_ENABLED . \"/\" . str_replace(\"\\\\\", \"/\", \$class) . \".php\"; ")); - + define(str_replace("-", "_", mb_strtoupper($name)) . "_ROOT", CHANDLER_EXTENSIONS_ENABLED . "/$name", false); define(str_replace("-", "_", mb_strtoupper($name)) . "_ROOT_CONF", chandler_parse_yaml(CHANDLER_EXTENSIONS_ENABLED . "/$name/$name.yml"), false); - + if(isset($configuration->init)) { $init = require(CHANDLER_EXTENSIONS_ENABLED . "/$name/" . $configuration->init); if(is_callable($init)) $init(); } - + if(is_dir($hooks = CHANDLER_EXTENSIONS_ENABLED . "/$name/Hooks")) { foreach(Finder::findFiles("*Hook.php")->in($hooks) as $hookFile) { $hookClassName = "$name\\Hooks\\" . str_replace(".php", "", end(explode("/", $hookFile))); $hook = new $hookClassName; - + $this->eventLoop->addListener($hook); } } - + if(is_dir($app = CHANDLER_EXTENSIONS_ENABLED . "/$name/Web")) #"app" means "web app", thus variable is called $app $this->router->readRoutes("$app/routes.yml", $name, $this->rootApp !== $name); } } - + function getExtensions(bool $onlyEnabled = false): array { return $onlyEnabled ? array_filter($this->extensions, function($e) { return $e->enabled; }) : $this->extensions; } - + function getExtension(string $name): ?object { return @$this->extensions[$name]; } - + function disableExtension(string $name): void { if(!array_key_exists($name, $this->getExtensions(true))) return; - + if(!unlink(CHANDLER_EXTENSIONS_ENABLED . "/$name")) throw new \Exception("Could not disable extension"); } - + function enableExtension(string $name): void { if(CHANDLER_ROOT_CONF["extensions"]["allEnabled"]) return; - + if(array_key_exists($name, $this->getExtensions(true))) return; - + $path = CHANDLER_EXTENSIONS_AVAILABLE . "/$name"; if(!is_dir($path)) throw new \Exception("Extension doesn't exist"); - + if(!symlink($path, str_replace("available", "enabled", $path))) throw new \Exception("Could not enable extension"); } - + use TSimpleSingleton; } diff --git a/chandler/MVC/Routing/Router.php b/chandler/MVC/Routing/Router.php index b98f89c..23b7ecc 100644 --- a/chandler/MVC/Routing/Router.php +++ b/chandler/MVC/Routing/Router.php @@ -4,7 +4,6 @@ declare(strict_types = 1); namespace Chandler\MVC\Routing; -use Chandler\Eventing\EventDispatcher; use Chandler\MVC\Exceptions\InterruptedException; use Chandler\MVC\IPresenter; use Chandler\Patterns\TSimpleSingleton; @@ -199,7 +198,7 @@ class Router { $key = hash("snefru", CHANDLER_ROOT_CONF["security"]["secret"] . bin2hex($nonce)); $data = $route->namespace; - $data .= Session::i()->get("tok", -1); + $data .= Session::getInstance()->get("tok", -1); return hash_hmac("snefru", $data, $key) . "#" . bin2hex($nonce); } @@ -257,10 +256,5 @@ class Router return null; } - private function __construct() - { - $this->events = EventDispatcher::i(); - } - use TSimpleSingleton; } diff --git a/chandler/MVC/SimplePresenter.php b/chandler/MVC/SimplePresenter.php index 065b9ce..8299571 100644 --- a/chandler/MVC/SimplePresenter.php +++ b/chandler/MVC/SimplePresenter.php @@ -10,22 +10,22 @@ abstract class SimplePresenter implements IPresenter const REDIRECT_TEMPORARY = 2; const REDIRECT_PERMAMENT_PRESISTENT = 8; const REDIRECT_TEMPORARY_PRESISTENT = 7; - + protected $mmReader; protected $template; protected $errorTemplate = NULL; - + function __construct() { $this->template = (object) []; } - + function getTemplatingEngine(): TemplatingEngine { $latte = new TemplatingEngine; $macros = new \Latte\Macros\MacroSet($latte->getCompiler()); $latte->setTempDirectory(CHANDLER_ROOT . "/tmp/cache/templates"); - + $macros->addMacro("css", ' $domain = "' . explode("\\", static::class)[0] . '"; $file = (%node.array)[0]; @@ -62,18 +62,18 @@ abstract class SimplePresenter implements IPresenter echo ""; ' ); - + return $latte; } - + protected function throwError(int $code = 400, string $desc = "Bad Request", string $message = ""): void { if(!is_null($this->errorTemplate)) { header("HTTP/1.0 $code $desc"); - + $ext = explode("\\", get_class($this))[0]; $path = CHANDLER_EXTENSIONS_ENABLED . "/$ext/Web/Presenters/templates/" . $this->errorTemplate . ".xml"; - + $latte = $this->getTemplatingEngine(); $latte->render($path, array_merge_recursive([ "code" => $code, @@ -85,18 +85,18 @@ abstract class SimplePresenter implements IPresenter chandler_http_panic($code, $desc, $message); } } - + protected function assertNoCSRF(): void { if(!$GLOBALS["csrfCheck"]) $this->throwError(400, "Bad Request", "CSRF token is missing or invalid."); } - + protected function terminate(): void { throw new Exceptions\InterruptedException; } - + protected function notFound(): void { $this->throwError( @@ -105,51 +105,51 @@ abstract class SimplePresenter implements IPresenter "The resource you are looking for has been deleted, had its name changed or doesn't exist." ); } - + protected function getCaller(): string { return $GLOBALS["parentModule"] ?? "libchandler:absolute.0"; } - + protected function redirect(string $location, int $code = 2): void { $code = 300 + $code; if(($code <=> 300) !== 0 && $code > 399) return; - + header("HTTP/1.1 $code"); header("Location: $location"); exit; } - + protected function pass(string $to, ...$args): void { $args = array_merge([$to], $args); - $router = \Chandler\MVC\Routing\Router::i(); + $router = \Chandler\MVC\Routing\Router::getInstance(); $__out = $router->execute($router->reverse(...$args), "libchandler:absolute.0"); exit($__out); } - + protected function sendmail(string $to, string $template, array $params): void { $emailDir = pathinfo($template, PATHINFO_DIRNAME); $template .= ".eml.latte"; - + $renderedHTML = (new TemplatingEngine)->renderToString($template, $params); $document = new \DOMDocument(); $document->loadHTML($renderedHTML, LIBXML_NOEMPTYTAG); $querySel = new \DOMXPath($document); - + $subject = $querySel->query("//title/text()")->item(0)->data; - + foreach($querySel->query("//link[@rel='stylesheet']") as $link) { $style = $document->createElement("style"); $style->setAttribute("id", uniqid("mail", true)); $style->appendChild(new \DOMText(file_get_contents("$emailDir/assets/css/" . $link->getAttribute("href")))); - + $link->parentNode->appendChild($style); $link->parentNode->removeChild($link); } - + foreach($querySel->query("//img") as $image) { $imagePath = "$emailDir/assets/res/" . $image->getAttribute("src"); $type = pathinfo($imagePath, PATHINFO_EXTENSION); @@ -157,36 +157,36 @@ abstract class SimplePresenter implements IPresenter $image->setAttribute("src", "data:image/$type;base64,$contents"); } - + \Chandler\Email\Email::send($to, $subject, $document->saveHTML()); } - + protected function queryParam(string $index): ?string { return $_GET[$index] ?? NULL; } - + protected function postParam(string $index, bool $csrfCheck = true): ?string { if($csrfCheck) $this->assertNoCSRF(); - + return $_POST[$index] ?? NULL; } - + protected function requestParam(string $index): ?string { return $_REQUEST[$index] ?? NULL; } - + protected function jsonParam(string $index): ?string { if(!isset($GLOBALS["jsonInputCache"])) $GLOBALS["jsonInputCache"] = json_decode($this->queryParam("js") ?? file_get_contents("php://input"), true); - + return $GLOBALS["jsonInputCache"][$index] ?? NULL; } - + protected function findParam(string $index, array $searchIn = ["json", "post", "query"]): ?string { $res = NULL; @@ -198,38 +198,38 @@ abstract class SimplePresenter implements IPresenter else if($search === "query") $res = $this->queryParam($index) ?? $res; } - + return $res; } - + protected function checkbox(string $name): bool { return ($this->postParam($name) ?? "off") === "on"; } - + function getTemplateScope(): array { return (array) $this->template; } - + function onStartup(): void { date_default_timezone_set("UTC"); } - + function onBeforeRender(): void { $this->template->csrfToken = $GLOBALS["csrfToken"]; } - + function onAfterRender(): void {} - + function onStop(): void {} - + function onDestruction(): void {} - + use SmartObject; } diff --git a/chandler/Patterns/TSimpleSingleton.php b/chandler/Patterns/TSimpleSingleton.php index c006746..c14672d 100644 --- a/chandler/Patterns/TSimpleSingleton.php +++ b/chandler/Patterns/TSimpleSingleton.php @@ -17,7 +17,7 @@ trait TSimpleSingleton /** * @return static */ - public static function i(): self + public static function getInstance(): self { if (is_null(static::$instance)) { return static::$instance = new static(); diff --git a/chandler/Security/Authenticator.php b/chandler/Security/Authenticator.php index aefd758..aec876c 100644 --- a/chandler/Security/Authenticator.php +++ b/chandler/Security/Authenticator.php @@ -8,18 +8,18 @@ class Authenticator { private $db; private $session; - + private function __construct() { $this->db = DatabaseConnection::i()->getContext(); - $this->session = Session::i(); + $this->session = Session::getInstance(); } - + private function verifySuRights(string $uId): bool { - + } - + private function makeToken(string $user, string $ip, string $ua): string { $data = ["user" => $user, "ip" => $ip, "ua" => $ua]; @@ -27,15 +27,15 @@ class Authenticator ->table("ChandlerTokens") ->where($data) ->fetch(); - + if(!$token) { $this->db->table("ChandlerTokens")->insert($data); $token = $this->db->table("ChandlerTokens")->where($data)->fetch(); } - + return $token->token; } - + static function verifyHash(string $input, string $hash): bool { try { @@ -54,46 +54,46 @@ class Authenticator } catch(\SodiumException $ex) { return false; } - + return true; } - + function getUser(): ?User { $token = $this->session->get("tok"); if(!$token) return null; - + $token = $this->db ->table("ChandlerTokens") ->where([ "token" => $token, ]) ->fetch(); - + if(!$token) return null; - + $checksPassed = false; if(CHANDLER_ROOT_CONF["security"]["extendedValidation"]) $checksPassed = $token->ip === CONNECTING_IP && $token->ua === $_SERVER["HTTP_USER_AGENT"]; else $checksPassed = true; - + if($checksPassed) { $su = $this->session->get("_su"); $user = $this->db->table("ChandlerUsers")->get($su ?? $token->user); if(!$user) return null; - + return new User($user, !is_null($su)); } - + return null; } - + function authenticate(string $user): void { $this->session->set("tok", $this->makeToken($user, CONNECTING_IP, $_SERVER["HTTP_USER_AGENT"])); } - + function verifyCredentials(string $id, string $password): bool { $user = $this->db->table("ChandlerUsers")->get($id); @@ -101,30 +101,30 @@ class Authenticator return false; else if(!$this->verifyHash($password, $user->passwordHash)) return false; - + return true; } - + function login(string $id, string $password): bool { if(!$this->verifyCredentials($id, $password)) return false; - + $this->authenticate($id); return true; } - + function logout(bool $revoke = false): bool { $token = $this->session->get("tok"); if(!$token) return false; - + if($revoke) $this->db->table("ChandlerTokens")->where("id", $token)->delete(); - + $this->session->set("tok", NULL); - + return true; } - + use TSimpleSingleton; } diff --git a/composer.json b/composer.json index ebadf82..5dfcab8 100644 --- a/composer.json +++ b/composer.json @@ -1,8 +1,11 @@ { "autoload": { "classmap": [ - "chandler/Eventing/Events/Event.php" - ] + "chandler/Patterns/TSimpleSingleton.php" + ], + "psr-4": { + "Chandler\\": "src" + } }, "autoload-dev": { "psr-4": { @@ -14,6 +17,7 @@ "minimum-stability": "stable", "name": "openvk/chanlder", "require": { + "ext-yaml": "*", "php": "^7.3", "nette/utils": "^3.0", "nette/di": "^3.0", diff --git a/install/nginx.conf b/install/nginx.conf index 99ba4cd..c4d582e 100644 --- a/install/nginx.conf +++ b/install/nginx.conf @@ -14,10 +14,10 @@ server { listen [::]:443 ssl http2; server_name domain.tld; - root /opt/chandler/htdocs; - + root /opt/chandler/public; + client_max_body_size 100m; - + index index.php; ssl_certificate /path/to/fullchain.pem; diff --git a/phpunit.xml b/phpunit.xml index 4410f54..5dcef63 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -3,6 +3,8 @@ chandler/Eventing/Events/Event.php + chandler/Eventing/EventDispatcher.php + chandler/Patterns/TSimpleSingleton.php diff --git a/public/index.php b/public/index.php index 88f0323..7d44b56 100644 --- a/public/index.php +++ b/public/index.php @@ -1,5 +1,9 @@ ignite(); diff --git a/tests/Chandler/Eventing/EventDispatcherTest.php b/tests/Chandler/Eventing/EventDispatcherTest.php deleted file mode 100644 index c37e1ac..0000000 --- a/tests/Chandler/Eventing/EventDispatcherTest.php +++ /dev/null @@ -1,71 +0,0 @@ -assertTrue(EventDispatcher::i()->addListener($hook)); - } - - /** - * @return void - */ - public function testMethodI(): void - { - $this->assertSame(EventDispatcher::i(), EventDispatcher::i()); - } - - /** - * @dataProvider provideMethodPushEvent - * - * @param \Chandler\Eventing\Events\Event $event - * - * @return void - */ - public function testMethodPushEvent(Event $event): void - { - $this->assertSame($event, EventDispatcher::i()->pushEvent($event)); - } -} diff --git a/tests/Chandler/Eventing/Events/EventTest.php b/tests/Chandler/Eventing/Events/EventTest.php deleted file mode 100644 index 56a6e74..0000000 --- a/tests/Chandler/Eventing/Events/EventTest.php +++ /dev/null @@ -1,46 +0,0 @@ -assertClassHasAttribute("code", Event::class); - } - - /** - * @return void - */ - public function testPropertyData(): void - { - $this->assertClassHasAttribute("data", Event::class); - } - - /** - * @return void - */ - public function testPropertyPristine(): void - { - $this->assertClassHasAttribute("pristine", Event::class); - } - - /** - * @return void - */ - public function testPropertyTime(): void - { - $this->assertClassHasAttribute("time", Event::class); - } -}