diff --git a/app/Controllers/Forum/ForumController.php b/app/Controllers/Forum/ForumController.php new file mode 100644 index 0000000..9f23267 --- /dev/null +++ b/app/Controllers/Forum/ForumController.php @@ -0,0 +1,161 @@ + + */ +class ForumController extends Controller +{ + public function index() + { + // Get the most active topics + $activeTopicsIds = DB::table('posts') + ->where('forum_id', '!=', config('forum.trash')) + ->groupBy('topic_id') + ->orderByRaw('COUNT(*) DESC') + ->limit(10) + ->get(['topic_id']); + $activeTopics = []; + + // make this not disgusting + while (list($_n, $_t) = each($activeTopicsIds)) { + $topic = new Topic($_t->topic_id); + $forum = new Forum($topic->forum); + + // Check if we have permission to view it + if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { + $fetch = DB::table('posts') + ->groupBy('topic_id') + ->orderByRaw('COUNT(*) DESC') + ->skip(11 + $_n) + ->take(1) + ->get(['topic_id']); + + if ($fetch) { + $activeTopicsIds[] = $fetch[0]; + } + continue; + } + + $activeTopics[$topic->id] = $topic; + } + + // Get the latest posts + $latestPostsIds = DB::table('posts') + ->where('forum_id', '!=', config('forum.trash')) + ->orderBy('post_id', 'desc') + ->limit(10) + ->get(['post_id']); + $latestPosts = []; + + while (list($_n, $_p) = each($latestPostsIds)) { + $post = new Post($_p->post_id); + $forum = new Forum($post->forum); + + // Check if we have permission to view it + if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { + $fetch = DB::table('posts') + ->orderBy('post_id', 'desc') + ->skip(11 + $_n) + ->take(1) + ->get(['post_id']); + + if ($fetch) { + $latestPostsIds[] = $fetch[0]; + } + continue; + } + + $latestPosts[$post->id] = $post; + } + + // Get the most active poster + $activePosterId = DB::table('posts') + ->where('forum_id', '!=', config('forum.trash')) + ->where('post_time', '>', time() - (24 * 60 * 60)) + ->groupBy('poster_id') + ->orderByRaw('COUNT(*) DESC') + ->limit(1) + ->get(['poster_id']); + $activePoster = User::construct( + $activePosterId ? $activePosterId[0]->poster_id : 0 + ); + + $forum = new Forum; + + return view('forum/index', compact('forum', 'activeTopics', 'latestPosts', 'activePoster')); + } + + public function forum($id = 0) + { + $forum = new Forum($id); + + $redirect = route('forums.index'); + $message = "The forum you tried to access does not exist!"; + + // Redirect forum id 0 to the main page + if ($forum->id === 0) { + return header("Location: {$redirect}"); + } + + // Check if the forum exists + if ($forum->id < 0 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { + return view('global/information', compact('message', 'redirect')); + } + + // Check if the forum isn't a link + if ($forum->type === 2) { + $message = "The forum you tried to access is a link. You're being redirected."; + $redirect = $forum->link; + + return view('global/information', compact('message', 'redirect')); + } + + return view('forum/forum', compact('forum')); + } + + public function markRead($id = 0) + { + $redirect = route('forums.index'); + + if (!session_check('s')) { + $message = "Your session expired! Go back and try again."; + return view('global/information', compact('message', 'redirect')); + } + + $forum = new Forum($id); + + // Check if the forum exists + if ($forum->id < 1 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { + $message = "The forum you tried to access does not exist."; + return view('global/information', compact('message', 'redirect')); + } + + $forum->trackUpdateAll(ActiveUser::$user->id); + + $message = 'All topics have been marked as read!'; + $redirect = route('forums.forum', $forum->id); + + return view('global/information', compact('message', 'redirect')); + } +} diff --git a/app/Controllers/Forum/PostController.php b/app/Controllers/Forum/PostController.php new file mode 100644 index 0000000..fec0433 --- /dev/null +++ b/app/Controllers/Forum/PostController.php @@ -0,0 +1,243 @@ + + */ +class PostController extends Controller +{ + public function find($id = 0) + { + $post = new Post($id); + $topic = new Topic($post->topic); + $forum = new Forum($topic->forum); + + // Check if the forum exists + if ($post->id === 0 + || $topic->id === 0 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { + $message = "This post doesn't exist or you don't have access to it!"; + $redirect = route('forums.index'); + + return view('global/information', compact('message', 'redirect')); + } + + $topicLink = route('forums.topic', $topic->id); + + // Get all post ids from the database + $postIds = DB::table('posts') + ->where('topic_id', $topic->id) + ->get(['post_id']); + $postIds = array_column($postIds, 'post_id'); + + // Find in array + $postAt = ceil(array_search($post->id, $postIds) / 10); + + // Only append the page variable if it's more than 1 + if ($postAt > 1) { + $topicLink .= "?page={$postAt}"; + } + + return header("Location: {$topicLink}#p{$post->id}"); + } + + public function raw($id = 0) + { + $post = new Post($id); + $topic = new Topic($post->topic); + $forum = new Forum($topic->forum); + + // Check if the forum exists + if ($post->id === 0 + || $topic->id === 0 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { + return ""; + } + + return $post->text; + } + + public function edit($id = 0) + { + $title = $_POST['title'] ?? null; + $text = $_POST['text'] ?? null; + + $post = new Post($id); + $topic = new Topic($post->topic); + $forum = new Forum($topic->forum); + + // Check permissions + $noAccess = $post->id === 0 + || $topic->id === 0 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id); + + $noEdit = ( + $post->poster->id === ActiveUser::$user->id + ? !ActiveUser::$user->permission(ForumPerms::EDIT_OWN, Perms::FORUM) + : !$forum->permission(ForumPerms::EDIT_ANY, ActiveUser::$user->id) + ) || ( + $topic->status === 1 + && !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id) + ); + + // Check if the forum exists + if ($noAccess || $noEdit) { + if ($noDelete) { + $message = "You aren't allowed to edit posts in this topic!"; + $redirect = route('forums.post', $post->id); + } else { + $message = "This post doesn't exist or you don't have access to it!"; + $redirect = route('forums.index'); + } + + return view('global/information', compact('message', 'redirect')); + } + + $titleLength = strlen($title); + $textLength = strlen($text); + $titleMin = config('forum.min_title_length'); + $titleMax = config('forum.max_title_length'); + $textMin = config('forum.min_post_length'); + $textMax = config('forum.max_post_length'); + + // Checks + $titleTooShort = $title !== null + && $post->id === $topic->firstPost()->id + && $titleLength < $titleMin; + $titleTooLong = $title !== null + && $post->id === $topic->firstPost()->id + && $titleLength > $titleMax; + $textTooShort = $textLength < $textMin; + $textTooLong = $textLength > $textMax; + + // Check requirments + if ($titleTooShort + || $titleTooLong + || $textTooShort + || $textTooLong) { + $message = ""; + + if ($titleTooShort) { + $message = "This title is too short!"; + } elseif ($titleTooLong) { + $message = "This title is too long!"; + } elseif ($textTooShort) { + $message = "Please make your post a little bit longer!"; + } elseif ($textTooLong) { + $message = "Your post is too long, you're gonna have to cut a little!"; + } + + $redirect = route('forums.post', $post->id); + + if (!isset($_SESSION['replyText'])) { + $_SESSION['replyText'] = []; + } + + $_SESSION['replyText']["t{$forum->id}"] = $text; + + return view('global/information', compact('message', 'redirect')); + } + + unset($_SESSION['replyText']["t{$forum->id}"]); + + if ($post->id !== $topic->firstPost()->id || $title === null) { + $title = "Re: {$topic->title}"; + } else { + $topic->title = $title; + $topic->update(); + } + + // Create the post + $post->subject = $title; + $post->text = $text; + $post->editTime = time(); + $post->editReason = ''; + $post->editUser = ActiveUser::$user; + $post = $post->update(); + + $postLink = route('forums.post', $post->id); + + return header("Location: {$postLink}"); + } + + public function delete($id = 0) + { + $action = isset($_POST['yes']) && session_check(); + + $post = new Post($id); + $topic = new Topic($post->topic); + $forum = new Forum($topic->forum); + + // Check permissions + $noAccess = $post->id === 0 + || $topic->id === 0 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id); + + $noDelete = ( + $post->poster->id === ActiveUser::$user->id + ? !ActiveUser::$user->permission(ForumPerms::DELETE_OWN, Perms::FORUM) + : !$forum->permission(ForumPerms::DELETE_ANY, ActiveUser::$user->id) + ) || ( + $topic->status === 1 + && !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id) + ); + + // Check if the forum exists + if ($noAccess || $noDelete) { + if ($noDelete) { + $message = "You aren't allowed to delete posts in this topic!"; + $redirect = Router::route('forums.post', $post->id); + } else { + $message = "This post doesn't exist or you don't have access to it!"; + $redirect = Router::route('forums.index'); + } + + return view('global/information', compact('message', 'redirect')); + } + + if ($action !== null) { + if ($action) { + // Set message + $message = "Deleted the post!"; + + // Check if the topic only has 1 post + if ($topic->replyCount() === 1) { + // Delete the entire topic + $topic->delete(); + + $redirect = route('forums.forum', $forum->id); + } else { + // Just delete the post + $post->delete(); + + $redirect = route('forums.topic', $topic->id); + } + + return view('global/information', compact('message', 'redirect')); + } + + $postLink = route('forums.post', $post->id); + return header("Location: {$postLink}"); + } + + $message = "Are you sure?"; + + return view('global/confirm', compact('message')); + } +} diff --git a/app/Controllers/Forum/TopicController.php b/app/Controllers/Forum/TopicController.php index 021eaeb..d319f82 100644 --- a/app/Controllers/Forum/TopicController.php +++ b/app/Controllers/Forum/TopicController.php @@ -1,6 +1,6 @@ forum); // Check if the forum exists - if ($topic->id === 0 || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { - // Set render data - Template::vars([ - 'message' => "This topic doesn't exist or you don't have access to it!", - 'redirect' => route('forums.index'), - ]); + if ($topic->id === 0 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { + $message = "This topic doesn't exist or you don't have access to it!"; + $redirect = route('forums.index'); - // Print page contents - return Template::render('global/information'); + return view('global/information', compact('message', 'redirect')); } - // Update the tracking status $topic->trackUpdate(ActiveUser::$user->id); - - // Update views $topic->viewsUpdate(); - // Set parse variables - Template::vars(compact('forum', 'topic')); - - // Print page contents - return Template::render('forum/topic'); + return view('forum/topic', compact('forum', 'topic')); } private function modBase($id) @@ -59,7 +46,9 @@ class TopicController extends Controller $topic = new Topic($id); $forum = new Forum($topic->forum); - if ($topic->id !== 0 || $forum->permission(ForumPerms::VIEW, ActiveUser::$user->id) || session_check()) { + if ($topic->id !== 0 + || $forum->permission(ForumPerms::VIEW, ActiveUser::$user->id) + || session_check()) { return compact('topic', 'forum'); } @@ -240,9 +229,7 @@ class TopicController extends Controller $message = "This post doesn't exist or you don't have access to it!"; $redirect = route('forums.index'); - Template::vars(compact('message', 'redirect')); - - return Template::render('global/information'); + return view('global/information', compact('message', 'redirect')); } // Check if the topic exists @@ -254,9 +241,7 @@ class TopicController extends Controller $message = "You are not allowed to post in this topic!"; $redirect = route('forums.topic', $topic->id); - Template::vars(compact('message', 'redirect')); - - return Template::render('global/information'); + return view('global/information', compact('message', 'redirect')); } // Length @@ -269,7 +254,7 @@ class TopicController extends Controller // Check requirments if ($tooShort || $tooLong) { - $route = Router::route('forums.topic', $topic->id); + $route = route('forums.topic', $topic->id); $message = "Your post is " . ( $tooShort @@ -278,15 +263,13 @@ class TopicController extends Controller ); $redirect = "{$route}#reply"; - Template::vars(compact('message', 'redirect')); - if (!isset($_SESSION['replyText'])) { $_SESSION['replyText'] = []; } $_SESSION['replyText']["t{$topic->id}"] = $text; - return Template::render('global/information'); + return view('global/information', compact('message', 'redirect')); } unset($_SESSION['replyText']["t{$topic->id}"]); @@ -306,4 +289,90 @@ class TopicController extends Controller // Head to the post return header("Location: {$postLink}"); } + + public function create($id = 0) + { + $title = $_POST['title'] ?? null; + $text = $_POST['text'] ?? null; + + // And attempt to get the forum + $forum = new Forum($id); + + // Check if the forum exists + if ($forum->id === 0 + || $forum->type !== 0 + || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id) + || !$forum->permission(ForumPerms::REPLY, ActiveUser::$user->id) + || !$forum->permission(ForumPerms::CREATE_THREADS, ActiveUser::$user->id)) { + $message = "This forum doesn't exist or you don't have access to it!"; + $redirect = route('forums.index'); + + return view('global/information', compact('message', 'redirect')); + } + + if ($text && $title) { + // Length + $titleLength = strlen($title); + $textLength = strlen($text); + $titleMin = config('forum.min_title_length'); + $titleMax = config('forum.max_title_length'); + $textMin = config('forum.min_post_length'); + $textMax = config('forum.max_post_length'); + + // Checks + $titleTooShort = $titleLength < $titleMin; + $titleTooLong = $titleLength > $titleMax; + $textTooShort = $textLength < $textMin; + $textTooLong = $textLength > $textMax; + + // Check requirments + if ($titleTooShort + || $titleTooLong + || $textTooShort + || $textTooLong) { + $message = ""; + + if ($titleTooShort) { + $message = "This title is too short, it has to be longer than {$titleMin} characters!"; + } elseif ($titleTooLong) { + $message = "This title is too long, keep it under {$titleMax} characters!"; + } elseif ($textTooShort) { + $message = "Please make your post a little bit longer, at least {$textMin} characters!"; + } elseif ($textTooLong) { + $message = "Your post is too long, you're gonna have to cut a little!" + . " Can't be more than {$textMax} characters."; + } + + $redirect = route('forums.new', $forum->id); + + if (!isset($_SESSION['replyText'])) { + $_SESSION['replyText'] = []; + } + + $_SESSION['replyText']["f{$forum->id}"]["title"] = $title; + $_SESSION['replyText']["f{$forum->id}"]["text"] = $text; + + return view('global/information', compact('message', 'redirect')); + } + + unset($_SESSION['replyText']["f{$forum->id}"]); + + // Create the post + $post = Post::create( + $title, + $text, + ActiveUser::$user, + 0, + $forum->id + ); + + // Go to the post + $postLink = route('forums.post', $post->id); + + // Head to the post + return header("Location: {$postLink}"); + } + + return view('forum/topic', compact('forum')); + } } diff --git a/app/Controllers/ForumController.php b/app/Controllers/ForumController.php deleted file mode 100644 index 23f9246..0000000 --- a/app/Controllers/ForumController.php +++ /dev/null @@ -1,630 +0,0 @@ - - */ -class ForumController extends Controller -{ - /** - * Serves the forum index. - * - * @return string HTML for the forum index. - */ - public function index() - { - // Get the most active topics - $activeTopicsIds = DB::table('posts') - ->where('forum_id', '!=', config('forum.trash')) - ->groupBy('topic_id') - ->orderByRaw('COUNT(*) DESC') - ->limit(10) - ->get(['topic_id']); - $activeTopics = []; - - // make this not disgusting - while (list($_n, $_t) = each($activeTopicsIds)) { - // Create the topic object - $topic = new Topic($_t->topic_id); - - // Create a forum object - $forum = new Forum($topic->forum); - - // Check if we have permission to view it - if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { - $fetch = DB::table('posts') - ->groupBy('topic_id') - ->orderByRaw('COUNT(*) DESC') - ->skip(11 + $_n) - ->take(1) - ->get(['topic_id']); - - if ($fetch) { - $activeTopicsIds[] = $fetch[0]; - } - continue; - } - - $activeTopics[$topic->id] = $topic; - } - - // Get the latest posts - $latestPostsIds = DB::table('posts') - ->where('forum_id', '!=', config('forum.trash')) - ->orderBy('post_id', 'desc') - ->limit(10) - ->get(['post_id']); - $latestPosts = []; - - while (list($_n, $_p) = each($latestPostsIds)) { - // Create new post object - $post = new Post($_p->post_id); - - // Forum id - $forum = new Forum($post->forum); - - // Check if we have permission to view it - if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { - $fetch = DB::table('posts') - ->orderBy('post_id', 'desc') - ->skip(11 + $_n) - ->take(1) - ->get(['post_id']); - - if ($fetch) { - $latestPostsIds[] = $fetch[0]; - } - continue; - } - - $latestPosts[$post->id] = $post; - } - - // Get the most active poster - $activePosterId = DB::table('posts') - ->where('forum_id', '!=', config('forum.trash')) - ->where('post_time', '>', time() - (24 * 60 * 60)) - ->groupBy('poster_id') - ->orderByRaw('COUNT(*) DESC') - ->limit(1) - ->get(['poster_id']); - $activePoster = User::construct( - $activePosterId ? $activePosterId[0]->poster_id : 0 - ); - - // Create the forum object - $forum = new Forum; - - Template::vars(compact('forum', 'activeTopics', 'latestPosts', 'activePoster')); - - // Return the compiled page - return Template::render('forum/index'); - } - - /** - * Get a forum page. - * - * @return string - */ - public function forum($id = 0) - { - // Get the forum - $forum = new Forum($id); - - // Redirect forum id 0 to the main page - if ($forum->id === 0) { - return header('Location: ' . Router::route('forums.index')); - } - - // Check if the forum exists - if ($forum->id < 0) { - // Set render data - Template::vars([ - 'page' => [ - 'message' => 'The forum you tried to access does not exist.', - 'redirect' => Router::route('forums.index'), - ], - ]); - - // Print page contents - return Template::render('global/information'); - } - - // Check if the user has access to the forum - if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { - // Set render data - Template::vars([ - 'page' => [ - 'message' => 'You do not have access to this forum.', - 'redirect' => Router::route('forums.index'), - ], - ]); - - // Print page contents - return Template::render('global/information'); - } - - // Check if the forum isn't a link - if ($forum->type === 2) { - // Set render data - Template::vars([ - 'page' => [ - 'message' => 'The forum you tried to access is a link. You\'re being redirected.', - 'redirect' => $forum->link, - ], - ]); - - // Print page contents - return Template::render('global/information'); - } - - // Set parse variables - Template::vars([ - 'forum' => $forum, - ]); - - // Print page contents - return Template::render('forum/forum'); - } - - /** - * Mark a forum as read. - * - * @return string - */ - public function markForumRead($id = 0) - { - // Check if the session id was supplied - if (!isset($_GET['s']) || $_GET['s'] != session_id()) { - // Set render data - Template::vars([ - 'page' => [ - 'message' => 'Your session expired! Go back and try again.', - 'redirect' => Router::route('forums.index'), - ], - ]); - - // Print page contents - return Template::render('global/information'); - } - - // Get the forum - $forum = new Forum($id); - - // Check if the forum exists - if ($forum->id < 1) { - // Set render data - Template::vars([ - 'page' => [ - 'message' => 'The forum you tried to access does not exist.', - 'redirect' => Router::route('forums.index'), - ], - ]); - - // Print page contents - return Template::render('global/information'); - } - - // Check if the user has access to the forum - if (!$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { - // Set render data - Template::vars([ - 'page' => [ - 'message' => 'You do not have access to this forum.', - 'redirect' => Router::route('forums.index'), - ], - ]); - - // Print page contents - return Template::render('global/information'); - } - - // Run the function - $forum->trackUpdateAll(ActiveUser::$user->id); - - // Set render data - Template::vars([ - 'page' => [ - 'message' => 'All topics have been marked as read.', - 'redirect' => Router::route('forums.forum', $forum->id), - ], - ]); - - // Print page contents - return Template::render('global/information'); - } - - /** - * Redirect to the position of a post in a topic. - * - * @return mixed - */ - public function post($id = 0) - { - // Attempt to get the post - $post = new Post($id); - - // And attempt to get the forum - $topic = new Topic($post->topic); - - // And attempt to get the forum - $forum = new Forum($topic->forum); - - // Check if the forum exists - if ($post->id == 0 - || $topic->id == 0 - || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { - $message = "This post doesn't exist or you don't have access to it!"; - $redirect = Router::route('forums.index'); - - Template::vars(compact('message', 'redirect')); - - return Template::render('global/information'); - } - - // Generate link - $topicLink = Router::route('forums.topic', $topic->id); - - // Get all post ids from the database - $postIds = DB::table('posts') - ->where('topic_id', $topic->id) - ->get(['post_id']); - $postIds = array_column($postIds, 'post_id'); - - // Find in array - $postAt = ceil(array_search($post->id, $postIds) / 10); - - // Only append the page variable if it's more than 1 - if ($postAt > 1) { - $topicLink .= "?page={$postAt}"; - } - - return header("Location: {$topicLink}#p{$post->id}"); - } - - /** - * Get the raw text of a post. - * - * @return string - */ - public function postRaw($id = 0) - { - // Attempt to get the post - $post = new Post($id); - - // And attempt to get the forum - $topic = new Topic($post->topic); - - // And attempt to get the forum - $forum = new Forum($topic->forum); - - // Check if the forum exists - if ($post->id == 0 - || $topic->id == 0 - || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id)) { - return ""; - } - - return $post->text; - } - - /** - * Create a topic. - * - * @return string - */ - public function createTopic($id = 0) - { - $title = isset($_POST['title']) ? $_POST['title'] : null; - $text = isset($_POST['text']) ? $_POST['text'] : null; - - // And attempt to get the forum - $forum = new Forum($id); - - // Check if the forum exists - if ($forum->id === 0 - || $forum->type !== 0 - || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id) - || !$forum->permission(ForumPerms::REPLY, ActiveUser::$user->id) - || !$forum->permission(ForumPerms::CREATE_THREADS, ActiveUser::$user->id)) { - $message = "This forum doesn't exist or you don't have access to it!"; - $redirect = Router::route('forums.index'); - - Template::vars(compact('message', 'redirect')); - - return Template::render('global/information'); - } - - if ($text && $title) { - // Length - $titleLength = strlen($title); - $textLength = strlen($text); - $titleMin = config('forum.min_title_length'); - $titleMax = config('forum.max_title_length'); - $textMin = config('forum.min_post_length'); - $textMax = config('forum.max_post_length'); - - // Checks - $titleTooShort = $titleLength < $titleMin; - $titleTooLong = $titleLength > $titleMax; - $textTooShort = $textLength < $textMin; - $textTooLong = $textLength > $textMax; - - // Check requirments - if ($titleTooShort - || $titleTooLong - || $textTooShort - || $textTooLong) { - $message = ""; - - if ($titleTooShort) { - $message = "This title is too short!"; - } elseif ($titleTooLong) { - $message = "This title is too long!"; - } elseif ($textTooShort) { - $message = "Please make your post a little bit longer!"; - } elseif ($textTooLong) { - $message = "Your post is too long, you're gonna have to cut a little!"; - } - - $redirect = Router::route('forums.new', $forum->id); - - Template::vars(compact('message', 'redirect')); - - if (!isset($_SESSION['replyText'])) { - $_SESSION['replyText'] = []; - } - - $_SESSION['replyText']["f{$forum->id}"]["title"] = $title; - $_SESSION['replyText']["f{$forum->id}"]["text"] = $text; - - return Template::render('global/information'); - } - - unset($_SESSION['replyText']["f{$forum->id}"]); - - // Create the post - $post = Post::create( - $title, - $text, - ActiveUser::$user, - 0, - $forum->id - ); - - // Go to the post - $postLink = Router::route('forums.post', $post->id); - - // Head to the post - return header("Location: {$postLink}"); - } - - Template::vars(compact('forum')); - - return Template::render('forum/topic'); - } - - /** - * Edit a post. - * - * @return string - */ - public function editPost($id = 0) - { - $title = isset($_POST['title']) ? $_POST['title'] : null; - $text = isset($_POST['text']) ? $_POST['text'] : null; - - // Attempt to get the post - $post = new Post($id); - - // Attempt to get the topic - $topic = new Topic($post->topic); - - // And attempt to get the forum - $forum = new Forum($topic->forum); - - // Check permissions - $noAccess = $post->id == 0 - || $topic->id == 0 - || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id); - - $noEdit = ( - $post->poster->id === ActiveUser::$user->id - ? !ActiveUser::$user->permission(ForumPerms::EDIT_OWN, Perms::FORUM) - : !$forum->permission(ForumPerms::EDIT_ANY, ActiveUser::$user->id) - ) || ( - $topic->status === 1 - && !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id) - ); - - // Check if the forum exists - if ($noAccess || $noEdit) { - if ($noDelete) { - $message = "You aren't allowed to edit posts in this topic!"; - $redirect = Router::route('forums.post', $post->id); - } else { - $message = "This post doesn't exist or you don't have access to it!"; - $redirect = Router::route('forums.index'); - } - - Template::vars(compact('message', 'redirect')); - - return Template::render('global/information'); - } - - // Length - $titleLength = strlen($title); - $textLength = strlen($text); - $titleMin = config('forum.min_title_length'); - $titleMax = config('forum.max_title_length'); - $textMin = config('forum.min_post_length'); - $textMax = config('forum.max_post_length'); - - // Checks - $titleTooShort = $title !== null - && $post->id === $topic->firstPost()->id - && $titleLength < $titleMin; - $titleTooLong = $title !== null - && $post->id === $topic->firstPost()->id - && $titleLength > $titleMax; - $textTooShort = $textLength < $textMin; - $textTooLong = $textLength > $textMax; - - // Check requirments - if ($titleTooShort - || $titleTooLong - || $textTooShort - || $textTooLong) { - $message = ""; - - if ($titleTooShort) { - $message = "This title is too short!"; - } elseif ($titleTooLong) { - $message = "This title is too long!"; - } elseif ($textTooShort) { - $message = "Please make your post a little bit longer!"; - } elseif ($textTooLong) { - $message = "Your post is too long, you're gonna have to cut a little!"; - } - - $redirect = Router::route('forums.post', $post->id); - - Template::vars(compact('message', 'redirect')); - - if (!isset($_SESSION['replyText'])) { - $_SESSION['replyText'] = []; - } - - $_SESSION['replyText']["t{$forum->id}"] = $text; - - return Template::render('global/information'); - } - - unset($_SESSION['replyText']["t{$forum->id}"]); - - if ($post->id !== $topic->firstPost()->id || $title === null) { - $title = "Re: {$topic->title}"; - } else { - $topic->title = $title; - $topic->update(); - } - - // Create the post - $post->subject = $title; - $post->text = $text; - $post->editTime = time(); - $post->editReason = ''; - $post->editUser = ActiveUser::$user; - $post = $post->update(); - - // Go to the post - $postLink = Router::route('forums.post', $post->id); - - // Head to the post - return header("Location: {$postLink}"); - } - - /** - * Delete a post. - * - * @return string - */ - public function deletePost($id = 0) - { - $action = isset($_POST['yes']) && isset($_POST['sessionid']) - ? $_POST['sessionid'] === session_id() - : null; - - // Attempt to get the post - $post = new Post($id); - - // And attempt to get the forum - $topic = new Topic($post->topic); - - // And attempt to get the forum - $forum = new Forum($topic->forum); - - // Check permissions - $noAccess = $post->id == 0 - || $topic->id == 0 - || !$forum->permission(ForumPerms::VIEW, ActiveUser::$user->id); - - $noDelete = ( - $post->poster->id === ActiveUser::$user->id - ? !ActiveUser::$user->permission(ForumPerms::DELETE_OWN, Perms::FORUM) - : !$forum->permission(ForumPerms::DELETE_ANY, ActiveUser::$user->id) - ) || ( - $topic->status === 1 - && !$forum->permission(ForumPerms::LOCK, ActiveUser::$user->id) - ); - - // Check if the forum exists - if ($noAccess || $noDelete) { - if ($noDelete) { - $message = "You aren't allowed to delete posts in this topic!"; - $redirect = Router::route('forums.post', $post->id); - } else { - $message = "This post doesn't exist or you don't have access to it!"; - $redirect = Router::route('forums.index'); - } - - Template::vars(compact('message', 'redirect')); - - return Template::render('global/information'); - } - - if ($action !== null) { - if ($action) { - // Set message - $message = "Deleted the post!"; - - // Check if the topic only has 1 post - if ($topic->replyCount() === 1) { - // Delete the entire topic - $topic->delete(); - - $redirect = Router::route('forums.forum', $forum->id); - } else { - // Just delete the post - $post->delete(); - - $redirect = Router::route('forums.topic', $topic->id); - } - - Template::vars(compact('message', 'redirect')); - - return Template::render('global/information'); - } - - $postLink = Router::route('forums.post', $post->id); - return header("Location: {$postLink}"); - } - - $message = "Are you sure?"; - - Template::vars(compact('message')); - - return Template::render('global/confirm'); - } -} diff --git a/app/Controllers/Manage/Controller.php b/app/Controllers/Manage/Controller.php new file mode 100644 index 0000000..6dec189 --- /dev/null +++ b/app/Controllers/Manage/Controller.php @@ -0,0 +1,37 @@ + + */ +class Controller extends BaseController +{ + public function __construct() + { + $navigation = $this->navigation(); + + Template::vars(compact('navigation')); + } + + public function navigation() + { + $nav = []; + + // Overview + $nav["Overview"]["Index"] = route('manage.overview.index'); + + return $nav; + } +} diff --git a/app/Controllers/Manage/OverviewController.php b/app/Controllers/Manage/OverviewController.php new file mode 100644 index 0000000..1ac340a --- /dev/null +++ b/app/Controllers/Manage/OverviewController.php @@ -0,0 +1,46 @@ + + */ +class OverviewController extends Controller +{ + public function index() + { + return view('manage/overview/index'); + } + + public function data() + { + $data = new \stdClass; + + $data->postsCount = DB::table('posts') + ->count(); + + $data->topicsCount = DB::table('topics') + ->count(); + + $data->usersCount = DB::table('users') + ->count(); + + $data->commentsCount = DB::table('comments') + ->count(); + + $data->uploadsCount = DB::table('uploads') + ->count(); + + return $this->json($data); + } +} diff --git a/app/Template.php b/app/Template.php index edaea27..68a383b 100644 --- a/app/Template.php +++ b/app/Template.php @@ -10,6 +10,7 @@ namespace Sakura; use Twig_Environment; use Twig_Extension_StringLoader; use Twig_Loader_Filesystem; +use Twig_SimpleFilter; use Twig_SimpleFunction; /** @@ -51,10 +52,18 @@ class Template * * @var array */ - protected static $utility = [ + protected static $utilityFunctions = [ 'route', 'config', 'session_id', + ]; + + /** + * List of utility filters to add to templating + * + * @var array + */ + protected static $utilityFilters = [ 'json_decode', 'byte_symbol', ]; @@ -99,8 +108,13 @@ class Template self::$engine->addExtension(new Twig_Extension_StringLoader()); // Add utility functions - foreach (self::$utility as $utility) { - self::$engine->addFunction(new Twig_SimpleFunction($utility, $utility)); + foreach (self::$utilityFunctions as $function) { + self::$engine->addFunction(new Twig_SimpleFunction($function, $function)); + } + + // Add utility filters + foreach (self::$utilityFilters as $filter) { + self::$engine->addFilter(new Twig_SimpleFilter($filter, $filter)); } } diff --git a/gulpfile.js b/gulpfile.js index 00c646a..2a42e39 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -7,7 +7,7 @@ elixir(function(mix) { mix .less('aitemu/master.less', 'public/css/aitemu.css') .less('yuuno/master.less', 'public/css/yuuno.css') - .typescript('master/**/*.ts', 'public/js/master.js') + .typescript('app/**/*.ts', 'public/js/app.js') .typescript('aitemu/**/*.ts', 'public/js/aitemu.js') .typescript('yuuno/**/*.ts', 'public/js/yuuno.js') .scripts([ diff --git a/resources/assets/typescript/app/AJAX.ts b/resources/assets/typescript/app/AJAX.ts new file mode 100644 index 0000000..0ff871b --- /dev/null +++ b/resources/assets/typescript/app/AJAX.ts @@ -0,0 +1,147 @@ +namespace Sakura +{ + export class AJAX + { + // XMLHTTPRequest container + private Request: XMLHttpRequest; + private Callbacks: any; + private Headers: any; + private URL: string; + private Send: string = null; + private Asynchronous: boolean = true; + + // Prepares the XMLHttpRequest and stuff + constructor(async: boolean = true) { + this.Request = new XMLHttpRequest(); + this.Callbacks = new Object(); + this.Headers = new Object(); + this.Asynchronous = async; + } + + // Start + public Start(method: HTTPMethod, avoidCache: boolean = false): void { + // Open the connection + this.Request.open(HTTPMethod[method], this.URL + (avoidCache ? (this.URL.indexOf("?") < 0 ? '?' : '&') + "no-cache=" + Date.now() : ""), this.Asynchronous); + + // Set headers + this.PrepareHeaders(); + + // Watch the ready state + this.Request.onreadystatechange = () => { + // Only invoke when complete + if (this.Request.readyState === 4) { + // Check if a callback if present + if ((typeof this.Callbacks[this.Request.status]).toLowerCase() === 'function') { + this.Callbacks[this.Request.status](this); + } else { // Else check if there's a generic fallback present + if ((typeof this.Callbacks['0']).toLowerCase() === 'function') { + // Call that + this.Callbacks['0'](this); + } + } + } + } + + this.Request.send(this.Send); + } + + // Stop + public Stop(): void { + this.Request = null; + } + + // Set content type required for forms + public Form(): void { + this.ContentType("application/x-www-form-urlencoded"); + } + + // Add post data + public SetSend(data: any): void { + // Storage array + var store: Array = new Array(); + + // Iterate over the object and them in the array with an equals sign inbetween + for (var item in data) { + store.push(encodeURIComponent(item) + "=" + encodeURIComponent(data[item])); + } + + // Assign to send + this.Send = store.join('&'); + } + + // Set raw post + public SetRawSend(data: string): void { + this.Send = data; + } + + // Get response + public Response(): string { + return this.Request.responseText; + } + + // Automatically JSON parse the response + public JSON(): Object { + return JSON.parse(this.Response()); + } + + // Get all headers + public ResponseHeaders(): string { + return this.Request.getAllResponseHeaders(); + } + + // Get a header + public ResponseHeader(name: string): string { + return this.Request.getResponseHeader(name); + } + + // Set charset + public ContentType(type: string, charset: string = null): void { + this.AddHeader('Content-Type', type + ';charset=' + (charset ? charset : 'utf-8')); + } + + // Add a header + public AddHeader(name: string, value: string): void { + // Attempt to remove a previous instance + this.RemoveHeader(name); + + // Add the new header + this.Headers[name] = value; + } + + // Remove a header + public RemoveHeader(name: string): void { + if ((typeof this.Headers[name]).toLowerCase() !== 'undefined') { + delete this.Headers[name]; + } + } + + // Prepare Request headers + public PrepareHeaders(): void { + for (var header in this.Headers) { + this.Request.setRequestHeader(header, this.Headers[header]); + } + } + + // Adds a callback + public AddCallback(status: number, callback: Function): void { + // Attempt to remove previous instances + this.RemoveCallback(status); + + // Add the new callback + this.Callbacks[status] = callback; + } + + // Delete a callback + public RemoveCallback(status: number): void { + // Delete the callback if present + if ((typeof this.Callbacks[status]).toLowerCase() === 'function') { + delete this.Callbacks[status]; + } + } + + // Sets the URL + public SetUrl(url: string): void { + this.URL = url; + } + } +} diff --git a/resources/assets/typescript/app/Changelog.ts b/resources/assets/typescript/app/Changelog.ts new file mode 100644 index 0000000..c60978d --- /dev/null +++ b/resources/assets/typescript/app/Changelog.ts @@ -0,0 +1,87 @@ +namespace Sakura +{ + export class Changelog + { + private static Client: AJAX; + private static Element: DOM; + private static Fetch: number = 0; + private static Colours: string[] = [ + 'inherit', // Unknown + '#2A2', // Add + '#2AA', // Update + '#2AA', // Fix + '#A22', // Remove + '#62C', // Export + '#C44', // Revert + ]; + + public static Build(target: DOM) + { + this.Client = new AJAX; + this.Element = DOM.Create('table', 'changelog panelTable'); + + this.Element.Element.style.borderSpacing = '0 1px'; + + var title: DOM = DOM.Create('div', 'head'), + link: DOM = DOM.Create('a', 'underline'); + + title.Element.style.marginBottom = '1px'; + + link.Text('Changelog'); + (link.Element).href = Config.ChangelogUrl + '#r' + Config.Revision; + (link.Element).target = '_blank'; + + title.Append(link); + target.Append(title); + target.Append(this.Element); + + this.Client.SetUrl(Config.ChangelogUrl + Config.ChangelogApi); + + this.Client.AddCallback(200, (client: AJAX) => { + Changelog.Add(client.JSON()); + + if (Changelog.Fetch < 2) { + Changelog.Fetch++; + Changelog.Client.SetUrl(Config.ChangelogUrl + Config.ChangelogApi + Changelog.Fetch); + Changelog.Client.Start(HTTPMethod.GET); + } + }); + + this.Client.SetUrl(Config.ChangelogUrl + Config.ChangelogApi + Changelog.Fetch); + this.Client.Start(HTTPMethod.GET); + } + + private static Add(changelog: IChangelogDate) + { + var header: DOM = DOM.Create('tr', 'changelog__row changelog__row--header'), + headerInner: DOM = DOM.Create('th', 'changelog__header'); + + headerInner.Text(changelog.date); + headerInner.Element.style.fontSize = '1.2em'; + (headerInner.Element).colSpan = 2; + + header.Append(headerInner); + this.Element.Append(header); + + for (var _i in changelog.changes) + { + var change: IChangelogChange = changelog.changes[_i], + row: DOM = DOM.Create('tr', 'changelog__row'), + action: DOM = DOM.Create('td', 'changelog__column'), + message: DOM = DOM.Create('td', 'changelog__column'); + + action.Text(change.action.name); + action.Element.style.backgroundColor = this.Colours[change.action.id]; + action.Element.style.borderBottom = '1px solid ' + this.Colours[change.action.id]; + + message.Text(change.message); + message.Element.style.borderBottom = '1px solid ' + this.Colours[change.action.id]; + + row.Append(action); + row.Append(message); + + this.Element.Append(row); + } + } + } +} diff --git a/resources/assets/typescript/app/Comments.ts b/resources/assets/typescript/app/Comments.ts new file mode 100644 index 0000000..3d31cbc --- /dev/null +++ b/resources/assets/typescript/app/Comments.ts @@ -0,0 +1,6 @@ +namespace Sakura +{ + export class Comments + { + } +} diff --git a/resources/assets/typescript/app/Config.ts b/resources/assets/typescript/app/Config.ts new file mode 100644 index 0000000..6b25172 --- /dev/null +++ b/resources/assets/typescript/app/Config.ts @@ -0,0 +1,21 @@ +namespace Sakura +{ + export class Config + { + public static Revision: number = 0; + public static SessionId: string = ""; + public static UserNameMinLength: number = 3; + public static UserNameMaxLength: number = 16; + public static PasswordMinEntropy: number = 48; + public static LoggedIn: boolean = false; + public static ChangelogUrl: string = "https://sakura.flash.moe/"; + public static ChangelogApi: string = "api.php/"; + + public static Set(config: Object): void + { + for (var key in config) { + this[key] = config[key]; + } + } + } +} diff --git a/resources/assets/typescript/app/Cookies.ts b/resources/assets/typescript/app/Cookies.ts new file mode 100644 index 0000000..95d10e7 --- /dev/null +++ b/resources/assets/typescript/app/Cookies.ts @@ -0,0 +1,33 @@ +namespace Sakura +{ + export class Cookies + { + public static Get(name: string): string { + // Do a regex on document.cookie + var get: RegExpExecArray = new RegExp('(^|; )' + encodeURIComponent(name) + '=([^;]*)').exec(document.cookie); + + // Return the value + return get ? get[2] : ''; + } + + public static Set(name: string, value: string): void { + // Check if the cookie already exists + if (this.Get(name).length > 1) { + this.Delete(name); + } + + // Lifetime + var life: Date = new Date(); + + // Cookies live for a year + life.setFullYear(life.getFullYear() + 1); + + // Assign the cookie + document.cookie = name + '=' + value + ';expires=' + life.toUTCString(); + } + + public static Delete(name: string): void { + document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; + } + } +} diff --git a/resources/assets/typescript/app/DOM.ts b/resources/assets/typescript/app/DOM.ts new file mode 100644 index 0000000..4f1c645 --- /dev/null +++ b/resources/assets/typescript/app/DOM.ts @@ -0,0 +1,74 @@ +namespace Sakura +{ + export class DOM + { + public Element: HTMLElement; + + constructor(object: any, mode: DOMSelector) { + switch (mode) { + case DOMSelector.ID: + this.Element = document.getElementById(object); + break; + + case DOMSelector.CLASS: + this.Element = document.getElementsByClassName(object)[0]; + break; + + case DOMSelector.ELEMENT: + this.Element = object; + break; + + case DOMSelector.QUERY: + this.Element = document.querySelector(object); + break; + } + } + + public static Create(element: string, className: string = null, id: string = null): DOM { + var elem: HTMLElement = document.createElement(element), + cont: DOM = new DOM(elem, DOMSelector.ELEMENT); + + if (className !== null) { + cont.SetClass(className); + } + + if (id !== null) { + cont.SetId(id); + } + + return cont; + } + + public Text(text: string): void { + this.Element.appendChild(document.createTextNode(text)); + } + + public Append(element: DOM): void { + this.Element.appendChild(element.Element); + } + + public Prepend(element: DOM, before: HTMLElement | Node = null): void { + if (before === null) { + before = this.Element.firstChild; + } + + if (this.Element.children.length) { + this.Element.insertBefore(element.Element, before); + } else { + this.Append(element); + } + } + + public SetId(id: string): void { + this.Element.id = id; + } + + public SetClass(name: string): void { + this.Element.className = name; + } + + public Remove(): void { + this.Element.parentNode.removeChild(this.Element); + } + } +} diff --git a/resources/assets/typescript/app/DOMSelector.ts b/resources/assets/typescript/app/DOMSelector.ts new file mode 100644 index 0000000..6829598 --- /dev/null +++ b/resources/assets/typescript/app/DOMSelector.ts @@ -0,0 +1,10 @@ +namespace Sakura +{ + export enum DOMSelector + { + ID, + CLASS, + ELEMENT, + QUERY + } +} diff --git a/resources/assets/typescript/app/Friend.ts b/resources/assets/typescript/app/Friend.ts new file mode 100644 index 0000000..3ed7651 --- /dev/null +++ b/resources/assets/typescript/app/Friend.ts @@ -0,0 +1,47 @@ +declare var ajaxBusyView: any; + +namespace Sakura +{ + export class Friend + { + private static Client: AJAX; + + public static Init(): void + { + this.Client = new AJAX; + this.Client.Form(); + } + + public static Add(id: number): void + { + this.Mode("/friends/1/add".replace('1', id.toString())); + } + + public static Remove(id: number): void + { + this.Mode("/friends/1/remove".replace('1', id.toString())); + } + + private static Mode(url: string): void + { + this.Client.SetUrl(url); + this.Client.SetSend({ "session": Config.SessionId }); + this.Client.AddCallback(200, (client: AJAX) => { + var response: IFriendResponse = client.JSON(), + error: string = response.error || null; + + if (error !== null) { + ajaxBusyView(true, error, 'fail'); + + setTimeout(() => { + ajaxBusyView(false); + }, 1500); + } else { + ajaxBusyView(true, response.message, 'ok'); + location.reload(); + } + }); + this.Client.Start(HTTPMethod.POST); + } + } +} diff --git a/resources/assets/typescript/app/HTTPMethod.ts b/resources/assets/typescript/app/HTTPMethod.ts new file mode 100644 index 0000000..26c9240 --- /dev/null +++ b/resources/assets/typescript/app/HTTPMethod.ts @@ -0,0 +1,11 @@ +namespace Sakura +{ + export enum HTTPMethod + { + GET, + HEAD, + POST, + PUT, + DELETE + } +} diff --git a/resources/assets/typescript/app/IChangelogAction.ts b/resources/assets/typescript/app/IChangelogAction.ts new file mode 100644 index 0000000..75a6d64 --- /dev/null +++ b/resources/assets/typescript/app/IChangelogAction.ts @@ -0,0 +1,8 @@ +namespace Sakura +{ + export interface IChangelogAction + { + id: number; + name: string; + } +} diff --git a/resources/assets/typescript/app/IChangelogChange.ts b/resources/assets/typescript/app/IChangelogChange.ts new file mode 100644 index 0000000..c60e046 --- /dev/null +++ b/resources/assets/typescript/app/IChangelogChange.ts @@ -0,0 +1,12 @@ +namespace Sakura +{ + export interface IChangelogChange + { + id: number; + action?: IChangelogAction; + contributor?: IChangelogContributor; + major: boolean; + url: string; + message: string; + } +} diff --git a/resources/assets/typescript/app/IChangelogContributor.ts b/resources/assets/typescript/app/IChangelogContributor.ts new file mode 100644 index 0000000..696b75d --- /dev/null +++ b/resources/assets/typescript/app/IChangelogContributor.ts @@ -0,0 +1,9 @@ +namespace Sakura +{ + export interface IChangelogContributor + { + id: number; + name: string; + url: string; + } +} diff --git a/resources/assets/typescript/app/IChangelogDate.ts b/resources/assets/typescript/app/IChangelogDate.ts new file mode 100644 index 0000000..da3e804 --- /dev/null +++ b/resources/assets/typescript/app/IChangelogDate.ts @@ -0,0 +1,9 @@ +namespace Sakura +{ + export interface IChangelogDate + { + date: string; + release?: IChangelogRelease; + changes: IChangelogChange[]; + } +} diff --git a/resources/assets/typescript/app/IChangelogRelease.ts b/resources/assets/typescript/app/IChangelogRelease.ts new file mode 100644 index 0000000..7eaa76e --- /dev/null +++ b/resources/assets/typescript/app/IChangelogRelease.ts @@ -0,0 +1,9 @@ +namespace Sakura +{ + export interface IChangelogRelease + { + id: number; + colour: string; + name: string; + } +} diff --git a/resources/assets/typescript/app/IFriendResponse.ts b/resources/assets/typescript/app/IFriendResponse.ts new file mode 100644 index 0000000..de71dec --- /dev/null +++ b/resources/assets/typescript/app/IFriendResponse.ts @@ -0,0 +1,9 @@ +namespace Sakura +{ + export interface IFriendResponse + { + message: string; + level: number; + error: string; + } +} diff --git a/resources/assets/typescript/app/Legacy.ts b/resources/assets/typescript/app/Legacy.ts new file mode 100644 index 0000000..3ee288f --- /dev/null +++ b/resources/assets/typescript/app/Legacy.ts @@ -0,0 +1,76 @@ +declare function escape(a); + +namespace Sakura +{ + export class Legacy + { + // Alternative for Math.log2() since it's still experimental + public static log2(num: number): number { + return Math.log(num) / Math.log(2); + } + + // Get the number of unique characters in a string + public static unique(string: string): number { + // Store the already found character + var used: string[] = []; + + // The amount of characters we've already found + var count: number = 0; + + // Count the amount of unique characters + for (var i = 0; i < string.length; i++) { + // Check if we already counted this character + if (used.indexOf(string[i]) == -1) { + // Push the character into the used array + used.push(string[i]); + + // Up the count + count++; + } + } + + // Return the count + return count; + } + + // Calculate password entropy + public static entropy(string: string): number { + // Decode utf-8 encoded characters + string = this.utf8_decode(string); + + // Count the unique characters in the string + var unique: number = this.unique(string); + + // Do the entropy calculation + return unique * this.log2(256); + } + + // Validate string lengths + public static stringLength(string: string, minimum: number, maximum: number): boolean { + // Get length of string + var length = string.length; + + // Check if it meets the minimum/maximum + if (length < minimum || length > maximum) { + return false; + } + + // If it passes both return true + return true; + } + + // Validate email address formats + public static validateEmail(email: string): boolean { + // RFC compliant e-mail address regex + var re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,48})+$/; + + // Test it on the email var which'll return a boolean + return re.test(email); + } + + // Decode a utf-8 string + public static utf8_decode(string): string { + return decodeURIComponent(escape(string)); + } + } +} diff --git a/resources/assets/typescript/app/Main.ts b/resources/assets/typescript/app/Main.ts new file mode 100644 index 0000000..6add911 --- /dev/null +++ b/resources/assets/typescript/app/Main.ts @@ -0,0 +1,15 @@ +namespace Sakura +{ + export class Main + { + public static Startup(): void { + console.log(this.Supported()); + TimeAgo.Init(); + Friend.Init(); + } + + public static Supported(): boolean { + return true; + } + } +} diff --git a/resources/assets/typescript/app/TimeAgo.ts b/resources/assets/typescript/app/TimeAgo.ts new file mode 100644 index 0000000..1441e90 --- /dev/null +++ b/resources/assets/typescript/app/TimeAgo.ts @@ -0,0 +1,56 @@ +namespace Sakura +{ + export class TimeAgo + { + private static WatchClass: string = "time-ago"; + + public static Init(): void + { + var watchElements: NodeListOf = document.getElementsByClassName(this.WatchClass); + + for (var _i in watchElements) { + if ((typeof watchElements[_i]).toLowerCase() !== 'object') { + continue; + } + + var elem: HTMLElement = watchElements[_i], + date: Date = new Date(elem.getAttribute('dateTime') || elem.innerText); + + elem.title = elem.innerText; + elem.innerText = this.Parse(date); + } + } + + public static Parse(date: Date, append: string = ' ago', none: string = 'Just now'): string { + var time: number = (Date.now() - date.getTime()) / 1000; + + if (time < 1) { + return none; + } + + var times: Object = { + 31536000: ['year', 'a'], + 2592000: ['month', 'a'], + 604800: ['week', 'a'], + 86400: ['day', 'a'], + 3600: ['hour', 'an'], + 60: ['minute', 'a'], + 1: ['second', 'a'] + }; + + var timeKeys: string[] = Object.keys(times).reverse(); + + for (var i in timeKeys) { + var calc: number = time / parseInt(timeKeys[i]); + + if (calc >= 1) { + var display: number = Math.floor(calc); + + return (display === 1 ? times[timeKeys[i]][1] : display) + " " + times[timeKeys[i]][0] + (display === 1 ? '' : 's') + append; + } + } + + return none; + } + } +} diff --git a/resources/assets/typescript/master/legacy.ts b/resources/assets/typescript/master/legacy.ts deleted file mode 100644 index f6e7888..0000000 --- a/resources/assets/typescript/master/legacy.ts +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Shared client side code - */ - -// Meta functions -class Sakura { - public static cookiePrefix: string = ""; // Cookie prefix, gets prepended to cookie names - public static cookiePath: string = "/"; // Cookie path, can in most cases be left untouched - - // Get or set a cookie value - public static cookie(name: string, value: string = null): string { - // If value is null only get the cookie's value - if (value) { - // Delete the old instance - document.cookie = this.cookiePrefix + name + '=;expires=Thu, 01 Jan 1970 00:00:01 GMT; path=' + this.cookiePath; - - // Assign the cookie - document.cookie = this.cookiePrefix + name + '=' + value + '; path=' + this.cookiePath; - - // Pass the value through - return value; - } else { - // Perform a regex on document.cookie - var get = new RegExp('(^|; )' + encodeURIComponent(this.cookiePrefix + name) + '=([^;]*)').exec(document.cookie); - - // If anything was returned return it (professional phrasing) - return get ? get[2] : ''; - } - } - - // Unix timestamp - public static epoch(): number { - return Math.floor(Date.now() / 1000); - } - - // Toggle a class - public static toggleClass(element: HTMLElement, name: string): void { - // Check if the class already exists and if not add it - if (element.className.indexOf(name) < 0) { - element.className += ' ' + name; - } else { // If so remove it and kill additional spaces - element.className = element.className.replace(name, '').trim(); - } - } - - // Remove every element with a specific class name - public static removeByClass(name: string): void { - // Get the elements - var objs = document.getElementsByClassName(name); - - // Use a while loop to remove each element - while (objs.length > 0) { - objs[0].parentNode.removeChild(objs[0]); - } - } - - // Remove a single element with a specific id - public static removeById(id: string): void { - // Get the element - var obj = document.getElementById(id); - - // If the element exists use the parent node to remove it - if (typeof (obj) != "undefined" && obj !== null) { - obj.parentNode.removeChild(obj); - } - } - - // Alternative for Math.log2() since it's still experimental - public static log2(num: number): number { - return Math.log(num) / Math.log(2); - } - - // Get the number of unique characters in a string - public static unique(string: string): number { - // Store the already found character - var used: string[] = []; - - // The amount of characters we've already found - var count: number = 0; - - // Count the amount of unique characters - for (var i = 0; i < string.length; i++) { - // Check if we already counted this character - if (used.indexOf(string[i]) == -1) { - // Push the character into the used array - used.push(string[i]); - - // Up the count - count++; - } - } - - // Return the count - return count; - } - - // Calculate password entropy - public static entropy(string: string): number { - // Decode utf-8 encoded characters - string = utf8.decode(string); - - // Count the unique characters in the string - var unique: number = this.unique(string); - - // Do the entropy calculation - return unique * this.log2(256); - } - - // Validate string lengths - public static stringLength(string: string, minimum: number, maximum: number): boolean { - // Get length of string - var length = string.length; - - // Check if it meets the minimum/maximum - if (length < minimum || length > maximum) { - return false; - } - - // If it passes both return true - return true; - } - - // Validate email address formats - public static validateEmail(email: string): boolean { - // RFC compliant e-mail address regex - var re = /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,48})+$/; - - // Test it on the email var which'll return a boolean - return re.test(email); - } - - // Calculate the time that has elapsed since a certain data (doesn't take leap years in account). - public static timeElapsed(timestamp: number, append: string = ' ago', none: string = 'Just now'): string { - // Subtract the entered timestamp from the current timestamp - var time: number = this.epoch() - timestamp; - - // If the new timestamp is below 1 return a standard string - if (time < 1) { - return none; - } - - // Times array - var times: Object = { - 31536000: ['year', 'a'], - 2592000: ['month', 'a'], - 604800: ['week', 'a'], - 86400: ['day', 'a'], - 3600: ['hour', 'an'], - 60: ['minute', 'a'], - 1: ['second', 'a'] - }; - - // - var timeKeys: string[] = Object.keys(times).reverse(); - - // Iterate over the times - for (var i in timeKeys) { - // Do a devision to check if the given timestamp fits in the current "type" - var calc: number = time / parseInt(timeKeys[i]); - - // Check if we have a match - if (calc >= 1) { - // Round the number - var display: number = Math.floor(calc); - - // Return the formatted string - return (display === 1 ? times[timeKeys[i]][1] : display) + " " + times[timeKeys[i]][0] + (display === 1 ? '' : 's') + append; - } - } - - // If everything fails just return none - return none; - } -} - -declare function unescape(a); -declare function escape(a); - -// UTF-8 functions -class utf8 { - // Encode a utf-8 string - public static encode(string): string { - return unescape(encodeURIComponent(string)); - } - - // Decode a utf-8 string - public static decode(string): string { - return decodeURIComponent(escape(string)); - } -} - -// HTTP methods -enum HTTPMethods { - GET, - HEAD, - POST, - PUT, - DELETE -} - -// AJAX functions -class AJAX { - // XMLHTTPRequest container - private request: XMLHttpRequest; - private callbacks: Object; - private headers: Object; - private url: string; - private send: string = null; - - // Prepares the XMLHttpRequest and stuff - constructor() { - this.request = new XMLHttpRequest(); - this.callbacks = new Object(); - this.headers = new Object(); - } - - // Start - public start(method: HTTPMethods): void { - // Open the connection - this.request.open(HTTPMethods[method], this.url, true); - - // Set headers - this.prepareHeaders(); - - // Watch the ready state - this.request.onreadystatechange = () => { - // Only invoke when complete - if (this.request.readyState === 4) { - // Check if a callback if present - if ((typeof this.callbacks[this.request.status]).toLowerCase() === 'function') { - this.callbacks[this.request.status](); - } else { // Else check if there's a generic fallback present - if ((typeof this.callbacks['0']).toLowerCase() === 'function') { - // Call that - this.callbacks['0'](); - } - } - } - } - - this.request.send(this.send); - } - - // Stop - public stop(): void { - this.request = null; - } - - // Add post data - public setSend(data: Object): void { - // Storage array - var store: Array = new Array(); - - // Iterate over the object and them in the array with an equals sign inbetween - for (var item in data) { - store.push(encodeURIComponent(item) + "=" + encodeURIComponent(data[item])); - } - - // Assign to send - this.send = store.join('&'); - } - - // Set raw post - public setRawSend(data: string) { - this.send = data; - } - - // Get response - public response(): string { - return this.request.responseText; - } - - // Set charset - public contentType(type: string, charset: string = null): void { - this.addHeader('Content-Type', type + ';charset=' + (charset ? charset : 'utf-8')); - } - - // Add a header - public addHeader(name: string, value: string): void { - // Attempt to remove a previous instance - this.removeHeader(name); - - // Add the new header - this.headers[name] = value; - } - - // Remove a header - public removeHeader(name: string): void { - if ((typeof this.headers[name]).toLowerCase() !== 'undefined') { - delete this.headers[name]; - } - } - - // Prepare request headers - public prepareHeaders(): void { - for (var header in this.headers) { - this.request.setRequestHeader(header, this.headers[header]); - } - } - - // Adds a callback - public addCallback(status: number, callback: Function): void { - // Attempt to remove previous instances - this.removeCallback(status); - - // Add the new callback - this.callbacks[status] = callback; - } - - // Delete a callback - public removeCallback(status: number): void { - // Delete the callback if present - if ((typeof this.callbacks[status]).toLowerCase() === 'function') { - delete this.callbacks[status]; - } - } - - // Sets the URL - public setUrl(url: string): void { - this.url = url; - } -} diff --git a/resources/assets/typescript/yuuno/Main.ts b/resources/assets/typescript/yuuno/Main.ts new file mode 100644 index 0000000..d37f912 --- /dev/null +++ b/resources/assets/typescript/yuuno/Main.ts @@ -0,0 +1,10 @@ +namespace Yuuno +{ + export class Main + { + public static Startup() + { + Sakura.Main.Startup(); + } + } +} diff --git a/resources/assets/typescript/yuuno/yuuno.ts b/resources/assets/typescript/yuuno/yuuno.ts index c18e2a7..d317652 100644 --- a/resources/assets/typescript/yuuno/yuuno.ts +++ b/resources/assets/typescript/yuuno/yuuno.ts @@ -101,7 +101,7 @@ function notifyClose(id: string): void { // Remove after 410 ms setTimeout(() => { - Sakura.removeById(id); + (new Sakura.DOM(id, Sakura.DOMSelector.ID)).Remove(); }, 410); } @@ -122,13 +122,13 @@ function notifyRequest(session: string): void { } // Create AJAX object - var get: any = new AJAX(); - get.setUrl('/notifications'); + var get = new Sakura.AJAX(); + get.SetUrl('/notifications'); // Add callbacks - get.addCallback(200, () => { + get.AddCallback(200, () => { // Assign the parsed JSON - var data: Notification = JSON.parse(get.response()); + var data: Notification = JSON.parse(get.Response()); // Check if nothing went wrong if ((typeof data).toLowerCase() === 'undefined') { @@ -142,7 +142,7 @@ function notifyRequest(session: string): void { } }); - get.start(HTTPMethods.GET); + get.Start(Sakura.HTTPMethod.GET); } // Show the full page busy window @@ -216,7 +216,7 @@ function ajaxBusyView(show: boolean, message: string = null, type: string = null if (parseInt(cont.style.opacity) > 0) { cont.style.opacity = (parseInt(cont.style.opacity) - 0.1).toString(); } else { - Sakura.removeById('ajaxBusy'); + (new Sakura.DOM('ajaxBusy', Sakura.DOMSelector.ID)).Remove(); clearInterval(out); } }, 10); @@ -227,29 +227,29 @@ function ajaxBusyView(show: boolean, message: string = null, type: string = null // Making a post request using AJAX function ajaxPost(url: string, data: Object, callback: Function) { // Create AJAX - var request: any = new AJAX(); + var request = new Sakura.AJAX; // Set url - request.setUrl(url); + request.SetUrl(url); // Add callbacks - request.addCallback(200, function() { - callback.call(request.response()) + request.AddCallback(200, function() { + callback.call(request.Response()) }); - request.addCallback(0, function() { + request.AddCallback(0, function() { ajaxBusyView(false); throw "POST Request failed"; }); // Add header - request.addHeader('Content-Type', 'application/x-www-form-urlencoded'); + request.AddHeader('Content-Type', 'application/x-www-form-urlencoded'); // Set the post data - request.setSend(data); + request.SetSend(data); // Make the request - request.start(HTTPMethods.POST); + request.Start(Sakura.HTTPMethod.POST); // Return the AJAX object return request; @@ -399,7 +399,7 @@ function submitPostHandler(data: string, busyView: boolean, resetCaptcha: boolea // Check if a password is within the minimum entropy value function checkPwdEntropy(pwd: string): boolean { - return (Sakura.entropy(pwd) >= sakuraVars.minPwdEntropy); + return (Sakura.Legacy.entropy(pwd) >= sakuraVars.minPwdEntropy); } // Check registration variables @@ -420,12 +420,12 @@ function registerVarCheck(id: string, mode: string, option: any = null): void { break; case 'email': - check = Sakura.validateEmail(input.value); + check = Sakura.Legacy.validateEmail(input.value); break; case 'username': default: - check = Sakura.stringLength(input.value, sakuraVars.minUserLen, sakuraVars.maxUserLen); + check = Sakura.Legacy.stringLength(input.value, sakuraVars.minUserLen, sakuraVars.maxUserLen); break; } diff --git a/resources/views/yuuno/auth/login.twig b/resources/views/yuuno/auth/login.twig index 913e702..fe13095 100644 --- a/resources/views/yuuno/auth/login.twig +++ b/resources/views/yuuno/auth/login.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Login' %} diff --git a/resources/views/yuuno/auth/reactivate.twig b/resources/views/yuuno/auth/reactivate.twig index 9054a4f..b5f0622 100644 --- a/resources/views/yuuno/auth/reactivate.twig +++ b/resources/views/yuuno/auth/reactivate.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Reactivate account' %} diff --git a/resources/views/yuuno/auth/register.twig b/resources/views/yuuno/auth/register.twig index edba845..df42756 100644 --- a/resources/views/yuuno/auth/register.twig +++ b/resources/views/yuuno/auth/register.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Register' %} diff --git a/resources/views/yuuno/auth/resetpassword.twig b/resources/views/yuuno/auth/resetpassword.twig index ffaae4e..8e1a147 100644 --- a/resources/views/yuuno/auth/resetpassword.twig +++ b/resources/views/yuuno/auth/resetpassword.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Reset Password' %} diff --git a/resources/views/yuuno/elements/comments.twig b/resources/views/yuuno/elements/comments.twig index 80c00ec..9775d5d 100644 --- a/resources/views/yuuno/elements/comments.twig +++ b/resources/views/yuuno/elements/comments.twig @@ -26,22 +26,22 @@ {% block js %} {% endblock %} diff --git a/resources/views/yuuno/elements/indexPanel.twig b/resources/views/yuuno/elements/indexPanel.twig index d2515a9..4c69eda 100644 --- a/resources/views/yuuno/elements/indexPanel.twig +++ b/resources/views/yuuno/elements/indexPanel.twig @@ -20,7 +20,7 @@ All active users in the past 2 minutes {% for amount,onlineUser in stats.onlineUsers %} - + {% endfor %}
{{ onlineUser.username }}
{{ onlineUser.username }}
{% else %} diff --git a/resources/views/yuuno/elements/newsPost.twig b/resources/views/yuuno/elements/newsPost.twig index eac73c3..bb09206 100644 --- a/resources/views/yuuno/elements/newsPost.twig +++ b/resources/views/yuuno/elements/newsPost.twig @@ -12,6 +12,6 @@
- Posted + Posted {% if newsHideCommentCount is not defined %}{{ post.commentCount }} comment{% if post.commentCount != 1 %}s{% endif %}{% endif %}
diff --git a/resources/views/yuuno/forum/elements/forumEntry.twig b/resources/views/yuuno/forum/elements/forumEntry.twig index d238497..e80b7c3 100644 --- a/resources/views/yuuno/forum/elements/forumEntry.twig +++ b/resources/views/yuuno/forum/elements/forumEntry.twig @@ -24,7 +24,7 @@
{% if forum.lastPost.id %} {{ forum.lastPost.subject|slice(0, 30) }}{% if forum.lastPost.subject|length > 30 %}...{% endif %}
- by {% if forum.lastPost.poster.id %}{{ forum.lastPost.poster.username }}{% else %}[deleted user]{% endif %} + by {% if forum.lastPost.poster.id %}{{ forum.lastPost.poster.username }}{% else %}[deleted user]{% endif %} {% else %} There are no posts in this forum.
  {% endif %} diff --git a/resources/views/yuuno/forum/elements/replyForm.twig b/resources/views/yuuno/forum/elements/replyForm.twig index b8d95af..9d0f626 100644 --- a/resources/views/yuuno/forum/elements/replyForm.twig +++ b/resources/views/yuuno/forum/elements/replyForm.twig @@ -53,14 +53,14 @@ rText = document.getElementById('previewText'), lastParsed = Date.now(), lastKeystroke = Date.now(), - parser = new AJAX(), - postFetch = new AJAX(), + parser = new Sakura.AJAX(), + postFetch = new Sakura.AJAX(), parserActive = false, op = {{ topic is defined ? topic.firstPost.id : 0 }}, topicName = "{{ topic is defined ? topic.firstPost.subject : '' }}"; - parser.setUrl("{{ route('helper.bbcode.parse') }}"); - parser.contentType("application/x-www-form-urlencoded"); + parser.SetUrl("{{ route('helper.bbcode.parse') }}"); + parser.ContentType("application/x-www-form-urlencoded"); pText.addEventListener("focus", function () { preview.style.display = null; @@ -89,10 +89,10 @@ } else { parserActive = true; - parser.setSend({"text":text}); + parser.SetSend({"text":text}); - parser.addCallback(200, function () { - rText.innerHTML = parser.response(); + parser.AddCallback(200, function () { + rText.innerHTML = parser.Response(); var codeBlocks = rText.querySelectorAll("pre code"); @@ -105,7 +105,7 @@ parserActive = false; }); - parser.start(HTTPMethods.POST); + parser.Start(Sakura.HTTPMethod.POST); } } }, 1000); @@ -156,10 +156,10 @@ url = "{{ route('forums.post.raw', '1') }}".replace('1', id); - postFetch.setUrl(url); + postFetch.SetUrl(url); - postFetch.addCallback(200, function () { - pText.value = postFetch.response(); + postFetch.AddCallback(200, function () { + pText.value = postFetch.Response(); pStopEdit.style.display = null; pForm.action = "{{ route('forums.post.edit', '1') }}".replace('1', id); pMode.innerText = 'Editing #' + id; @@ -172,7 +172,7 @@ pText.focus(); }); - postFetch.start(HTTPMethods.GET); + postFetch.Start(Sakura.HTTPMethod.GET); } function stopEdit() { diff --git a/resources/views/yuuno/forum/elements/topicEntry.twig b/resources/views/yuuno/forum/elements/topicEntry.twig index 539e628..dbe87ec 100644 --- a/resources/views/yuuno/forum/elements/topicEntry.twig +++ b/resources/views/yuuno/forum/elements/topicEntry.twig @@ -22,6 +22,6 @@ {% else %} [deleted user] {% endif %}
- + diff --git a/resources/views/yuuno/forum/forum.twig b/resources/views/yuuno/forum/forum.twig index e05e569..4ae21b3 100644 --- a/resources/views/yuuno/forum/forum.twig +++ b/resources/views/yuuno/forum/forum.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title %}Forums / {{ forum.name }}{% endset %} diff --git a/resources/views/yuuno/forum/index.twig b/resources/views/yuuno/forum/index.twig index 3ce4b30..05c4734 100644 --- a/resources/views/yuuno/forum/index.twig +++ b/resources/views/yuuno/forum/index.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Forums' %} @@ -17,7 +17,7 @@ {{ _t.title }} - + {% endfor %} @@ -36,7 +36,7 @@ by {{ _p.poster.username }} - + {% endfor %} diff --git a/resources/views/yuuno/forum/topic.twig b/resources/views/yuuno/forum/topic.twig index 242f196..f44120d 100644 --- a/resources/views/yuuno/forum/topic.twig +++ b/resources/views/yuuno/forum/topic.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set forumBackLink %}{{ route('forums.forum', forum.id) }}{% endset %} @@ -97,7 +97,7 @@ {% endif %} {% if not (post.poster.permission(constant('Sakura\\Perms\\Site::DEACTIVATED')) or post.poster.permission(constant('Sakura\\Perms\\Site::RESTRICTED')) or user.id == post.poster.id) %} - + {% endif %} @@ -111,7 +111,7 @@ {{ post.subject|slice(0, 50) }}{% if post.subject|length > 50 %}...{% endif %}
diff --git a/resources/views/yuuno/global/confirm.twig b/resources/views/yuuno/global/confirm.twig index a52d8eb..6bb069e 100644 --- a/resources/views/yuuno/global/confirm.twig +++ b/resources/views/yuuno/global/confirm.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Confirmation' %} diff --git a/resources/views/yuuno/global/information.twig b/resources/views/yuuno/global/information.twig index dc3b144..8f4b1d3 100644 --- a/resources/views/yuuno/global/information.twig +++ b/resources/views/yuuno/global/information.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Information' %} diff --git a/resources/views/yuuno/global/restricted.twig b/resources/views/yuuno/global/restricted.twig index ebc7629..bcb87b1 100644 --- a/resources/views/yuuno/global/restricted.twig +++ b/resources/views/yuuno/global/restricted.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Restricted' %} diff --git a/resources/views/yuuno/group/index.twig b/resources/views/yuuno/group/index.twig index 38b9115..fc530f2 100644 --- a/resources/views/yuuno/group/index.twig +++ b/resources/views/yuuno/group/index.twig @@ -1 +1 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} diff --git a/resources/views/yuuno/info/master.twig b/resources/views/yuuno/info/master.twig index 79bcf74..584bf68 100644 --- a/resources/views/yuuno/info/master.twig +++ b/resources/views/yuuno/info/master.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% block content %}
diff --git a/resources/views/yuuno/manage/master.twig b/resources/views/yuuno/manage/master.twig new file mode 100644 index 0000000..11ee709 --- /dev/null +++ b/resources/views/yuuno/manage/master.twig @@ -0,0 +1,27 @@ +{% extends 'master.twig' %} + +{% set title = category ~ ' / ' ~ mode %} + +{% block content %} +
+
+
+ Navigation +
+
+ {% for name, links in navigation %} +
{{ name }}
+ {% for name,link in links %} + {{ name }} + {% endfor %} + {% endfor %} +
+
+
+
{{ title }}
+
{{ block('description') }}
+ {{ block('manageContent') }} +
+
+
+{% endblock %} diff --git a/resources/views/yuuno/manage/overview/index.twig b/resources/views/yuuno/manage/overview/index.twig new file mode 100644 index 0000000..a63f2af --- /dev/null +++ b/resources/views/yuuno/manage/overview/index.twig @@ -0,0 +1,11 @@ +{% extends 'manage/overview/master.twig' %} + +{% set mode = 'Index' %} + +{% block description %} +

A quick overview on what's going on.

+{% endblock %} + +{% block manageContent %} +

wew

+{% endblock %} diff --git a/resources/views/yuuno/manage/overview/master.twig b/resources/views/yuuno/manage/overview/master.twig new file mode 100644 index 0000000..f61f9aa --- /dev/null +++ b/resources/views/yuuno/manage/overview/master.twig @@ -0,0 +1,3 @@ +{% extends 'manage/master.twig' %} + +{% set category = 'Overview' %} diff --git a/resources/views/yuuno/global/master.twig b/resources/views/yuuno/master.twig similarity index 62% rename from resources/views/yuuno/global/master.twig rename to resources/views/yuuno/master.twig index 124882a..77d6886 100644 --- a/resources/views/yuuno/global/master.twig +++ b/resources/views/yuuno/master.twig @@ -1,7 +1,6 @@ - {{ title|default(config('general.name')) }} @@ -10,13 +9,13 @@ {% endif %} {{ block('meta') }} - + {{ block('css') }} - - + + {% if config('dev.show_changelog', true) and stats %} - + Sakura.Changelog.Build(new Sakura.DOM('indexPanel', Sakura.DOMSelector.ID)); {% endif %} + + notifyRequest(Sakura.Config.SessionId); + + setInterval(function() { + notifyRequest(Sakura.Config.SessionId); + }, 60000); + diff --git a/resources/views/yuuno/meta/banned.twig b/resources/views/yuuno/meta/banned.twig index 07f77c7..1867a61 100644 --- a/resources/views/yuuno/meta/banned.twig +++ b/resources/views/yuuno/meta/banned.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'You are banned!' %} diff --git a/resources/views/yuuno/meta/faq.twig b/resources/views/yuuno/meta/faq.twig index 4467790..c79312e 100644 --- a/resources/views/yuuno/meta/faq.twig +++ b/resources/views/yuuno/meta/faq.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Frequently Asked Questions' %} diff --git a/resources/views/yuuno/meta/index.twig b/resources/views/yuuno/meta/index.twig index c401a1b..313af8e 100644 --- a/resources/views/yuuno/meta/index.twig +++ b/resources/views/yuuno/meta/index.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% block content %}
diff --git a/resources/views/yuuno/meta/report.twig b/resources/views/yuuno/meta/report.twig index 38b9115..fc530f2 100644 --- a/resources/views/yuuno/meta/report.twig +++ b/resources/views/yuuno/meta/report.twig @@ -1 +1 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} diff --git a/resources/views/yuuno/meta/search.twig b/resources/views/yuuno/meta/search.twig index ff109ed..fcf0f14 100644 --- a/resources/views/yuuno/meta/search.twig +++ b/resources/views/yuuno/meta/search.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Search' %} diff --git a/resources/views/yuuno/meta/settings.twig b/resources/views/yuuno/meta/settings.twig index 3aae9f6..a68cb6e 100644 --- a/resources/views/yuuno/meta/settings.twig +++ b/resources/views/yuuno/meta/settings.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title %}{{ page.category }} / {{ page.mode }}{% endset %} diff --git a/resources/views/yuuno/news/category.twig b/resources/views/yuuno/news/category.twig index ec9e655..213c918 100644 --- a/resources/views/yuuno/news/category.twig +++ b/resources/views/yuuno/news/category.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'News' %} diff --git a/resources/views/yuuno/news/post.twig b/resources/views/yuuno/news/post.twig index 3262f3e..30f7161 100644 --- a/resources/views/yuuno/news/post.twig +++ b/resources/views/yuuno/news/post.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = post.title %} diff --git a/resources/views/yuuno/premium/complete.twig b/resources/views/yuuno/premium/complete.twig index 131c506..173be87 100644 --- a/resources/views/yuuno/premium/complete.twig +++ b/resources/views/yuuno/premium/complete.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = 'Purchase complete!' %} diff --git a/resources/views/yuuno/premium/index.twig b/resources/views/yuuno/premium/index.twig index d647517..7ca7922 100644 --- a/resources/views/yuuno/premium/index.twig +++ b/resources/views/yuuno/premium/index.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title %}Support {{ config('general.name') }}{% endset %} diff --git a/resources/views/yuuno/settings/account/username.twig b/resources/views/yuuno/settings/account/username.twig index 9acda4a..01b52ca 100644 --- a/resources/views/yuuno/settings/account/username.twig +++ b/resources/views/yuuno/settings/account/username.twig @@ -12,7 +12,7 @@ {% block settingsContent %}

You are {% if not eligible %}not {% endif %}eligible for a name change.

-

{% if user.getUsernameHistory %}Your last name change was .{% else %}This is your first username change.{% endif %}

+

{% if user.getUsernameHistory %}Your last name change was .{% else %}This is your first username change.{% endif %}

{% if eligible %}

Username

diff --git a/resources/views/yuuno/settings/advanced/sessions.twig b/resources/views/yuuno/settings/advanced/sessions.twig index 54da3dc..baeed32 100644 --- a/resources/views/yuuno/settings/advanced/sessions.twig +++ b/resources/views/yuuno/settings/advanced/sessions.twig @@ -26,7 +26,7 @@ {{ s.user_agent }} - + diff --git a/resources/views/yuuno/settings/appearance/_preview.twig b/resources/views/yuuno/settings/appearance/_preview.twig index e0ca61f..8d7bd8d 100644 --- a/resources/views/yuuno/settings/appearance/_preview.twig +++ b/resources/views/yuuno/settings/appearance/_preview.twig @@ -1,11 +1,11 @@ diff --git a/resources/views/yuuno/settings/friends/listing.twig b/resources/views/yuuno/settings/friends/listing.twig index 1a2f9c7..be6f324 100644 --- a/resources/views/yuuno/settings/friends/listing.twig +++ b/resources/views/yuuno/settings/friends/listing.twig @@ -21,7 +21,7 @@
{{ friend.username }}
- +
diff --git a/resources/views/yuuno/settings/friends/requests.twig b/resources/views/yuuno/settings/friends/requests.twig index e603c90..519443d 100644 --- a/resources/views/yuuno/settings/friends/requests.twig +++ b/resources/views/yuuno/settings/friends/requests.twig @@ -21,8 +21,8 @@
{{ friend.username }}
- - + +
diff --git a/resources/views/yuuno/settings/master.twig b/resources/views/yuuno/settings/master.twig index a5422d6..979c909 100644 --- a/resources/views/yuuno/settings/master.twig +++ b/resources/views/yuuno/settings/master.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set title = category ~ ' / ' ~ mode %} diff --git a/resources/views/yuuno/settings/notifications/history.twig b/resources/views/yuuno/settings/notifications/history.twig index 591e6fc..064dd4c 100644 --- a/resources/views/yuuno/settings/notifications/history.twig +++ b/resources/views/yuuno/settings/notifications/history.twig @@ -41,7 +41,7 @@
- +
diff --git a/resources/views/yuuno/user/members.twig b/resources/views/yuuno/user/members.twig index 3ea4627..ee2c0f6 100644 --- a/resources/views/yuuno/user/members.twig +++ b/resources/views/yuuno/user/members.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set sorts = ['boxes', 'rectangles', 'list'] %} {% set sort = get.sort in sorts ? get.sort : sorts[0] %} @@ -80,10 +80,10 @@ {{ user.username }} - + - {% if user.lastOnline == 0 %}Never logged in.{% else %}{% endif %} + {% if user.lastOnline == 0 %}Never logged in.{% else %}{% endif %} {{ user.title }} diff --git a/resources/views/yuuno/user/profile.twig b/resources/views/yuuno/user/profile.twig index d96c5fa..8b68597 100644 --- a/resources/views/yuuno/user/profile.twig +++ b/resources/views/yuuno/user/profile.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% set profileHidden = profile.permission(constant('Sakura\\Perms\\Site::DEACTIVATED')) or (profile.permission(constant('Sakura\\Perms\\Site::RESTRICTED')) and (user.id != profile.id and not user.permission(constant('Sakura\\Perms\\Manage::USE_MANAGE'), constant('Sakura\\Perms::MANAGE')))) %} @@ -84,12 +84,12 @@ {% if profile.isPremium %}Tenshi {% endif %}{{ profile.country }} {{ profile.title }}
- Joined + Joined
{% if profile.lastOnline < 1 %} {{ profile.username }} hasn't logged in yet. {% else %} - Last online + Last online {% endif %} {% if profile.birthday != '0000-00-00' and profile.birthday|split('-')[0] > 0 %}
Age {{ profile.birthday(true) }} years old  @@ -114,7 +114,7 @@ {% else %} {% if user.isFriends(profile.id) != 0 %}{% endif %} - + {% endif %} {% if user.permission(constant('Sakura\\Perms\\Manage::CAN_RESTRICT_USERS'), constant('Sakura\\Perms::MANAGE')) %} diff --git a/resources/views/yuuno/user/report.twig b/resources/views/yuuno/user/report.twig index b982653..718a35f 100644 --- a/resources/views/yuuno/user/report.twig +++ b/resources/views/yuuno/user/report.twig @@ -1,4 +1,4 @@ -{% extends 'global/master.twig' %} +{% extends 'master.twig' %} {% block content %}

I'll actually make reporting a thing, someday...

diff --git a/routes.php b/routes.php index 2dc4855..0de7a17 100644 --- a/routes.php +++ b/routes.php @@ -75,8 +75,8 @@ Router::group(['before' => 'maintenance'], function () { 'chat' => 'chat.redirect', //'irc' => 'chat.irc', 'feedback' => 'forums.index', - //'mcp' => 'manage.index', - //'mcptest' => 'manage.index', + 'mcp' => 'manage.index', + 'mcptest' => 'manage.index', //'report' => 'report.something', //'osu' => 'eventual link to flashii team', //'filehost' => '???', @@ -120,12 +120,12 @@ Router::group(['before' => 'maintenance'], function () { Router::group(['prefix' => 'forum'], function () { // Post Router::group(['prefix' => 'post'], function () { - Router::get('/{id:i}', 'ForumController@post', 'forums.post'); + Router::get('/{id:i}', 'Forum.PostController@find', 'forums.post'); Router::group(['before' => 'loginCheck'], function () { - Router::get('/{id:i}/raw', 'ForumController@postRaw', 'forums.post.raw'); - Router::get('/{id:i}/delete', 'ForumController@deletePost', 'forums.post.delete'); - Router::post('/{id:i}/delete', 'ForumController@deletePost', 'forums.post.delete'); - Router::post('/{id:i}/edit', 'ForumController@editPost', 'forums.post.edit'); + Router::get('/{id:i}/raw', 'Forum.PostController@raw', 'forums.post.raw'); + Router::get('/{id:i}/delete', 'Forum.PostController@delete', 'forums.post.delete'); + Router::post('/{id:i}/delete', 'Forum.PostController@delete', 'forums.post.delete'); + Router::post('/{id:i}/edit', 'Forum.PostController@edit', 'forums.post.edit'); }); }); @@ -142,12 +142,12 @@ Router::group(['before' => 'maintenance'], function () { }); // Forum - Router::get('/', 'ForumController@index', 'forums.index'); - Router::get('/{id:i}', 'ForumController@forum', 'forums.forum'); + Router::get('/', 'Forum.ForumController@index', 'forums.index'); + Router::get('/{id:i}', 'Forum.ForumController@forum', 'forums.forum'); Router::group(['before' => 'loginCheck'], function () { - Router::get('/{id:i}/mark', 'ForumController@markForumRead', 'forums.mark'); - Router::get('/{id:i}/new', 'ForumController@createTopic', 'forums.new'); - Router::post('/{id:i}/new', 'ForumController@createTopic', 'forums.new'); + Router::get('/{id:i}/mark', 'Forum.ForumController@markRead', 'forums.mark'); + Router::get('/{id:i}/new', 'Forum.TopicController@create', 'forums.new'); + Router::post('/{id:i}/new', 'Forum.TopicController@create', 'forums.new'); }); }); @@ -308,16 +308,26 @@ Router::group(['before' => 'maintenance'], function () { }); }); + // Settings + Router::group(['prefix' => 'manage', 'before' => 'loginCheck'], function () { + Router::get('/', function () { + $route = Router::route('manage.overview'); + return header("Location: {$route}"); + }, 'manage.index'); + + // Overview section + Router::group(['prefix' => 'overview'], function () { + Router::get('/', function () { + $route = Router::route('manage.overview.index'); + return header("Location: {$route}"); + }, 'manage.overview'); + + Router::get('/index', 'Manage.OverviewController@index', 'manage.overview.index'); + Router::get('/data', 'Manage.OverviewController@data', 'manage.overview.data'); + }); + }); // Management /* - * General - * - Dashboard - * - Info pages (possibly deprecate with wiki) - * Configuration - * - General - * - Files - * - User - * - Mail * Forums * - Manage * - Settings