diff options
Diffstat (limited to '')
47 files changed, 953 insertions, 482 deletions
diff --git a/web/_controller/_index.php b/web/_controller/_index.php new file mode 100644 index 0000000..fdf9440 --- /dev/null +++ b/web/_controller/_index.php @@ -0,0 +1,23 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class _index_controller extends Controller { + + // the home model + private $home_model; + + // the request model + private $request_model; + + // the caceh model + private $cache_model; + + public function index(): void { + if ($this->main->session) { + $this->redirect('/home'); + } else { + $this->redirect('/login'); + } + } + +} + +?> diff --git a/web/_controller/apps/error.php b/web/_controller/apps/error.php new file mode 100644 index 0000000..5ce9ec4 --- /dev/null +++ b/web/_controller/apps/error.php @@ -0,0 +1,20 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Error_controller extends Controller { + + private $error_model; + + function __construct($load) { + parent::__construct($load); + $this->error_model = $this->load->model('apps/error'); + } + + public function index() { + parent::index(); + $data = $this->error_model->get_data(); + $this->view('header', $data); + $this->view('apps/error/main', $data); + } + +} + +?> diff --git a/web/routes/home/controller.php b/web/_controller/apps/home.php index 775e43a..25c8c4e 100644 --- a/web/routes/home/controller.php +++ b/web/_controller/apps/home.php @@ -1,22 +1,31 @@ <?php /* Copyright (c) 2024 Freya Murphy */ -class HomeController extends Controller { +class Home_controller extends Controller { - private $model; + // the home model + private $home_model; - function __construct($model) { - parent::__construct(); - $this->model = $model; + // the request model + private $request_model; + + // the caceh model + private $cache_model; + + function __construct($load) { + parent::__construct($load); + $this->home_model = $this->load->model('apps/home'); + $this->request_model = $this->load->model('request'); + $this->cache_model = $this->load->model('cache'); } - public function index() { + public function index(): void { parent::index(); - $data = $this->model->get_data(); + $data = $this->home_model->get_data(); $this->view('header', $data); - $this->app_view('main', $data); + $this->view('apps/home/main', $data); } - public function posts() { - $page = $this->main->get_num('page', 0); + public function posts(): void { + $page = $this->request_model->get_int('page', 0); $page_size = 20; $offset = $page * $page_size; @@ -30,7 +39,7 @@ class HomeController extends Controller { $query = $query->select('p.*, FALSE as liked'); } - $query = $query->from('admin.post p'); + $query = $query->from('api.post p'); if ($user) { $query = $query->join('admin.like l', 'p.id = l.post_id') @@ -42,7 +51,7 @@ class HomeController extends Controller { ->offset($offset) ->rows(); - $users = $this->main->get_users($posts); + $users = $this->cache_model->get_users($posts); foreach ($posts as $post) { $data = array(); @@ -52,9 +61,9 @@ class HomeController extends Controller { } } - public function comments() { - $page = $this->main->get_num('page', 0); - $id = $this->main->get_num('id'); + public function comments(): void { + $page = $this->request_model->get_int('page', 0); + $id = $this->request_model->get_int('id'); $page_size = 20; $offset = $page * $page_size; @@ -65,7 +74,7 @@ class HomeController extends Controller { ->offset($offset) ->rows(); - $users = $this->main->get_users($comments); + $users = $this->cache_model->get_users($comments); foreach ($comments as $comment) { $data = array(); @@ -75,10 +84,6 @@ class HomeController extends Controller { } } - public function new_post_modal() { - $this->modal(lang('new_post_modal_title'), 'new-post'); - } - } ?> diff --git a/web/_controller/modal.php b/web/_controller/modal.php new file mode 100644 index 0000000..9ae4ca8 --- /dev/null +++ b/web/_controller/modal.php @@ -0,0 +1,26 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Modal_controller extends Controller { + + + function __construct($load) { + parent::__construct($load); + } + + /** + * @param string $name + * @param array $data + */ + private function modal($name, $data = array()): void { + $title = lang($name . '_modal_title'); + $data['title'] = $title; + $data['content'] = $name; + $this->view('template/modal', $data); + } + + public function new_post(): void { + $this->modal('new_post'); + } +} + +?> + diff --git a/web/_controller/template.php b/web/_controller/template.php new file mode 100644 index 0000000..7a8cdf8 --- /dev/null +++ b/web/_controller/template.php @@ -0,0 +1,22 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Template_controller extends Controller { + + // the request model + private $request_model; + + function __construct($load) { + parent::__construct($load); + $this->request_model = $this->load->model('request'); + } + + public function toast(): void { + $data = array( + 'msg' => $this->request_model->get_str('msg', FALSE), + 'detail' => $this->request_model->get_str('detail', FALSE), + 'hint' => $this->request_model->get_str('hint', FALSE) + ); + $this->view('template/toast', $data); + } + +} + diff --git a/web/routes/error/model.php b/web/_model/apps/error.php index a30fccc..ad72b28 100644 --- a/web/routes/error/model.php +++ b/web/_model/apps/error.php @@ -1,5 +1,9 @@ <?php /* Copyright (c) 2024 Freya Murphy */ -class ErrorModel extends Model { +class Error_model extends Model { + + function __construct($load) { + parent::__construct($load); + } private function get_msg(&$data) { if (!array_key_exists('code', $_GET)) { @@ -22,7 +26,7 @@ class ErrorModel extends Model { } } - public function get_data() { + public function get_data(): array { $data = parent::get_data(); $this->get_msg($data); return $data; diff --git a/web/routes/home/model.php b/web/_model/apps/home.php index 44a8549..82fbf26 100644 --- a/web/routes/home/model.php +++ b/web/_model/apps/home.php @@ -1,7 +1,11 @@ <?php /* Copyright (c) 2024 Freya Murphy */ -class HomeModel extends Model { +class Home_model extends Model { - private function get_posts() { + function __construct($load) { + parent::__construct($load); + } + + private function get_posts(): array { return $this->db ->select('*') ->from('admin.post') @@ -9,11 +13,10 @@ class HomeModel extends Model { ->rows(); } - public function get_data() { + public function get_data(): array { $data = parent::get_data(); $data['title'] = lang('title'); $data['posts'] = $this->get_posts(); return $data; } } -?> diff --git a/web/_model/cache.php b/web/_model/cache.php new file mode 100644 index 0000000..6cf9924 --- /dev/null +++ b/web/_model/cache.php @@ -0,0 +1,37 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Cache_model extends Model { + + // the user cache + private $users; + + function __construct($load) { + parent::__construct($load); + $this->users = array(); + } + + /** + * Gets a array of users + */ + public function get_users($objs) { + $ids = array(); + foreach ($objs as $obj) { + $id = $obj['user_id']; + if (!array_key_exists($id, $this->users)) { + array_push($ids, intval($id)); + } + } + if (!empty($ids)) { + $result = $this->main->db + ->select('*') + ->from('api.user') + ->where_in('id', $ids) + ->rows(); + foreach ($result as $user) { + $id = $user['id']; + $this->users[$id] = $user; + } + } + return $this->users; + } + +} diff --git a/web/_model/format.php b/web/_model/format.php new file mode 100644 index 0000000..d8c7480 --- /dev/null +++ b/web/_model/format.php @@ -0,0 +1,45 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Format_model extends Modal { + + function __construct($load) { + parent::__construct($load); + } + + /** + * Formats a users's name + * @param array $user - the $user + * @returns the user's formatted display name + */ + public function name($user) { + $name = ''; + if ($user['first_name']) { + $name .= $user['first_name']; + } + if ($user['middle_name']) { + if ($name != '') { + $name .= ' '; + } + $name .= $user['middle_name']; + } + if ($user['last_name']) { + if ($name != '') { + $name .= ' '; + } + $name .= $user['last_name']; + } + if ($name == '') { + $name = '@' . $user['username']; + } + return $name; + } + + /** + * Formats a date + * @param string $date - the data in RFC3999 format + * @returns the formatted date + */ + public function date($date) { + return $date; + } + +} diff --git a/web/_model/main.php b/web/_model/main.php new file mode 100644 index 0000000..f72a2f3 --- /dev/null +++ b/web/_model/main.php @@ -0,0 +1,84 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Main_model { + + // the website database + public $db; + + // the current user session (can be NULL) + public $session; + + // current loaded users + private $users; + + // stores the current request info + public $info; + + /** + * Loads the main model + * @param Loader $load - the main loader object + */ + function __construct($load) { + /// load the database helper + $this->db = new DatabaseHelper(); + /// load the current session + if (array_key_exists('jwt', $_SESSION)) { + $this->get_session($_SESSION['jwt']); + } else { + $this->session = NULL; + }; + /// init other vars + $this->users = array(); + } + + /** + * Loads current session + * @param string $jwt - the user provided JWT + */ + private function get_session($jwt) { + $query = $this->db + ->select("_api.verify_jwt('" . $jwt . "') AS user_id;"); + $result = $query->row(); + $user_id = $result['user_id']; + if ($user_id) { + $this->session = array( + 'id' => $user_id, + 'jwt' => $jwt + ); + } + } + + /** + * Loads a css html link + * @param string $path - the path to the css file + */ + public function link_css($path) { + return '<link rel="stylesheet" href="/public/' . $path . '">'; + } + + /** + * Loads a js html link + * @param string $path - the path to the js file + */ + public function link_js($path) { + return '<script src="/public/'. $path . '"></script>'; + } + + /** + * Gets the current user + */ + public function user() { + if ($this->session) { + return $this->db + ->select('*') + ->from('api.user') + ->where('id') + ->eq($this->session['id']) + ->row(); + } else { + return NULL; + } + } + +} + +?> diff --git a/web/_model/request.php b/web/_model/request.php new file mode 100644 index 0000000..4cce07a --- /dev/null +++ b/web/_model/request.php @@ -0,0 +1,40 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +class Request_model extends Model { + + function __construct($load) { + parent::__construct($load); + } + + /** + * Loads a string from the GET request + * @param string $key - the name for the query param + * @param string $default - the default value if not exists + */ + public function get_str($key, $default = NULL): string | NULL { + if (!array_key_exists($key, $_GET)) { + return $default; + } else { + return $_GET[$key]; + } + } + + /** + * Loads a number from the GET request + * @param string $key - the name for the query param + * @param int $default - the default value if not exists + */ + public function get_int($key, $default = NULL): int | NULL { + if (!array_key_exists($key, $_GET)) { + return $default; + } else { + $val = $_GET[$key]; + $val = intval($val); + if ($val < 0) { + return 0; + } else { + return $val; + } + } + } + +} diff --git a/web/core/aesthetic.php b/web/config/aesthetic.php index 1180ad1..ed79653 100644 --- a/web/core/aesthetic.php +++ b/web/config/aesthetic.php @@ -9,7 +9,7 @@ class Aesthetic { 'js' => [ 'js/jquery-3.7.1.min.js', 'js/lib.js', - 'js/modal.js', + 'js/shared/modal.js', ], 'css' => [ 'css/common.css' @@ -22,7 +22,8 @@ class Aesthetic { ), 'home' => array( 'js' => [ - 'js/post.js', + 'js/shared/post.js', + 'js/routes/home.js', ], 'css' => [ 'css/home.css', diff --git a/web/config/routes.php b/web/config/routes.php new file mode 100644 index 0000000..78df332 --- /dev/null +++ b/web/config/routes.php @@ -0,0 +1,7 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ + +$routes = array(); +$routes['home'] = 'apps/home'; +$routes['error'] = 'apps/error'; + +$routes[''] = '_index'; diff --git a/web/core/_controller.php b/web/core/_controller.php new file mode 100644 index 0000000..8a467d6 --- /dev/null +++ b/web/core/_controller.php @@ -0,0 +1,49 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +abstract class Controller { + + // the main model + public $main; + + // the loader + public $load; + + // the database + public $db; + + /** + * Creates a constructor + * @param Loader $load - the website loaded object + */ + function __construct($load) { + $this->load = $load; + $this->main = $this->load->model('main'); + $this->db = $this->main->db; + + $info = $this->main->info; + $lang = $info['lang']; + $this->load->lang($lang); + $app = $info['app']; + if ($app) { + $this->load->app_lang($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; + } + } + +} +?> diff --git a/web/core/_model.php b/web/core/_model.php new file mode 100644 index 0000000..936fab4 --- /dev/null +++ b/web/core/_model.php @@ -0,0 +1,44 @@ +<?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) { + $this->load = $load; + $this->main = $this->load->model('main'); + $this->db = $this->main->db; + $this->config = new Aesthetic(); + } + + /** + * @returns the base model data + */ + public function get_data(): array { + $data = array(); + $data['self'] = $this->main->user(); + + $info = $this->main->info; + $app = $info['app']; + + if ($app) { + $files = $this->config->get_files($app); + $data = array_merge($data, $files); + } else { + $files = $this->config->get_files(); + $data = array_merge($data, $files); + } + + return $data; + } +} diff --git a/web/core/controller.php b/web/core/controller.php deleted file mode 100644 index 946b460..0000000 --- a/web/core/controller.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php /* Copyright (c) 2024 Freya Murphy */ -abstract class Controller { - - // the main model - public $main; - - // the loader - public $load; - - // the database - public $db; - - function __construct() { - $this->main = $GLOBALS['__vars']['main']; - $this->load = $GLOBALS['__vars']['load']; - $this->db = $this->main->db; - - $info = $this->main->info; - $lang_code = $info['lang']; - $route_name = $info['route']; - $this->load->lang($lang_code); - $this->load->route_lang($lang_code, $route_name); - } - - public function index() {} - - 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 app_view($__name, $data = array()) { - $__root = $GLOBALS['webroot']; - $__route = $this->main->info['route']; - $__path = $__root . '/routes/' . $__route . '/views/' . $__name . '.php'; - if (is_file($__path)) { - extract($data); - require($__path); - return; - } - } - - protected function modal($title, $content, $data = array()) { - $data['title'] = $title; - $data['content'] = $content; - $this->view('template/modal', $data); - } - -} -?> diff --git a/web/core/database.php b/web/core/database.php index b3a597b..4b44e86 100644 --- a/web/core/database.php +++ b/web/core/database.php @@ -170,4 +170,3 @@ class DatabaseHelper { } } -?> diff --git a/web/core/loader.php b/web/core/loader.php index 4d4526c..2091533 100644 --- a/web/core/loader.php +++ b/web/core/loader.php @@ -1,16 +1,79 @@ <?php /* Copyright (c) 2024 Freya Murphy */ class Loader { + // keep track of what has been loaded + private $loaded; + + 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 - * @param lang_code - the language code + * @param string $lang_code 0 the language code */ - public function lang($lang_code) { + public function lang($lang_code): void { $dir = $GLOBALS['webroot'] . '/lang/' . $lang_code . '/'; $lang = $GLOBALS['lang']; if ($handle = opendir($dir)) { while (false !== ($entry = readdir($handle))) { - if ($entry === '.' || $entry === '..' || $entry === 'routes') { + if ($entry === '.' || $entry === '..' || $entry === 'apps') { continue; } $path = $dir . $entry; @@ -21,12 +84,12 @@ class Loader { } /** - * Loads a given route specific lang - * @param lang_coed - the language code - * #param name - the name of the route + * Loads a given app specific lang + * @param string $lang_code - the language code + * @param string $name - the name of the app */ - public function route_lang($lang_code, $name) { - $dir = $GLOBALS['webroot'] . '/lang/' . $lang_code . '/routes/'; + public function app_lang($lang_code, $name): void { + $dir = $GLOBALS['webroot'] . '/lang/' . $lang_code . '/apps/'; $file = $dir . $name . '.php'; if (file_exists($file)) { $lang = $GLOBALS['lang']; diff --git a/web/core/main.php b/web/core/main.php deleted file mode 100644 index c3c65dd..0000000 --- a/web/core/main.php +++ /dev/null @@ -1,123 +0,0 @@ -<?php /* Copyright (c) 2024 Freya Murphy */ -class MainModel { - - // loaded route infomation - public $info; - public $db; - public $user_id; - - private $users; - - function __construct() { - $this->info = NULL; - $this->db = new DatabaseHelper(); - $this->users = array(); - $_SESSION['jwt'] = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicmVzdF91c2VyIiwidXNlcl9pZCI6MSwiZXhwIjoxNzExODUxMDUzfQ.FUcFO44SWV--YtVOy7NftTF8OeeOYGZDaDHigygQxsY'; - if (array_key_exists('jwt', $_SESSION)) { - $this->get_session($_SESSION['jwt']); - } else { - $this->user_id = NULL; - }; - } - - private function get_session($jwt) { - $query = $this->db - ->select("_api.verify_jwt('" . $jwt . "') AS user_id;"); - $result = $query->row(); - $user_id = $result['user_id']; - if ($user_id) { - $this->user_id = $user_id; - } - } - - public function link_css($path) { - return '<link rel="stylesheet" href="/public/' . $path . '">'; - } - - public function link_js($path) { - return '<script src="/public/'. $path . '"></script>'; - } - - public function user() { - if ($this->user_id) { - return $this->db - ->select('*') - ->from('api.user') - ->where('id') - ->eq($this->user_id) - ->row(); - } else { - return NULL; - } - } - - public function get_num($key, $default = NULL) { - if (!array_key_exists($key, $_GET)) { - if ($default !== NULL) { - return $default; - } else { - error_page(400, lang('error_400')); - } - } else { - $val = $_GET[$key]; - $val = intval($val); - if ($val < 0) { - return 0; - } else { - return $val; - } - } - } - - public function get_users($objs) { - $ids = array(); - foreach ($objs as $obj) { - $id = $obj['user_id']; - if (!array_key_exists($id, $this->users)) { - array_push($ids, intval($id)); - } - } - if (!empty($ids)) { - $result = $this->db - ->select('*') - ->from('api.user') - ->where_in('id', $ids) - ->rows(); - foreach ($result as $user) { - $id = $user['id']; - $this->users[$id] = $user; - } - } - return $this->users; - } - - public function display_name($user) { - $name = ''; - if ($user['first_name']) { - $name .= $user['first_name']; - } - if ($user['middle_name']) { - if ($name != '') { - $name .= ' '; - } - $name .= $user['middle_name']; - } - if ($user['last_name']) { - if ($name != '') { - $name .= ' '; - } - $name .= $user['last_name']; - } - if ($name == '') { - $name = '@' . $user['username']; - } - return $name; - } - - public function display_date($date) { - return $date; - } - -} - -?> diff --git a/web/core/model.php b/web/core/model.php deleted file mode 100644 index 039b138..0000000 --- a/web/core/model.php +++ /dev/null @@ -1,29 +0,0 @@ -<?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; - - function __construct() { - $this->main = $GLOBALS['__vars']['main']; - $this->load = $GLOBALS['__vars']['load']; - $this->db = $this->main->db; - $this->config = new Aesthetic(); - } - - public function get_data() { - $data = array(); - $route = $this->main->info['route']; - $files = $this->config->get_files($route); - $data = array_merge($data, $files); - $data['self'] = $this->main->user(); - return $data; - } -} -?> diff --git a/web/core/router.php b/web/core/router.php index 6ee28a9..72c7674 100644 --- a/web/core/router.php +++ b/web/core/router.php @@ -1,127 +1,147 @@ <?php /* Copyright (c) 2024 Freya Murphy */ class Router { - private $main; + // the loader private $load; - private $routes; - - function load_route($route) { - $name = $route['name']; - $controller_cls = $route['controller']; - $model_cls = $route['model']; - - $root = $GLOBALS['webroot']; - $dir = $root . '/routes/' . $name; - require($dir . '/model.php'); - require($dir . '/controller.php'); - - $model_ref = new ReflectionClass($model_cls); - $model = $model_ref->newInstance(); - $controller_ref = new ReflectionClass($controller_cls); - $controller = $controller_ref->newInstance($model); + // the main model + private $main; - return $controller; + /** + * Creates a router + * @param Loader $load - the main laoder object + */ + function __construct($load) { + $this->load = $load; + $this->main = $this->load->model('main'); } - function __construct($main, $load) { + /** + * @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); - $routes = array( - 'home' => array( - 'slugs' => ['', 'home'], - 'model' => 'HomeModel', - 'controller' => 'HomeController', - ), - ); + $len = count($parts); - $this->routes = array(); - foreach ($routes as $name => $route) { - foreach ($route['slugs'] as $slug) { - $this->routes[$slug] = $route; - $this->routes[$slug]['name'] = $name; - } + // get route info + $route = array(); + // e.g. / + if ($path === '') { + $route = array( + 'route' => '', + 'slug' => 'index', + ); + // e.g. /home /login + } else if ($len === 1) { + $route = array( + 'route' => $parts[0], + 'slug' => 'index', + ); + // e.g. /home/posts + } else { + $route = array ( + 'route' => implode('/', array_slice($parts, 0, -1)), + 'slug' => end($parts) + ); + }; + + $route['app'] = $route['route']; + $routes = $GLOBALS['routes']; + if (array_key_exists($route['route'], $routes)) { + $route['route'] = $routes[$route['route']]; } - $this->main = $main; - $this->load = $load; + return $route; } - function get_info() { - $uri = parse_url($_SERVER['REQUEST_URI']); + /** + * Gets the curret request info + * @return array<string,mixed> + */ + private function get_req(): array { $method = $_SERVER['REQUEST_METHOD']; - $parts = explode('/', $uri['path']); - - $slug = sizeof($parts) > 1 ? $parts[1] : ''; - $path = sizeof($parts) > 2 ? $parts[2] : 'index'; - if (sizeof($parts) > 3) { - return NULL; - } - - return array( - 'method' => $method, - 'uri' => $uri, + $uri = parse_url($_SERVER['REQUEST_URI']); + $path = $uri['path']; - 'slug' => $slug, - 'path' => $path + return array_merge( + array( + 'uri' => $uri, + 'method' => $method, + 'lang' => $this->get_lang(), + ), + $this->get_req_route($path), ); } - function handle_error($code) { - $route = array( - 'name' => 'error', - 'model' => 'ErrorModel', - 'controller' => 'ErrorController' - ); - $this->main->info = array( - 'slug' => 'error', - 'lang' => 'en_US', - 'route' => 'error' - ); - $controller = $this->load_route($route); - $_GET['code'] = $code; - http_response_code($code); - $controller->index(); + /** + * Gets the current language + * @return string + */ + private function get_lang(): string { + return 'en_US'; } - public function handle_request() { - $request = $this->get_info(); - - if ($request === NULL) { - $this->handle_error(404); - return; + /** + * 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)'); } - $slug = $request['slug']; - if (!array_key_exists($slug, $this->routes)) { - $this->handle_error(404); - return; - } + $this->main->info['slug'] = 'index'; + $this->main->info['app'] = 'error'; + $this->main->info['route'] = 'apps/error'; + $req = $this->main->info; + $_GET['code'] = $code; - $route = $this->routes[$slug]; - $this->main->info = array( - 'lang' => 'en_US', - 'slug' => $slug, - 'route' => $route['name'], - ); + $this->handle_req($req, TRUE); + } - $controller = $this->load_route($route); + /** + * @param array $req + * @param bool $recursed + */ + private function handle_req($req, $recursed = FALSE): void { + $controller = $this->load->controller($req['route']); - $path = $request['path']; - $ref = NULL; + if ($controller === NULL) { + $this->handle_error(404, $recursed); + return; + } + $ref = NULL; try { - $ref = new ReflectionMethod($controller, $path); + $ref = new ReflectionMethod($controller, $req['slug']); } catch (Exception $_e) {} if ($ref === NULL || !$ref->isPublic()) { - $this->handle_error(404); + $this->handle_error(404, $recursed); return; } $ref->invoke($controller); + } + /** + * Handels the incomming reuqest + */ + public function handle_request(): void { + $req = $this->get_req(); + $this->main->info = $req; + $this->handle_req($req); } } diff --git a/web/helper/error.php b/web/helper/error.php new file mode 100644 index 0000000..6fcaddd --- /dev/null +++ b/web/helper/error.php @@ -0,0 +1,9 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ + +function error_page($code, $msg) { + $root = $GLOBALS['webroot']; + error_reporting(E_ERROR | E_PARSE); + http_response_code($code); + require($root . '/views/template/error.php'); + die(); +} diff --git a/web/helper/lang.php b/web/helper/lang.php new file mode 100644 index 0000000..96944da --- /dev/null +++ b/web/helper/lang.php @@ -0,0 +1,77 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ +$lang = array(); + +function lang($key, $default = NULL, $sub = NULL) { + $lang = $GLOBALS['lang']; + if(array_key_exists($key, $lang)) { + if ($sub) { + return sprintf($lang[$key], ...$sub); + } else { + return $lang[$key]; + } + } else if ($default !== NULL) { + return $default; + } else { + return $key; + } +} + +function ilang($key, + $class = NULL, + $id = NULL, + $href = NULL, + $click = NULL, + $attrs = array(), + $sub = NULL, + $button = FALSE, +) { + $text = lang($key . "_text", FALSE, sub: $sub); + $tip = lang($key . "_tip", FALSE); + $icon = lang($key . "_icon", FALSE); + $content = lang($key . "_content", FALSE); + + if ($click || $button) { + echo '<button '; + } else { + echo '<a '; + } + if ($tip) { + echo 'title="' . $tip . '" '; + echo 'aria-label="' . $tip . '" '; + } + if ($class) { + echo 'class="' . $class . '" '; + } + if ($id) { + echo 'id="' . $id . '" '; + } + if ($click) { + echo 'onclick="' . $click . '" '; + } + if ($href) { + echo 'href="' . $href . '" '; + } + foreach ($attrs as $key => $attr) { + echo $key . '="' . $attr . '" '; + } + echo '> '; + if ($icon) { + echo '<i class="' . $icon . '">'; + if ($content) { + echo $content; + } + echo '</i>'; + } + if ($text) { + echo '<span'; + if ($icon) { + echo ' class="ml-sm"'; + } + echo '>' . $text . '</span>'; + } + if ($click) { + echo '</button>'; + } else { + echo '</a>'; + } +} diff --git a/web/index.php b/web/index.php index 1032b7f..9c2d239 100644 --- a/web/index.php +++ b/web/index.php @@ -2,114 +2,26 @@ session_start(); -$lang = array(); -$__vars = array(); $webroot = dirname(__FILE__); -function error_page($code, $msg) { - $root = $GLOBALS['webroot']; - error_reporting(E_ERROR | E_PARSE); - http_response_code($code); - require($root . '/core/error.php'); - die(); -} +// load all the helper files +require($webroot . '/helper/error.php'); +require($webroot . '/helper/lang.php'); -function lang($key, $default = NULL, $sub = NULL) { - $lang = $GLOBALS['lang']; - if(array_key_exists($key, $lang)) { - if ($sub) { - return sprintf($lang[$key], ...$sub); - } else { - return $lang[$key]; - } - } else if ($default !== NULL) { - return $default; - } else { - return $key; - } -} +// load all the config files +require($webroot . '/config/aesthetic.php'); +require($webroot . '/config/routes.php'); -function ilang($key, - $class = NULL, - $id = NULL, - $href = NULL, - $click = NULL, - $attrs = array(), - $sub = NULL, - $button = FALSE, -) { - $text = lang($key . "_text", FALSE, sub: $sub); - $tip = lang($key . "_tip", FALSE); - $icon = lang($key . "_icon", FALSE); - $content = lang($key . "_content", FALSE); - - if ($click || $button) { - echo '<button '; - } else { - echo '<a '; - } - if ($tip) { - echo 'title="' . $tip . '" '; - echo 'aria-label="' . $tip . '" '; - } - if ($class) { - echo 'class="' . $class . '" '; - } - if ($id) { - echo 'id="' . $id . '" '; - } - if ($click) { - echo 'onclick="' . $click . '" '; - } - if ($href) { - echo 'href="' . $href . '" '; - } - foreach ($attrs as $key => $attr) { - echo $key . '="' . $attr . '" '; - } - echo '> '; - if ($icon) { - echo '<i class="' . $icon . '">'; - if ($content) { - echo $content; - } - echo '</i>'; - } - if ($text) { - echo '<span'; - if ($icon) { - echo ' class="ml-sm"'; - } - echo '>' . $text . '</span>'; - } - if ($click) { - echo '</button>'; - } else { - echo '</a>'; - } -} +// load all core files +require($webroot . '/core/_controller.php'); +require($webroot . '/core/_model.php'); +require($webroot . '/core/database.php'); +require($webroot . '/core/loader.php'); +require($webroot . '/core/router.php'); function __init() { - - $root = $GLOBALS['webroot']; - - // load all core files - require($root . '/core/database.php'); - require($root . '/core/aesthetic.php'); - require($root . '/core/controller.php'); - require($root . '/core/model.php'); - require($root . '/core/loader.php'); - require($root . '/core/main.php'); - require($root . '/core/router.php'); - - $main = new MainModel(); $load = new Loader(); - $router = new Router($main, $load); - - $GLOBALS['__vars']['main'] = $main; - $GLOBALS['__vars']['load'] = $load; - $GLOBALS['__vars']['router'] = $router; - + $router = new Router($load); $router->handle_request(); }; @@ -118,5 +30,3 @@ if (!file_exists('/status/ready')) { } __init(); - -?> diff --git a/web/lang/en_US/api_lang.php b/web/lang/en_US/api_lang.php new file mode 100644 index 0000000..129147c --- /dev/null +++ b/web/lang/en_US/api_lang.php @@ -0,0 +1,26 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ + +// user column +$lang['api_column_username'] = 'username'; +$lang['api_column_first_name'] = 'first name'; +$lang['api_column_last_name'] = 'last name'; +$lang['api_column_middle_name'] = 'middle name'; +$lang['api_column_email'] = 'email'; +$lang['api_column_gender'] = 'gender'; +$lang['api_column_join_date'] = 'join date'; +$lang['api_column_birth_date'] = 'birth date'; +$lang['api_column_profile_avatar'] = 'avatar image'; +$lang['api_column_profile_banner'] = 'banner image'; +$lang['api_column_profile_bio'] = 'profile bio'; + +// post column +$lang['api_column_content'] = 'post content'; + +// error messages +$lang['api_denied'] = 'Action was denied'; +$lang['api_null_value'] = '%s cannot be empty'; +$lang['api_unique_value'] = '%s is not available (not unique)'; +$lang['api_min_value'] = '%s length cannot be less than %s'; +$lang['api_max_value'] = '%s length cannot exceed %s'; + +?> diff --git a/web/lang/en_US/apps/home.php b/web/lang/en_US/apps/home.php new file mode 100644 index 0000000..a30eb88 --- /dev/null +++ b/web/lang/en_US/apps/home.php @@ -0,0 +1,9 @@ +<?php + +$lang['title'] = 'Home'; + +// actions +$lang['action_load_posts_text'] = 'Load more posts'; +$lang['action_load_posts_tip'] = 'Load more posts'; + +?> diff --git a/web/lang/en_US/common_lang.php b/web/lang/en_US/common_lang.php index 3ce2fdb..7e214b5 100644 --- a/web/lang/en_US/common_lang.php +++ b/web/lang/en_US/common_lang.php @@ -1,4 +1,4 @@ -<?php +<?php /* Copyright (c) 2024 Freya Murphy */ // Navigation Bar Lang $lang['action_home_text'] = 'Home'; @@ -29,6 +29,10 @@ $lang['action_load_comments_tip'] = 'Load more comments'; // General $lang['action_submit_text'] = 'Submit'; $lang['action_submit_tip'] = 'Submit'; +$lang['action_close_text'] = ''; +$lang['action_close_tip'] = 'Close'; +$lang['action_close_icon'] = 'mi mi-sm'; +$lang['action_close_content'] = 'close'; // Modals $lang['action_modal_close_text'] = ''; @@ -36,6 +40,10 @@ $lang['action_modal_close_tip'] = 'Close modal.'; $lang['action_modal_close_icon'] = 'mi mi-sm'; $lang['action_modal_close_content'] = 'close'; +$lang['new_post_modal_title'] = 'Author New Post'; +$lang['action_new_post_text'] = 'What\'s on your mind, %s'; +$lang['action_new_post_tip'] = 'Author a new post.'; + // Words $lang['now'] = 'Now'; diff --git a/web/lang/en_US/error_lang.php b/web/lang/en_US/error_lang.php index 42ade65..afecaa1 100644 --- a/web/lang/en_US/error_lang.php +++ b/web/lang/en_US/error_lang.php @@ -1,4 +1,4 @@ -<?php +<?php /* Copyright (c) 2024 Freya Murphy */ $lang['error_400'] = 'Bad request'; $lang['error_404'] = 'Resource not found'; diff --git a/web/lang/en_US/routes/home.php b/web/lang/en_US/routes/home.php deleted file mode 100644 index 051eff2..0000000 --- a/web/lang/en_US/routes/home.php +++ /dev/null @@ -1,14 +0,0 @@ -<?php - -$lang['title'] = 'Home'; - -// actions -$lang['action_new_post_text'] = 'What\'s on your mind, %s'; -$lang['action_new_post_tip'] = 'Author a new post.'; -$lang['action_load_posts_text'] = 'Load more posts'; -$lang['action_load_posts_tip'] = 'Load more posts'; - -// modals -$lang['new_post_modal_title'] = 'Author New Post'; - -?> diff --git a/web/public/css/common.css b/web/public/css/common.css index 05f429f..84aabfc 100644 --- a/web/public/css/common.css +++ b/web/public/css/common.css @@ -52,6 +52,8 @@ body { } header { + top: 0; + position: sticky; height: 3.5rem; background-color: var(--primary); display: flex; @@ -145,6 +147,10 @@ input:focus { align-items: center; } +.nav { + position: sticky; +} + .nav-right { flex: 1; justify-content: flex-end; @@ -333,6 +339,30 @@ input:focus { border-radius: .5rem; display: flex; flex-direction: column; + animation: fadeIn .1s, slideInModal .1s linear; +} + +@keyframes slideInModal { + 0% { + animation-timing-function: ease-in; + transform: translate(-50%, -60%); + } +} + +@keyframes slideIn { + 0% { + animation-timing-function: ease-out; + transform: translate(0, -50%); + } +} + +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } } .modal>form { @@ -402,3 +432,30 @@ button[type="submit"]:hover { background-color: var(--logo); } +#toast-container { + position: fixed; + top: 4rem; + left: 100%; + transform: translateX(-110%); + margin-top: 1rem; + z-index: 10000; +} + +.toast { + padding: .75rem; + margin: .5rem; + border-radius: .5rem; + min-width: 15rem; + font-family: sfpro; + animation: fadeIn .1s, slideIn .25s linear; + display: flex; + justify-content: space-between; +} + +.toast.error { + background-color: var(--error); +} + +.toast.success { + background-color: var(--success); +} diff --git a/web/public/js/lib.js b/web/public/js/lib.js index d822b02..7647208 100644 --- a/web/public/js/lib.js +++ b/web/public/js/lib.js @@ -1,4 +1,8 @@ +/// +/// document ready functions +/// + let ready = false; $(function() { @@ -15,9 +19,14 @@ var r$ = function(callback) { } } +/// +/// dom observer +/// checks for elements on the DOM now and added later +/// + function observe(containerSelector, elementSelector, callback) { - r$(() => { + r$(() => { $(containerSelector + ' ' + elementSelector).each(function (_, e) { let me = $(e); callback(me); @@ -37,9 +46,64 @@ function observe(containerSelector, elementSelector, callback) { }; var target = $(containerSelector)[0]; + + if (!target) { + console.warn('[observe] didnt find container: ', containerSelector); + return; + } + var config = { childList: true, subtree: true }; var MutationObserver = window.MutationObserver; var observer = new MutationObserver(onMutationsObserved); observer.observe(target, config); }); } + +/// +/// ajax setup +/// + +let ajaxHeaders = {}; +ajaxHeaders['Content-Type'] = 'application/json'; +if (jwtStr) { + ajaxHeaders['Authorization'] = 'Bearer ' + jwtStr +} + +$.ajaxSetup({ + headers: ajaxHeaders +}) + +/// +/// ajax error handle +/// + +var errorToast = (xhr) => { + let data = xhr.responseJSON; + let msg = data.message; + let detail = data.details; + let hint = data.hint; + + let query = '?msg=' + msg; + if (detail) { + query += '&detail=' + detail; + } + if (hint) { + query += '&hint=' + hint; + } + let url = '/template/toast' + query; + $.get(url, function (data) { + $('#toast-container').prepend(data); + }) +} + +observe('#toast-container', '.action-close-toast', function(el) { + el.on('click', function() { + el.parent().remove(); + }); + + setTimeout(function() { + el.parent().remove(); + }, 5000); +}); + + diff --git a/web/core/helper.php b/web/public/js/routes/home.js index e69de29..e69de29 100644 --- a/web/core/helper.php +++ b/web/public/js/routes/home.js diff --git a/web/public/js/modal.js b/web/public/js/shared/modal.js index 792cd85..792cd85 100644 --- a/web/public/js/modal.js +++ b/web/public/js/shared/modal.js diff --git a/web/public/js/post.js b/web/public/js/shared/post.js index 736fa2b..afbeaf0 100644 --- a/web/public/js/post.js +++ b/web/public/js/shared/post.js @@ -1,4 +1,4 @@ -observe('.post', '.action-load-comments', function(me) { +observe('#main-content', '.action-load-comments', function(me) { me.on('click', function() { let page = me.attr('page'); if (!page) { diff --git a/web/routes/error/controller.php b/web/routes/error/controller.php deleted file mode 100644 index 3cb2345..0000000 --- a/web/routes/error/controller.php +++ /dev/null @@ -1,20 +0,0 @@ -<?php /* Copyright (c) 2024 Freya Murphy */ -class ErrorController extends Controller { - - private $model; - - function __construct($model) { - parent::__construct(); - $this->model = $model; - } - - public function index() { - parent::index(); - $data = $this->model->get_data(); - $this->view('header', $data); - $this->app_view('error', $data); - } - -} - -?> diff --git a/web/routes/error/views/error.php b/web/routes/error/views/error.php deleted file mode 100644 index 3b926bc..0000000 --- a/web/routes/error/views/error.php +++ /dev/null @@ -1,4 +0,0 @@ -<div id="error"> - <h1><?=$title?></h1> - <span><?=$msg?></span> -</div> diff --git a/web/views/apps/error/main.php b/web/views/apps/error/main.php new file mode 100644 index 0000000..81051bd --- /dev/null +++ b/web/views/apps/error/main.php @@ -0,0 +1,6 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> +<div id="error"> + <h1><?=$title?></h1> + <span><?=$msg?></span> +</div> diff --git a/web/routes/home/views/main.php b/web/views/apps/home/main.php index bf0087b..b1c1efc 100644 --- a/web/routes/home/views/main.php +++ b/web/views/apps/home/main.php @@ -1,4 +1,5 @@ -<?php // vi: syntax=php ?> +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <div id="main-content"> <?php if ($self): ?> <div id="new-post" class="card"> @@ -15,7 +16,7 @@ </div> <script> $('#action-new-post').on('click', function() { - $.get( "/home/new_post_modal", function (data) { + $.get( "/modal/new_post", function (data) { $(document.body).append(data); }); }) diff --git a/web/views/footer.php b/web/views/footer.php index 6cbe21b..1266b9a 100644 --- a/web/views/footer.php +++ b/web/views/footer.php @@ -1,2 +1,4 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <body> </html> diff --git a/web/views/header.php b/web/views/header.php index 183f4f4..891e27e 100644 --- a/web/views/header.php +++ b/web/views/header.php @@ -1,10 +1,18 @@ -<?php // vi: syntax=php ?> +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <?php $self = $this->main->user(); ?> <!DOCTYPE html> <html> <head> + <script> + <?php if ($this->main->session): ?> + var jwtStr = <?=json_encode($this->main->session['jwt'])?>; + <?php else: ?> + var jwtStr = null; + <?php endif; ?> + </script> <?php foreach ($js_files as $js) { echo $this->main->link_js($js); @@ -71,4 +79,6 @@ menu.toggleClass('visible'); }); </script> - </header> + </header> + <div id="toast-container"> + </div> diff --git a/web/views/modal/new-post.php b/web/views/modal/new_post.php index 7215862..82243fb 100644 --- a/web/views/modal/new-post.php +++ b/web/views/modal/new_post.php @@ -1,7 +1,9 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <?php $user = $this->main->user(); ?> -<form> +<form id="new-post-form"> <div class="modal-content new-post-modal"> <div class="row"> <?php $this->view('template/pfp', array('user' => $user))?> @@ -12,8 +14,8 @@ </div> <textarea type="text" - name="text" - id="text" + name="content" + id="new-post-content" placeholder="<?=lang('action_new_post_text', sub: [$user['first_name']])?>" ></textarea> </div> @@ -26,3 +28,19 @@ )?> </div> </form> +<script> + $('#new-post-form').submit(function(e) { + e.preventDefault(); + let content = $('#new-post-content').val(); + + $.ajax({ + url: '/api/post', + method: 'POST', + data: JSON.stringify({ content }), + success: function(data) { + window.location.reload(); + }, + error: errorToast + }); + }); +</script> diff --git a/web/views/template/comment.php b/web/views/template/comment.php index ef7a081..943f232 100644 --- a/web/views/template/comment.php +++ b/web/views/template/comment.php @@ -1,3 +1,5 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <div class="comment row mt"> <?php $this->view('template/pfp', array('user' => $user))?> <div class="ml col sub-card"> diff --git a/web/core/error.php b/web/views/template/error.php index 2e02cb1..2e02cb1 100644 --- a/web/core/error.php +++ b/web/views/template/error.php diff --git a/web/views/template/modal.php b/web/views/template/modal.php index 4f47400..e3ce6fe 100644 --- a/web/views/template/modal.php +++ b/web/views/template/modal.php @@ -1,3 +1,5 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <div class="modal-container"> <div class="modal"> <div class="modal-header row"> diff --git a/web/views/template/pfp.php b/web/views/template/pfp.php index 842fc92..aec7318 100644 --- a/web/views/template/pfp.php +++ b/web/views/template/pfp.php @@ -1,3 +1,5 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <?php $class = isset($class) ? $class : ''; ?> diff --git a/web/views/template/post.php b/web/views/template/post.php index d9c7c92..40a3c1d 100644 --- a/web/views/template/post.php +++ b/web/views/template/post.php @@ -1,3 +1,5 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> <div class="post card"> <div class="row"> <?php $this->view('template/pfp', array('user' => $user))?> diff --git a/web/views/template/toast.php b/web/views/template/toast.php new file mode 100644 index 0000000..1f74602 --- /dev/null +++ b/web/views/template/toast.php @@ -0,0 +1,19 @@ +<?php /* Copyright (c) 2024 Freya Murphy */ ?> +<?php /* vi: syntax=php */ ?> +<?php + $params = array(); + + if ($detail) { + array_push($params, lang('api_column_' . $detail)); + } + + if ($hint) { + array_push($params, $hint); + } + + $msg = lang($msg, sub: $params); +?> +<div class="toast error"> + <?=ucfirst($msg)?> + <?=ilang('action_close', class: 'action-close-toast')?> +</div> |