diff options
Diffstat (limited to 'src/web/core')
-rw-r--r-- | src/web/core/_controller.php | 51 | ||||
-rw-r--r-- | src/web/core/_model.php | 50 | ||||
-rw-r--r-- | src/web/core/loader.php | 111 | ||||
-rw-r--r-- | src/web/core/router.php | 248 |
4 files changed, 460 insertions, 0 deletions
diff --git a/src/web/core/_controller.php b/src/web/core/_controller.php new file mode 100644 index 0000000..0dbb5b8 --- /dev/null +++ b/src/web/core/_controller.php @@ -0,0 +1,51 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +abstract class Controller { + + // the main model + public $main; + + // the loader + public $load; + + /** + * Creates a constructor + * @param Loader $load - the website loaded object + */ + function __construct($load) { + $this->load = $load; + $this->main = $this->load->model('main'); + + $this->load->lang(); + $info = $this->main->info; + $app = $info['app']; + if ($app) { + $this->load->app_lang($app); + } + } + + public function index() {} + + public function redirect($link) { + header('Location: '. $link, true, 301); + die(); + } + + protected function view($__name, $data = array()) { + $__root = $GLOBALS['webroot']; + $__path = $__root . '/_views/' . $__name . '.php'; + if (is_file($__path)) { + extract($data); + require($__path); + return; + } + } + + protected function error($code): void { + $_GET['code'] = $code; + $this->main->info['app'] = 'error'; + $error_controller = $this->load->controller('error'); + $error_controller->index(); + die(); + } + +} diff --git a/src/web/core/_model.php b/src/web/core/_model.php new file mode 100644 index 0000000..4c27b1b --- /dev/null +++ b/src/web/core/_model.php @@ -0,0 +1,50 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +abstract class Model { + // the main model + // shared by all controllers and models + public $main; + public $load; + + // the database + public $db; + + private $config; + + /** + * Creates a model + * @param Loader $load - the main loader object + */ + function __construct($load, $main = FALSE) { + $this->load = $load; + if ($main) { + $this->main = $this; + } else { + $this->main = $this->load->model('main'); + } + $this->db = $this->load->db(); + } + + /** + * @returns the base model data + */ + public function get_data(): ?array { + $data = array(); + + $info = $this->main->info; + $app = $info['app']; + + $data['title'] = lang('first_name'); + $data['desc'] = lang('default_short_desc'); + $data['css'] = array(); + + $style = $GLOBALS['style']; + if (isset($style[$app])) { + $css = $style[$app]; + if (!is_array($css)) + $css = array($css); + $data['css'] = $css; + } + + return $data; + } +} diff --git a/src/web/core/loader.php b/src/web/core/loader.php new file mode 100644 index 0000000..b0a1cbd --- /dev/null +++ b/src/web/core/loader.php @@ -0,0 +1,111 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Loader { + + // keep track of what has been loaded + private $loaded; + + // the database + private $db; + + function __construct() { + $this->loaded = array(); + } + + /** + * Loads a $type of object from a $dir with a given $name + * @param string $name - the name of the object to load + * @param string $dir - the directory theese objects are stored in + * @param string $type - the type of the object + */ + private function load_type($name, $dir, $type): object|NULL { + $path = $dir . '/' . $name . '.php'; + if (array_key_exists($path, $this->loaded)) { + return $this->loaded[$path]; + } + + if (!file_exists($path)) { + return NULL; + } + + $parts = explode('/', $name); + $part = end($parts); + $class = ucfirst($part) . '_' . $type; + require($path); + + $ref = NULL; + try { + $ref = new ReflectionClass($class); + } catch (Exception $_e) {} + + if ($ref === NULL) { + return NULL; + } + + $obj = $ref->newInstance($this); + $this->loaded[$path] = $obj; + + return $obj; + } + + /** + * Loads a model + * @param string $name - the name of the model to load + */ + public function model($name): object|NULL { + $root = $GLOBALS['webroot']; + $dir = $root . '/_model'; + return $this->load_type($name, $dir, 'model'); + } + + /** + * Loads a controller + * @param string $name - the name of the controller to load + */ + public function controller($name): Controller|NULL { + $root = $GLOBALS['webroot']; + $dir = $root . '/_controller'; + return $this->load_type($name, $dir, 'controller'); + } + + /** + * Loads the given common lang + */ + public function lang(): void { + $dir = $GLOBALS['webroot'] . '/lang/'; + $lang = $GLOBALS['lang']; + if ($handle = opendir($dir)) { + while (false !== ($entry = readdir($handle))) { + if ($entry === '.' || $entry === '..' || $entry === 'apps') { + continue; + } + $path = $dir . $entry; + require($path); + } + } + $GLOBALS['lang'] = $lang; + } + + /** + * Loads a given app specific lang + * @param string $name - the name of the app + */ + public function app_lang($name): void { + $dir = $GLOBALS['webroot'] . '/lang/apps/'; + $file = $dir . $name . '.php'; + if (file_exists($file)) { + $lang = $GLOBALS['lang']; + require($dir . $name . '.php'); + $GLOBALS['lang'] = $lang; + } + } + + public function db() { + if ($this->db) { + return $this->db; + } else { + $this->db = new DatabaseHelper(); + return $this->db; + } + } + +} diff --git a/src/web/core/router.php b/src/web/core/router.php new file mode 100644 index 0000000..c8fb142 --- /dev/null +++ b/src/web/core/router.php @@ -0,0 +1,248 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Router { + + // the loader + private $load; + + // the main model + private $main; + + // the database + private $db; + + private $db_ready; + + /** + * Creates a router + * @param Loader $load - the main laoder object + */ + function __construct($load) { + $this->load = $load; + $this->db = $load->db(); + $this->main = $this->load->model('main'); + $this->db_ready = file_exists('/status/ready'); + } + + /** + * @param string $path - the current request path + * Gets the current route + * @return array<string,mixed> + */ + private function get_req_route($path): array { + // trim the path + $path = trim($path); + // remove first '/' + $path = substr($path, 1); + // get path parts + $parts = explode('/', $path); + + $len = count($parts); + + // get route info + $route = array(); + // e.g. / + if ($path === '') { + $route = array( + 'app' => '', + 'slug' => 'index', + ); + // e.g. /home /login + } else if ($len === 1) { + $route = array( + 'app' => $parts[0], + 'slug' => 'index', + ); + // e.g. /home/posts + } else { + $route = array ( + 'app' => implode('/', array_slice($parts, 0, -1)), + 'slug' => end($parts) + ); + }; + + $routes = $GLOBALS['routes']; + if (array_key_exists($route['app'], $routes)) { + $parts = explode('/', $routes[$route['app']]); + if (count($parts) == 1) { + $route['app'] = $parts[0]; + } else { + $route['app'] = $parts[0]; + $route['slug'] = $parts[1]; + } + } + + return $route; + } + + /** + * Gets the users ip + */ + private function get_ip(): string { + $ip = ''; + if (!empty($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } else { + $ip = $_SERVER['REMOTE_ADDR']; + } + return $ip; + } + + /** + * Gets the curret request info + * @return array<string,mixed> + */ + private function get_req(): array|bool { + $method = $_SERVER['REQUEST_METHOD']; + + $uri_str = $_SERVER['REQUEST_URI']; + $uri = parse_url($uri_str); + if (!$uri) { + return FALSE; + } + + $path = ''; + if (array_key_exists('path', $uri)) { + $path = $uri['path']; + } + + return array_merge( + array( + 'uri' => $uri, + 'uri_str' => $uri_str, + 'method' => $method, + 'ip' => $this->get_ip() + ), + $this->get_req_route($path), + ); + } + + /** + * Handles a router error code + * @param int $code - the http error code + * @param bool $recursed + */ + private function handle_error($code, $recursed): void { + if ($recursed) { + die($code . ' (recursed)'); + } + $uri_str = $_SERVER['REQUEST_URI']; + $req = array(); + $req['slug'] = 'index'; + $req['app'] = 'error'; + $req['uri_str'] = $uri_str; + $this->main->info = $req; + $_GET['code'] = $code; + $this->handle_req($req, TRUE); + } + + private function load_htc($req, $recursed): void { + $parts = explode('/', $req['uri_str']); + $file = end($parts); + $path = $GLOBALS['publicroot'] . '/polyfills/' . $file; + + if (file_exists($path)) { + header('Content-type: text/x-component'); + include($path); + } else { + $this->handle_error(400, $recursed); + } + } + + /** + * @param array $req + * @param bool $recursed + */ + private function handle_req($req, $recursed = FALSE): void { + + if ($recursed === false) { + if ( + $this->db_ready === false && + in_array($req['app'], $GLOBALS['serviceable']) === false + ) { + $this->handle_error(503, $recursed); + return; + } + + if ($this->check_banned($req)) { + $this->handle_error(401, $recursed); + return; + } + } + + if (!$req) { + $this->handle_error(500, $recursed); + return; + } + + if (str_ends_with($req['uri_str'], '.htc')) { + $this->load_htc($req, $recursed); + return; + } + + $controller = $this->load->controller($req['app']); + + if ($controller === NULL) { + $this->handle_error(404, $recursed); + return; + } + + $ref = NULL; + try { + $ref = new ReflectionMethod($controller, $req['slug']); + } catch (Exception $_e) {} + + if ($ref === NULL || !$ref->isPublic()) { + $this->handle_error(404, $recursed); + return; + + } + + $ref->invoke($controller); + } + + private function log_request($req): void { + if ( + $req === FALSE || + $this->db_ready === FALSE || + in_array($req['app'], $GLOBALS['serviceable']) + ) { + return; + } + + $query = $this->db + ->insert_into('admin.request_log', + 'ip', 'method', 'uri') + ->values( + $req['ip'], $req['method'], $req['uri_str']); + + $query->execute(); + } + + private function check_banned($req) { + $ip = FALSE; + if ($req) { + $ip = $req['ip']; + } else { + $ip = $this->get_ip(); + } + $query = $this->db + ->select('TRUE') + ->from('admin.banned') + ->where('ip')->eq($ip); + + return !!($query->row()); + } + + /** + * Handels the incomming reuqest + */ + public function handle_request(): void { + $req = $this->get_req(); + $this->log_request($req); + $this->main->info = $req; + $this->handle_req($req); + } + +} |