bye bye ORM (successfully this time!)
This commit is contained in:
parent
b9f0e88f25
commit
966301c21e
44 changed files with 845 additions and 1316 deletions
|
@ -14,7 +14,6 @@
|
|||
"nesbot/carbon": "~1.22",
|
||||
"illuminate/database": "~5.5",
|
||||
"illuminate/filesystem": "~5.5",
|
||||
"illuminate/pagination": "~5.5",
|
||||
"doctrine/dbal": "~2.6",
|
||||
"swiftmailer/swiftmailer": "~6.0",
|
||||
"erusev/parsedown": "~1.6",
|
||||
|
|
146
composer.lock
generated
146
composer.lock
generated
|
@ -1,10 +1,10 @@
|
|||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "bfc5b8cbdbf22514c4b51ae1af8c333b",
|
||||
"content-hash": "640791cee4f0e5a55e8fe6829d68c99b",
|
||||
"packages": [
|
||||
{
|
||||
"name": "composer/ca-bundle",
|
||||
|
@ -697,16 +697,16 @@
|
|||
},
|
||||
{
|
||||
"name": "illuminate/container",
|
||||
"version": "v5.6.17",
|
||||
"version": "v5.6.22",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/container.git",
|
||||
"reference": "4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a"
|
||||
"reference": "1a29b314dd5c7a5a5bce3dd7b9da924cc1ec22ec"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/container/zipball/4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a",
|
||||
"reference": "4a42d667a05ec6d31f05b532cdac7e8e68e5ea2a",
|
||||
"url": "https://api.github.com/repos/illuminate/container/zipball/1a29b314dd5c7a5a5bce3dd7b9da924cc1ec22ec",
|
||||
"reference": "1a29b314dd5c7a5a5bce3dd7b9da924cc1ec22ec",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -737,20 +737,20 @@
|
|||
],
|
||||
"description": "The Illuminate Container package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"time": "2018-01-21T02:13:38+00:00"
|
||||
"time": "2018-05-14T12:49:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/contracts",
|
||||
"version": "v5.6.17",
|
||||
"version": "v5.6.22",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/contracts.git",
|
||||
"reference": "322ec80498b3bf85bc4025d028e130a9b50242b9"
|
||||
"reference": "3dc639feabe0f302f574157a782ede323881a944"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/contracts/zipball/322ec80498b3bf85bc4025d028e130a9b50242b9",
|
||||
"reference": "322ec80498b3bf85bc4025d028e130a9b50242b9",
|
||||
"url": "https://api.github.com/repos/illuminate/contracts/zipball/3dc639feabe0f302f574157a782ede323881a944",
|
||||
"reference": "3dc639feabe0f302f574157a782ede323881a944",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -781,20 +781,20 @@
|
|||
],
|
||||
"description": "The Illuminate Contracts package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"time": "2018-04-07T17:05:26+00:00"
|
||||
"time": "2018-05-11T23:38:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/database",
|
||||
"version": "v5.6.17",
|
||||
"version": "v5.6.22",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/database.git",
|
||||
"reference": "a949e082dbb520fdcb2798e0a5408669724aa197"
|
||||
"reference": "713d376a2e2b9c7cded8916125c24b7b3adb4984"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/database/zipball/a949e082dbb520fdcb2798e0a5408669724aa197",
|
||||
"reference": "a949e082dbb520fdcb2798e0a5408669724aa197",
|
||||
"url": "https://api.github.com/repos/illuminate/database/zipball/713d376a2e2b9c7cded8916125c24b7b3adb4984",
|
||||
"reference": "713d376a2e2b9c7cded8916125c24b7b3adb4984",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -840,20 +840,20 @@
|
|||
"orm",
|
||||
"sql"
|
||||
],
|
||||
"time": "2018-04-17T12:36:27+00:00"
|
||||
"time": "2018-05-14T22:33:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/filesystem",
|
||||
"version": "v5.6.17",
|
||||
"version": "v5.6.22",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/filesystem.git",
|
||||
"reference": "c9ab9376076cedd88a374d7281d62b619634d578"
|
||||
"reference": "a4ca4a9c2f969ec227748ab334693144995ba0ce"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/c9ab9376076cedd88a374d7281d62b619634d578",
|
||||
"reference": "c9ab9376076cedd88a374d7281d62b619634d578",
|
||||
"url": "https://api.github.com/repos/illuminate/filesystem/zipball/a4ca4a9c2f969ec227748ab334693144995ba0ce",
|
||||
"reference": "a4ca4a9c2f969ec227748ab334693144995ba0ce",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -892,64 +892,20 @@
|
|||
],
|
||||
"description": "The Illuminate Filesystem package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"time": "2018-04-06T13:15:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/pagination",
|
||||
"version": "v5.6.17",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/pagination.git",
|
||||
"reference": "77e9cfd4daf526aab9bf9c75ee1676f3ba6dff51"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/pagination/zipball/77e9cfd4daf526aab9bf9c75ee1676f3ba6dff51",
|
||||
"reference": "77e9cfd4daf526aab9bf9c75ee1676f3ba6dff51",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/contracts": "5.6.*",
|
||||
"illuminate/support": "5.6.*",
|
||||
"php": "^7.1.3"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "5.6-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Illuminate\\Pagination\\": ""
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Taylor Otwell",
|
||||
"email": "taylor@laravel.com"
|
||||
}
|
||||
],
|
||||
"description": "The Illuminate Pagination package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"time": "2018-03-26T13:17:45+00:00"
|
||||
"time": "2018-05-02T16:10:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "illuminate/support",
|
||||
"version": "v5.6.17",
|
||||
"version": "v5.6.22",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/illuminate/support.git",
|
||||
"reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085"
|
||||
"reference": "2ef559ad8840481d5247bd7ebfd04eb37d3f6889"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/illuminate/support/zipball/cc8d6f5cef3a901de6bb7d1b362102a6db001085",
|
||||
"reference": "cc8d6f5cef3a901de6bb7d1b362102a6db001085",
|
||||
"url": "https://api.github.com/repos/illuminate/support/zipball/2ef559ad8840481d5247bd7ebfd04eb37d3f6889",
|
||||
"reference": "2ef559ad8840481d5247bd7ebfd04eb37d3f6889",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -993,7 +949,7 @@
|
|||
],
|
||||
"description": "The Illuminate Support package.",
|
||||
"homepage": "https://laravel.com",
|
||||
"time": "2018-04-17T12:26:47+00:00"
|
||||
"time": "2018-05-12T17:43:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "maxmind-db/reader",
|
||||
|
@ -1099,16 +1055,16 @@
|
|||
},
|
||||
{
|
||||
"name": "nesbot/carbon",
|
||||
"version": "1.26.4",
|
||||
"version": "1.27.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/briannesbitt/Carbon.git",
|
||||
"reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b"
|
||||
"reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e3d9014279133a3cccc01f6a691322a2d5a6a87b",
|
||||
"reference": "e3d9014279133a3cccc01f6a691322a2d5a6a87b",
|
||||
"url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/ef81c39b67200dcd7401c24363dcac05ac3a4fe9",
|
||||
"reference": "ef81c39b67200dcd7401c24363dcac05ac3a4fe9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1143,7 +1099,7 @@
|
|||
"datetime",
|
||||
"time"
|
||||
],
|
||||
"time": "2018-04-17T15:35:42+00:00"
|
||||
"time": "2018-04-23T09:02:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "psr/container",
|
||||
|
@ -1299,7 +1255,7 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
"version": "v4.0.8",
|
||||
"version": "v4.0.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/finder.git",
|
||||
|
@ -1348,16 +1304,16 @@
|
|||
},
|
||||
{
|
||||
"name": "symfony/polyfill-mbstring",
|
||||
"version": "v1.7.0",
|
||||
"version": "v1.8.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b"
|
||||
"reference": "3296adf6a6454a050679cde90f95350ad604b171"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b",
|
||||
"reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b",
|
||||
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/3296adf6a6454a050679cde90f95350ad604b171",
|
||||
"reference": "3296adf6a6454a050679cde90f95350ad604b171",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1369,7 +1325,7 @@
|
|||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "1.7-dev"
|
||||
"dev-master": "1.8-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -1403,20 +1359,20 @@
|
|||
"portable",
|
||||
"shim"
|
||||
],
|
||||
"time": "2018-01-30T19:27:44+00:00"
|
||||
"time": "2018-04-26T10:06:28+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/translation",
|
||||
"version": "v4.0.8",
|
||||
"version": "v4.0.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/translation.git",
|
||||
"reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938"
|
||||
"reference": "ad3abf08eb3450491d8d76513100ef58194cd13e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/e20a9b7f9f62cb33a11638b345c248e7d510c938",
|
||||
"reference": "e20a9b7f9f62cb33a11638b345c248e7d510c938",
|
||||
"url": "https://api.github.com/repos/symfony/translation/zipball/ad3abf08eb3450491d8d76513100ef58194cd13e",
|
||||
"reference": "ad3abf08eb3450491d8d76513100ef58194cd13e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -1437,7 +1393,7 @@
|
|||
"symfony/yaml": "~3.4|~4.0"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "To use logging capability in translator",
|
||||
"psr/log-implementation": "To use logging capability in translator",
|
||||
"symfony/config": "",
|
||||
"symfony/yaml": ""
|
||||
},
|
||||
|
@ -1471,7 +1427,7 @@
|
|||
],
|
||||
"description": "Symfony Translation Component",
|
||||
"homepage": "https://symfony.com",
|
||||
"time": "2018-02-22T10:50:29+00:00"
|
||||
"time": "2018-04-30T01:23:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
|
@ -1896,23 +1852,23 @@
|
|||
},
|
||||
{
|
||||
"name": "phpspec/prophecy",
|
||||
"version": "1.7.5",
|
||||
"version": "1.7.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpspec/prophecy.git",
|
||||
"reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401"
|
||||
"reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/dfd6be44111a7c41c2e884a336cc4f461b3b2401",
|
||||
"reference": "dfd6be44111a7c41c2e884a336cc4f461b3b2401",
|
||||
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
|
||||
"reference": "33a7e3c4fda54e912ff6338c48823bd5c0f0b712",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/instantiator": "^1.0.2",
|
||||
"php": "^5.3|^7.0",
|
||||
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
|
||||
"sebastian/comparator": "^1.1|^2.0",
|
||||
"sebastian/comparator": "^1.1|^2.0|^3.0",
|
||||
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
|
||||
},
|
||||
"require-dev": {
|
||||
|
@ -1955,7 +1911,7 @@
|
|||
"spy",
|
||||
"stub"
|
||||
],
|
||||
"time": "2018-02-19T10:16:54+00:00"
|
||||
"time": "2018-04-18T13:57:24+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
|
|
40
misuzu.php
40
misuzu.php
|
@ -4,6 +4,8 @@ namespace Misuzu;
|
|||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
require_once __DIR__ . '/src/colour.php';
|
||||
require_once __DIR__ . '/src/zalgo.php';
|
||||
require_once __DIR__ . '/src/Users/login_attempt.php';
|
||||
require_once __DIR__ . '/src/Users/validation.php';
|
||||
|
||||
$app = new Application(
|
||||
__DIR__ . '/config/config.ini',
|
||||
|
@ -31,24 +33,44 @@ if (PHP_SAPI !== 'cli') {
|
|||
exit;
|
||||
}
|
||||
|
||||
$app->startTemplating();
|
||||
$app->getTemplating()->addPath('mio', __DIR__ . '/views/mio');
|
||||
|
||||
if (isset($_COOKIE['msz_uid'], $_COOKIE['msz_sid'])) {
|
||||
$app->startSession((int)$_COOKIE['msz_uid'], $_COOKIE['msz_sid']);
|
||||
$session = $app->getSession();
|
||||
|
||||
if ($session !== null) {
|
||||
$session->user->last_seen = \Carbon\Carbon::now();
|
||||
$session->user->last_ip = Net\IPAddress::remote();
|
||||
$session->user->save();
|
||||
if ($app->hasActiveSession()) {
|
||||
$db = Database::connection();
|
||||
|
||||
$bumpUserLast = $db->prepare('
|
||||
UPDATE `msz_users` SET
|
||||
`last_seen` = NOW(),
|
||||
`last_ip` = INET6_ATON(:last_ip)
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$bumpUserLast->bindValue('last_ip', Net\IPAddress::remote()->getString());
|
||||
$bumpUserLast->bindValue('user_id', $app->getUserId());
|
||||
$bumpUserLast->execute();
|
||||
|
||||
$getUserDisplayInfo = $db->prepare('
|
||||
SELECT
|
||||
u.`user_id`, u.`username`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `colour`
|
||||
FROM `msz_users` as u
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$getUserDisplayInfo->bindValue('user_id', $app->getUserId());
|
||||
$userDisplayInfo = $getUserDisplayInfo->execute() ? $getUserDisplayInfo->fetch() : [];
|
||||
$app->getTemplating()->var('current_user', $userDisplayInfo);
|
||||
}
|
||||
}
|
||||
|
||||
$manage_mode = starts_with($_SERVER['REQUEST_URI'], '/manage');
|
||||
|
||||
$app->startTemplating();
|
||||
$app->getTemplating()->addPath('mio', __DIR__ . '/views/mio');
|
||||
|
||||
if ($manage_mode) {
|
||||
if ($app->getSession() === null || $app->getSession()->user->user_id !== 1) {
|
||||
if ($app->getUserId() !== 1) {
|
||||
http_response_code(403);
|
||||
echo $app->getTemplating()->render('errors.403');
|
||||
exit;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
namespace Misuzu;
|
||||
|
||||
exit;
|
||||
use Illuminate\Database\Migrations\DatabaseMigrationRepository;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
|
|
@ -6,19 +6,51 @@
|
|||
|
||||
namespace Misuzu;
|
||||
|
||||
use Misuzu\Users\Role;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Database;
|
||||
|
||||
require_once __DIR__ . '/misuzu.php';
|
||||
|
||||
$role = Role::find(1);
|
||||
$db = Database::connection();
|
||||
|
||||
if ($role === null) {
|
||||
$role = Role::createRole('Member');
|
||||
$mainRoleId = (int)$db->query('
|
||||
SELECT `role_id`
|
||||
FROM `msz_roles`
|
||||
WHERE `role_id` = 1
|
||||
')->fetchColumn();
|
||||
|
||||
if ($mainRoleId !== 1) {
|
||||
$db->query("
|
||||
REPLACE INTO `msz_roles`
|
||||
(`role_id`, `role_name`, `role_hierarchy`, `role_colour`, `role_description`, `created_at`)
|
||||
VALUES
|
||||
(1, 'Member', 1, 1073741824, NULL, NOW())
|
||||
");
|
||||
|
||||
$mainRoleId = 1;
|
||||
}
|
||||
|
||||
foreach (User::all() as $user) {
|
||||
if (!$user->hasRole($role)) {
|
||||
$user->addRole($role);
|
||||
}
|
||||
$notInMainRole = $db->query('
|
||||
SELECT `user_id`
|
||||
FROM `msz_users` as u
|
||||
WHERE NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM `msz_user_roles` as ur
|
||||
WHERE `role_id` = 1
|
||||
AND u.`user_id` = ur.`user_id`
|
||||
)
|
||||
')->fetchAll();
|
||||
|
||||
if (count($notInMainRole) < 1) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$addToMainRole = $db->prepare('
|
||||
INSERT INTO `msz_user_roles`
|
||||
(`user_id`, `role_id`)
|
||||
VALUES
|
||||
(:user_id, 1)
|
||||
');
|
||||
|
||||
foreach ($notInMainRole as $user) {
|
||||
$addToMainRole->execute($user);
|
||||
}
|
||||
|
|
160
public/auth.php
160
public/auth.php
|
@ -1,21 +1,19 @@
|
|||
<?php
|
||||
use Carbon\Carbon;
|
||||
use Misuzu\Database;
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Users\Role;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\Session;
|
||||
use Misuzu\Users\LoginAttempt;
|
||||
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
$db = Database::connection();
|
||||
$config = $app->getConfig();
|
||||
$templating = $app->getTemplating();
|
||||
$session = $app->getSession();
|
||||
|
||||
$username_validation_errors = [
|
||||
'trim' => 'Your username may not start or end with spaces!',
|
||||
'short' => "Your username is too short, it has to be at least " . User::USERNAME_MIN_LENGTH . " characters!",
|
||||
'long' => "Your username is too long, it can't be longer than " . User::USERNAME_MAX_LENGTH . " characters!",
|
||||
'short' => "Your username is too short, it has to be at least " . MSZ_USERNAME_MIN_LENGTH . " characters!",
|
||||
'long' => "Your username is too long, it can't be longer than " . MSZ_USERNAME_MAX_LENGTH . " characters!",
|
||||
'double-spaces' => "Your username can't contain double spaces.",
|
||||
'invalid' => 'Your username contains invalid characters.',
|
||||
'spacing' => 'Please use either underscores or spaces, not both!',
|
||||
|
@ -38,17 +36,20 @@ if (!empty($_REQUEST['email'])) {
|
|||
|
||||
switch ($mode) {
|
||||
case 'logout':
|
||||
if ($session === null) {
|
||||
if (!$app->hasActiveSession()) {
|
||||
header('Location: /');
|
||||
return;
|
||||
}
|
||||
|
||||
// this is temporary, don't scream at me for using md5
|
||||
if (isset($_GET['s']) && tmp_csrf_verify($_GET['s'])) {
|
||||
set_cookie_m('uid', '', -3600);
|
||||
set_cookie_m('sid', '', -3600);
|
||||
$session->delete();
|
||||
$app->setSession(null);
|
||||
$deleteSession = $db->prepare('
|
||||
DELETE FROM `msz_sessions`
|
||||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$deleteSession->bindValue('session_id', $app->getSessionId());
|
||||
$deleteSession->execute();
|
||||
header('Location: /');
|
||||
return;
|
||||
}
|
||||
|
@ -57,7 +58,7 @@ switch ($mode) {
|
|||
break;
|
||||
|
||||
case 'login':
|
||||
if ($session !== null) {
|
||||
if ($app->hasActiveSession()) {
|
||||
header('Location: /');
|
||||
break;
|
||||
}
|
||||
|
@ -66,56 +67,103 @@ switch ($mode) {
|
|||
$auth_login_error = '';
|
||||
|
||||
while ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$ipAddress = IPAddress::remote();
|
||||
$ipAddressObj = IPAddress::remote();
|
||||
$ipAddress = $ipAddressObj->getString();
|
||||
|
||||
if (!isset($_POST['username'], $_POST['password'])) {
|
||||
$auth_login_error = "You didn't fill all the forms!";
|
||||
break;
|
||||
}
|
||||
|
||||
$loginAttempts = LoginAttempt::fromIpAddress(IPAddress::remote())
|
||||
->where('was_successful', false)
|
||||
->where('created_at', '>', Carbon::now()->subHour()->toDateTimeString())
|
||||
->get();
|
||||
$fetchRemainingAttempts = $db->prepare('
|
||||
SELECT 5 - COUNT(`attempt_id`)
|
||||
FROM `msz_login_attempts`
|
||||
WHERE `was_successful` = false
|
||||
AND `created_at` > NOW() - INTERVAL 1 HOUR
|
||||
AND `attempt_ip` = INET6_ATON(:remote_ip)
|
||||
');
|
||||
$fetchRemainingAttempts->bindValue('remote_ip', $ipAddress);
|
||||
$remainingAttempts = $fetchRemainingAttempts->execute()
|
||||
? (int)$fetchRemainingAttempts->fetchColumn()
|
||||
: 0;
|
||||
|
||||
if ($loginAttempts->count() >= 5) {
|
||||
if ($remainingAttempts < 1) {
|
||||
$auth_login_error = 'Too many failed login attempts, try again later.';
|
||||
break;
|
||||
}
|
||||
|
||||
$remainingAttempts -= 1;
|
||||
$username = $_POST['username'] ?? '';
|
||||
$password = $_POST['password'] ?? '';
|
||||
|
||||
$user = User::findLogin($username);
|
||||
$getUser = $db->prepare('
|
||||
SELECT `user_id`, `password`
|
||||
FROM `msz_users`
|
||||
WHERE LOWER(`email`) = LOWER(:email)
|
||||
OR LOWER(`username`) = LOWER(:username)
|
||||
');
|
||||
$getUser->bindValue('email', $username);
|
||||
$getUser->bindValue('username', $username);
|
||||
$userData = $getUser->execute() ? $getUser->fetch() : [];
|
||||
$userId = (int)($userData['user_id'] ?? 0);
|
||||
|
||||
if ($user === null) {
|
||||
LoginAttempt::recordFail($ipAddress, null, $user_agent);
|
||||
$auth_login_error = 'Invalid username or password!';
|
||||
$auth_error_str = "Invalid username or password, {$remainingAttempts} attempt(s) remaining.";
|
||||
|
||||
if ($userId < 1) {
|
||||
user_login_attempt_record(false, null, $ipAddress, $user_agent);
|
||||
$auth_login_error = $auth_error_str;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$user->verifyPassword($password)) {
|
||||
LoginAttempt::recordFail($ipAddress, $user, $user_agent);
|
||||
$auth_login_error = 'Invalid username or password!';
|
||||
if (!password_verify($password, $userData['password'])) {
|
||||
user_login_attempt_record(false, $userId, $ipAddress, $user_agent);
|
||||
$auth_login_error = $auth_error_str;
|
||||
break;
|
||||
}
|
||||
|
||||
LoginAttempt::recordSuccess($ipAddress, $user, $user_agent);
|
||||
user_login_attempt_record(true, $userId, $ipAddress, $user_agent);
|
||||
|
||||
$session = Session::createSession($user, $user_agent, null, $ipAddress);
|
||||
$app->setSession($session);
|
||||
$cookie_life = Carbon::now()->addMonth()->timestamp;
|
||||
set_cookie_m('uid', $session->user_id, $cookie_life);
|
||||
set_cookie_m('sid', $session->session_key, $cookie_life);
|
||||
$sessionKey = bin2hex(random_bytes(32));
|
||||
|
||||
$createSession = $db->prepare('
|
||||
INSERT INTO `msz_sessions`
|
||||
(`user_id`, `session_ip`, `user_agent`, `session_key`, `created_at`, `expires_on`)
|
||||
VALUES
|
||||
(:user_id, INET6_ATON(:session_ip), :user_agent, :session_key, NOW(), NOW() + INTERVAL 1 MONTH)
|
||||
');
|
||||
$createSession->bindValue('user_id', $userId);
|
||||
$createSession->bindValue('session_ip', $ipAddress);
|
||||
$createSession->bindValue('user_agent', $user_agent);
|
||||
$createSession->bindValue('session_key', $sessionKey);
|
||||
|
||||
if (!$createSession->execute()) {
|
||||
$auth_login_error = 'Unable to create new session, contact an administrator.';
|
||||
break;
|
||||
}
|
||||
|
||||
$app->startSession($userId, $sessionKey);
|
||||
$cookieLife = Carbon::now()->addMonth()->timestamp;
|
||||
set_cookie_m('uid', $userId, $cookieLife);
|
||||
set_cookie_m('sid', $sessionKey, $cookieLife);
|
||||
|
||||
// Temporary key generation for chat login.
|
||||
// Should eventually be replaced with a callback login system.
|
||||
// Also uses different cookies since $httponly is required to be false for these.
|
||||
$user->user_chat_key = bin2hex(random_bytes(16));
|
||||
$user->save();
|
||||
$chatKey = bin2hex(random_bytes(16));
|
||||
|
||||
$setChatKey = $db->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET `user_chat_key` = :user_chat_key
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$setChatKey->bindValue('user_chat_key', $chatKey);
|
||||
$setChatKey->bindValue('user_id', $userId);
|
||||
|
||||
if ($setChatKey->execute()) {
|
||||
setcookie('msz_tmp_id', $userId, $cookieLife, '/', '.flashii.net');
|
||||
setcookie('msz_tmp_key', $chatKey, $cookieLife, '/', '.flashii.net');
|
||||
}
|
||||
|
||||
setcookie('msz_tmp_id', $user->user_id, $cookie_life, '/', '.flashii.net');
|
||||
setcookie('msz_tmp_key', $user->user_chat_key, $cookie_life, '/', '.flashii.net');
|
||||
header('Location: /');
|
||||
return;
|
||||
}
|
||||
|
@ -128,7 +176,7 @@ switch ($mode) {
|
|||
break;
|
||||
|
||||
case 'register':
|
||||
if ($session !== null) {
|
||||
if ($app->hasActiveSession()) {
|
||||
header('Location: /');
|
||||
}
|
||||
|
||||
|
@ -149,13 +197,13 @@ switch ($mode) {
|
|||
$password = $_POST['password'] ?? '';
|
||||
$email = $_POST['email'] ?? '';
|
||||
|
||||
$username_validate = User::validateUsername($username, true);
|
||||
$username_validate = user_validate_username($username, true);
|
||||
if ($username_validate !== '') {
|
||||
$auth_register_error = $username_validation_errors[$username_validate];
|
||||
break;
|
||||
}
|
||||
|
||||
$email_validate = User::validateEmail($email, true);
|
||||
$email_validate = user_validate_email($email, true);
|
||||
if ($email_validate !== '') {
|
||||
$auth_register_error = $email_validate === 'in-use'
|
||||
? 'This e-mail address has already been used!'
|
||||
|
@ -163,13 +211,45 @@ switch ($mode) {
|
|||
break;
|
||||
}
|
||||
|
||||
if (User::validatePassword($password) !== '') {
|
||||
if (user_validate_password($password) !== '') {
|
||||
$auth_register_error = 'Your password is too weak!';
|
||||
break;
|
||||
}
|
||||
|
||||
$user = User::createUser($username, $password, $email);
|
||||
$user->addRole(Role::find(1), true);
|
||||
$ipAddress = IPAddress::remote()->getString();
|
||||
$createUser = $db->prepare('
|
||||
INSERT INTO `msz_users`
|
||||
(
|
||||
`username`, `password`, `email`, `register_ip`,
|
||||
`last_ip`, `user_country`, `created_at`, `display_role`
|
||||
)
|
||||
VALUES
|
||||
(
|
||||
:username, :password, :email, INET6_ATON(:register_ip),
|
||||
INET6_ATON(:last_ip), :user_country, NOW(), 1
|
||||
)
|
||||
');
|
||||
$createUser->bindValue('username', $username);
|
||||
$createUser->bindValue('password', password_hash($password, PASSWORD_ARGON2I));
|
||||
$createUser->bindValue('email', $email);
|
||||
$createUser->bindValue('register_ip', $ipAddress);
|
||||
$createUser->bindValue('last_ip', $ipAddress);
|
||||
$createUser->bindValue('user_country', get_country_code($ipAddress));
|
||||
|
||||
if (!$createUser->execute()) {
|
||||
$auth_register_error = 'Something happened?';
|
||||
break;
|
||||
}
|
||||
|
||||
$addRole = $db->prepare('
|
||||
INSERT INTO `msz_user_roles`
|
||||
(`user_id`, `role_id`)
|
||||
VALUES
|
||||
(:user_id, 1)
|
||||
');
|
||||
$addRole->bindValue('user_id', $db->lastInsertId());
|
||||
$addRole->execute();
|
||||
|
||||
$templating->var('auth_register_message', 'Welcome to Flashii! You may now log in.');
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,33 +1,24 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
use Misuzu\News\NewsPost;
|
||||
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
//$featured_news = NewsPost::where('is_featured', true)->orderBy('created_at', 'desc')->take(3)->get();
|
||||
$featuredNews = [];
|
||||
|
||||
$fetchNews = Database::connection()
|
||||
$featuredNews = Database::connection()
|
||||
->query('
|
||||
SELECT
|
||||
SELECT
|
||||
p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`,
|
||||
u.`user_id`, u.`username`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour`
|
||||
FROM `msz_news_posts` as p
|
||||
LEFT JOIN `msz_users` as u
|
||||
ON p.`user_id` = u.`user_id`
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
WHERE p.`is_featured` = true
|
||||
ORDER BY p.`created_at` DESC
|
||||
LIMIT 3
|
||||
');
|
||||
|
||||
while (($newsPost = $fetchNews->fetchObject(NewsPost::class)) !== false) {
|
||||
$featuredNews['post'] = $newsPost;
|
||||
}
|
||||
|
||||
var_dump($featuredNews);
|
||||
FROM `msz_news_posts` as p
|
||||
LEFT JOIN `msz_users` as u
|
||||
ON p.`user_id` = u.`user_id`
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
WHERE p.`is_featured` = true
|
||||
ORDER BY p.`created_at` DESC
|
||||
LIMIT 3
|
||||
')->fetchAll();
|
||||
|
||||
//var_dump(Database::connection()->query('SHOW SESSION STATUS LIKE "Questions"')->fetch());
|
||||
|
||||
echo $app->getTemplating()->render('home.landing', compact('featuredNews'));
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
use Misuzu\Users\Role;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Database;
|
||||
|
||||
require_once __DIR__ . '/../../misuzu.php';
|
||||
|
||||
$db = Database::connection();
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
$is_post_request = $_SERVER['REQUEST_METHOD'] === 'POST';
|
||||
|
@ -11,7 +11,17 @@ $page_id = (int)($_GET['p'] ?? 1);
|
|||
|
||||
switch ($_GET['v'] ?? null) {
|
||||
case 'listing':
|
||||
$manage_users = User::paginate(32, ['*'], 'p', $page_id);
|
||||
$manage_users = $db->query('
|
||||
SELECT
|
||||
u.`user_id`, u.`username`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `colour`
|
||||
FROM `msz_users` as u
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
LIMIT 0, 32
|
||||
')->fetchAll();
|
||||
|
||||
//$manage_users = UserV1::paginate(32, ['*'], 'p', $page_id);
|
||||
$templating->vars(compact('manage_users'));
|
||||
echo $templating->render('@manage.users.listing');
|
||||
break;
|
||||
|
@ -24,19 +34,44 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
}
|
||||
|
||||
$view_user = User::find($user_id);
|
||||
$getUser = $db->prepare('
|
||||
SELECT
|
||||
u.*,
|
||||
INET6_NTOA(u.`register_ip`) as `register_ip_decoded`,
|
||||
INET6_NTOA(u.`last_ip`) as `last_ip_decoded`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `colour`
|
||||
FROM `msz_users` as u
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$getUser->bindValue('user_id', $user_id);
|
||||
$getUser->execute();
|
||||
$manageUser = $getUser->execute() ? $getUser->fetch() : [];
|
||||
|
||||
if ($view_user === null) {
|
||||
if (!$manageUser) {
|
||||
echo 'Could not find that user.';
|
||||
break;
|
||||
}
|
||||
|
||||
$templating->var('view_user', $view_user);
|
||||
$templating->var('view_user', $manageUser);
|
||||
echo $templating->render('@manage.users.view');
|
||||
break;
|
||||
|
||||
case 'roles':
|
||||
$manage_roles = Role::paginate(32, ['*'], 'p', $page_id);
|
||||
$manage_roles = $db->query('
|
||||
SELECT
|
||||
`role_id`, `role_colour`, `role_name`,
|
||||
(
|
||||
SELECT COUNT(`user_id`)
|
||||
FROM `msz_user_roles` as ur
|
||||
WHERE ur.`role_id` = r.`role_id`
|
||||
) as `users`
|
||||
FROM `msz_roles` as r
|
||||
LIMIT 0, 10
|
||||
')->fetchAll();
|
||||
|
||||
//$manage_roles = Role::paginate(10, ['*'], 'p', $page_id);
|
||||
$templating->vars(compact('manage_roles'));
|
||||
echo $templating->render('@manage.users.roles');
|
||||
break;
|
||||
|
@ -97,15 +132,38 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
}
|
||||
|
||||
$edit_role = $role_id < 1 ? new Role : Role::find($role_id);
|
||||
$edit_role->role_name = $role_name;
|
||||
$edit_role->role_hierarchy = $role_hierarchy;
|
||||
$edit_role->role_secret = $role_secret;
|
||||
$edit_role->role_colour = $role_colour;
|
||||
$edit_role->role_description = $role_description;
|
||||
$edit_role->save();
|
||||
if ($role_id < 1) {
|
||||
$updateRole = $db->prepare('
|
||||
INSERT INTO `msz_roles`
|
||||
(`role_name`, `role_hierarchy`, `role_secret`, `role_colour`, `role_description`, `created_at`)
|
||||
VALUES
|
||||
(:role_name, :role_hierarchy, :role_secret, :role_colour, :role_description, NOW())
|
||||
');
|
||||
} else {
|
||||
$updateRole = $db->prepare('
|
||||
UPDATE `msz_roles` SET
|
||||
`role_name` = :role_name,
|
||||
`role_hierarchy` = :role_hierarchy,
|
||||
`role_secret` = :role_secret,
|
||||
`role_colour` = :role_colour,
|
||||
`role_description` = :role_description
|
||||
WHERE `role_id` = :role_id
|
||||
');
|
||||
$updateRole->bindValue('role_id', $role_id);
|
||||
}
|
||||
|
||||
header("Location: ?v=role&r={$edit_role->role_id}");
|
||||
$updateRole->bindValue('role_name', $role_name);
|
||||
$updateRole->bindValue('role_hierarchy', $role_hierarchy);
|
||||
$updateRole->bindValue('role_secret', $role_secret ? 1 : 0);
|
||||
$updateRole->bindValue('role_colour', $role_colour);
|
||||
$updateRole->bindValue('role_description', $role_description);
|
||||
$updateRole->execute();
|
||||
|
||||
if ($role_id < 1) {
|
||||
$role_id = (int)$db->lastInsertId();
|
||||
}
|
||||
|
||||
header("Location: ?v=role&r={$role_id}");
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -115,9 +173,15 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
}
|
||||
|
||||
$edit_role = Role::find($role_id);
|
||||
$getEditRole = $db->prepare('
|
||||
SELECT *
|
||||
FROM `msz_roles`
|
||||
WHERE `role_id` = :role_id
|
||||
');
|
||||
$getEditRole->bindValue('role_id', $role_id);
|
||||
$edit_role = $getEditRole->execute() ? $getEditRole->fetch() : [];
|
||||
|
||||
if ($edit_role === null) {
|
||||
if (!$edit_role) {
|
||||
echo 'invalid role';
|
||||
break;
|
||||
}
|
||||
|
|
111
public/news.php
111
public/news.php
|
@ -1,9 +1,9 @@
|
|||
<?php
|
||||
use Misuzu\News\NewsCategory;
|
||||
use Misuzu\News\NewsPost;
|
||||
use Misuzu\Database;
|
||||
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
$db = Database::connection();
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
$category_id = isset($_GET['c']) ? (int)$_GET['c'] : null;
|
||||
|
@ -11,9 +11,25 @@ $post_id = isset($_GET['n']) ? (int)$_GET['n'] : null;
|
|||
$page_id = (int)($_GET['p'] ?? 1);
|
||||
|
||||
if ($post_id !== null) {
|
||||
$post = NewsPost::find($post_id);
|
||||
$getPost = $db->prepare('
|
||||
SELECT
|
||||
p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`,
|
||||
c.`category_id`, c.`category_name`,
|
||||
u.`user_id`, u.`username`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour`
|
||||
FROM `msz_news_posts` as p
|
||||
LEFT JOIN `msz_news_categories` as c
|
||||
ON p.`category_id` = c.`category_id`
|
||||
LEFT JOIN `msz_users` as u
|
||||
ON p.`user_id` = u.`user_id`
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
WHERE `post_id` = :post_id
|
||||
');
|
||||
$getPost->bindValue(':post_id', $post_id, PDO::PARAM_INT);
|
||||
$post = $getPost->execute() ? $getPost->fetch() : false;
|
||||
|
||||
if ($post === null) {
|
||||
if ($post === false) {
|
||||
http_response_code(404);
|
||||
echo $templating->render('errors.404');
|
||||
return;
|
||||
|
@ -24,31 +40,102 @@ if ($post_id !== null) {
|
|||
}
|
||||
|
||||
if ($category_id !== null) {
|
||||
$category = NewsCategory::find($category_id);
|
||||
$getCategory = $db->prepare('
|
||||
SELECT
|
||||
`category_id`, `category_name`, `category_description`
|
||||
FROM `msz_news_categories`
|
||||
WHERE `category_id` = :category_id
|
||||
');
|
||||
$getCategory->bindValue(':category_id', $category_id, PDO::PARAM_INT);
|
||||
$category = $getCategory->execute() ? $getCategory->fetch() : false;
|
||||
|
||||
if ($category === null) {
|
||||
if ($category === false) {
|
||||
http_response_code(404);
|
||||
echo $templating->render('errors.404');
|
||||
return;
|
||||
}
|
||||
|
||||
$posts = $category->posts()->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id);
|
||||
$getPosts = $db->prepare('
|
||||
SELECT
|
||||
p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`,
|
||||
c.`category_id`, c.`category_name`,
|
||||
u.`user_id`, u.`username`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour`
|
||||
FROM `msz_news_posts` as p
|
||||
LEFT JOIN `msz_news_categories` as c
|
||||
ON p.`category_id` = c.`category_id`
|
||||
LEFT JOIN `msz_users` as u
|
||||
ON p.`user_id` = u.`user_id`
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
WHERE p.`category_id` = :category_id
|
||||
ORDER BY `created_at` DESC
|
||||
LIMIT 0, 5
|
||||
');
|
||||
$getPosts->bindValue('category_id', $category['category_id'], PDO::PARAM_INT);
|
||||
$posts = $getPosts->execute() ? $getPosts->fetchAll() : false;
|
||||
|
||||
if (!is_valid_page($posts, $page_id)) {
|
||||
//$posts = $category->posts()->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id);
|
||||
|
||||
//if (!is_valid_page($posts, $page_id)) {
|
||||
if ($posts === false) {
|
||||
http_response_code(404);
|
||||
echo $templating->render('errors.404');
|
||||
return;
|
||||
}
|
||||
|
||||
$featured = $category->posts()->where('is_featured', 1)->orderBy('created_at', 'desc')->take(10)->get();
|
||||
$getFeatured = $db->prepare('
|
||||
SELECT `post_id`, `post_title`
|
||||
FROM `msz_news_posts`
|
||||
WHERE `category_id` = :category_id
|
||||
AND `is_featured` = true
|
||||
ORDER BY `created_at` DESC
|
||||
LIMIT 10
|
||||
');
|
||||
$getFeatured->bindValue('category_id', $category['category_id'], PDO::PARAM_INT);
|
||||
$featured = $getFeatured->execute() ? $getFeatured->fetchAll() : [];
|
||||
|
||||
echo $templating->render('news.category', compact('category', 'posts', 'featured', 'page_id'));
|
||||
return;
|
||||
}
|
||||
|
||||
$categories = NewsCategory::where('is_hidden', false)->get();
|
||||
$posts = NewsPost::where('is_featured', true)->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id);
|
||||
$getCategories = $db->prepare('
|
||||
SELECT
|
||||
c.`category_id`, c.`category_name`,
|
||||
COUNT(p.`post_id`) AS count
|
||||
FROM `msz_news_categories` as c
|
||||
LEFT JOIN `msz_news_posts` as p
|
||||
ON c.`category_id` = p.`category_id`
|
||||
WHERE `is_hidden` = false
|
||||
GROUP BY c.`category_id`
|
||||
HAVING count > 0
|
||||
');
|
||||
$categories = $getCategories->execute() ? $getCategories->fetchAll() : [];
|
||||
|
||||
if (!is_valid_page($posts, $page_id)) {
|
||||
$getPosts = $db->prepare('
|
||||
SELECT
|
||||
p.`post_id`, p.`post_title`, p.`post_text`, p.`created_at`,
|
||||
c.`category_id`, c.`category_name`,
|
||||
u.`user_id`, u.`username`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour`
|
||||
FROM `msz_news_posts` as p
|
||||
LEFT JOIN `msz_news_categories` as c
|
||||
ON p.`category_id` = c.`category_id`
|
||||
LEFT JOIN `msz_users` as u
|
||||
ON p.`user_id` = u.`user_id`
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON u.`display_role` = r.`role_id`
|
||||
WHERE p.`is_featured` = true
|
||||
AND c.`is_hidden` = false
|
||||
ORDER BY p.`created_at` DESC
|
||||
LIMIT 0, 5
|
||||
');
|
||||
$posts = $getPosts->execute() ? $getPosts->fetchAll() : [];
|
||||
|
||||
//$posts = NewsPost::where('is_featured', true)->orderBy('created_at', 'desc')->paginate(5, ['*'], 'p', $page_id);
|
||||
|
||||
//if (!is_valid_page($posts, $page_id)) {
|
||||
if ($posts === false) {
|
||||
http_response_code(404);
|
||||
echo $templating->render('errors.404');
|
||||
return;
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
use Misuzu\IO\File;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
$user_id = (int)($_GET['u'] ?? 0);
|
||||
$mode = (string)($_GET['m'] ?? 'view');
|
||||
$profile_user = User::find($user_id);
|
||||
|
||||
switch ($mode) {
|
||||
case 'avatar':
|
||||
|
@ -14,8 +13,7 @@ switch ($mode) {
|
|||
$app->getConfig()->get('Avatar', 'default_path', 'string', 'public/images/no-avatar.png')
|
||||
);
|
||||
|
||||
if ($profile_user !== null) {
|
||||
$user_avatar = "{$profile_user->user_id}.msz";
|
||||
$user_avatar = "{$user_id}.msz";
|
||||
$cropped_avatar = $app->getStore('avatars/200x200')->filename($user_avatar);
|
||||
|
||||
if (File::exists($cropped_avatar)) {
|
||||
|
@ -35,7 +33,6 @@ switch ($mode) {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header('Content-Type: ' . mime_content_type($avatar_filename));
|
||||
echo File::readToEnd($avatar_filename);
|
||||
|
@ -45,13 +42,26 @@ switch ($mode) {
|
|||
default:
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
if ($profile_user === null) {
|
||||
$getProfile = Database::connection()->prepare('
|
||||
SELECT
|
||||
u.*,
|
||||
r.`role_title` as `user_title`,
|
||||
COALESCE(r.`role_colour`, CAST(0x40000000 AS UNSIGNED)) as `display_colour`
|
||||
FROM `msz_users` as u
|
||||
LEFT JOIN `msz_roles` as r
|
||||
ON r.`role_id` = u.`display_role`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$getProfile->bindValue('user_id', $user_id);
|
||||
$profile = $getProfile->execute() ? $getProfile->fetch() : [];
|
||||
|
||||
if (!$profile) {
|
||||
http_response_code(404);
|
||||
echo $templating->render('user.notfound');
|
||||
break;
|
||||
}
|
||||
|
||||
$templating->var('profile', $profile_user);
|
||||
$templating->vars(compact('profile'));
|
||||
echo $templating->render('user.view');
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
<?php
|
||||
use Misuzu\Application;
|
||||
use Misuzu\Database;
|
||||
use Misuzu\IO\File;
|
||||
use Misuzu\Users\User;
|
||||
use Misuzu\Users\Session;
|
||||
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
$settings_session = $app->getSession();
|
||||
$db = Database::connection();
|
||||
$templating = $app->getTemplating();
|
||||
|
||||
$page_id = (int)($_GET['p'] ?? 1);
|
||||
|
||||
if (Application::getInstance()->getSession() === null) {
|
||||
if (!$app->hasActiveSession()) {
|
||||
http_response_code(403);
|
||||
echo $templating->render('errors.403');
|
||||
return;
|
||||
|
@ -73,8 +71,6 @@ $settings_profile_fields = [
|
|||
],
|
||||
];
|
||||
|
||||
$settings_user = $settings_session->user;
|
||||
|
||||
$settings_modes = [
|
||||
'account' => 'Account',
|
||||
'avatar' => 'Avatar',
|
||||
|
@ -83,7 +79,7 @@ $settings_modes = [
|
|||
];
|
||||
$settings_mode = $_GET['m'] ?? key($settings_modes);
|
||||
|
||||
$templating->vars(compact('settings_mode', 'settings_modes', 'settings_user', 'settings_session'));
|
||||
$templating->vars(compact('settings_mode', 'settings_modes'));
|
||||
|
||||
if (!array_key_exists($settings_mode, $settings_modes)) {
|
||||
http_response_code(404);
|
||||
|
@ -95,7 +91,7 @@ if (!array_key_exists($settings_mode, $settings_modes)) {
|
|||
$settings_errors = [];
|
||||
|
||||
$prevent_registration = $app->getConfig()->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$avatar_filename = "{$settings_user->user_id}.msz";
|
||||
$avatar_filename = "{$app->getUserId()}.msz";
|
||||
$avatar_max_width = $app->getConfig()->get('Avatar', 'max_width', 'int', 4000);
|
||||
$avatar_max_height = $app->getConfig()->get('Avatar', 'max_height', 'int', 4000);
|
||||
$avatar_max_filesize = $app->getConfig()->get('Avatar', 'max_filesize', 'int', 1000000);
|
||||
|
@ -109,6 +105,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
break;
|
||||
}
|
||||
|
||||
$updatedUserFields = [];
|
||||
|
||||
if (isset($_POST['profile']) && is_array($_POST['profile'])) {
|
||||
foreach ($settings_profile_fields as $name => $props) {
|
||||
if (isset($_POST['profile'][$name])) {
|
||||
|
@ -129,7 +127,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
$field_value = $field_matches[1];
|
||||
}
|
||||
|
||||
$settings_user->{"user_{$name}"} = $field_value;
|
||||
$updatedUserFields["user_{$name}"] = $field_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -141,45 +139,69 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
&& (!empty($_POST['password']['new']) || !empty($_POST['email']['new']))
|
||||
)
|
||||
) {
|
||||
if (!$settings_user->verifyPassword($_POST['current_password'])) {
|
||||
$settings_errors[] = "Your current password was incorrect.";
|
||||
$fetchPassword = $db->prepare('
|
||||
SELECT `password`
|
||||
FROM `msz_users`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$fetchPassword->bindValue('user_id', $app->getUserId());
|
||||
$currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : null;
|
||||
|
||||
if (empty($currentPassword)) {
|
||||
$settings_errors[] = 'Something went horribly wrong.';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!password_verify($_POST['current_password'], $currentPassword)) {
|
||||
$settings_errors[] = 'Your current password was incorrect.';
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($_POST['email']['new'])) {
|
||||
if (empty($_POST['email']['confirm']) || $_POST['email']['new'] !== $_POST['email']['confirm']) {
|
||||
$settings_errors[] = "The given e-mail addresses did not match.";
|
||||
if (empty($_POST['email']['confirm'])
|
||||
|| $_POST['email']['new'] !== $_POST['email']['confirm']) {
|
||||
$settings_errors[] = 'The given e-mail addresses did not match.';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($_POST['email']['new'] === $settings_user->email) {
|
||||
$settings_errors[] = "This is your e-mail address already!";
|
||||
$checkIfAlreadySet = $db->prepare('
|
||||
SELECT COUNT(`user_id`)
|
||||
FROM `msz_users`
|
||||
WHERE LOWER(:email) = LOWER(:email)
|
||||
');
|
||||
$checkIfAlreadySet->bindValue('email', $_POST['email']['new']);
|
||||
$isAlreadySet = $checkIfAlreadySet->execute()
|
||||
? $checkIfAlreadySet->fetchColumn() > 0
|
||||
: false;
|
||||
|
||||
if ($isAlreadySet) {
|
||||
$settings_errors[] = 'This is your e-mail address already!';
|
||||
break;
|
||||
}
|
||||
|
||||
$email_validate = User::validateEmail($_POST['email']['new'], true);
|
||||
$email_validate = user_validate_email($_POST['email']['new'], true);
|
||||
|
||||
if ($email_validate !== '') {
|
||||
switch ($email_validate) {
|
||||
case 'dns':
|
||||
$settings_errors[] = "No valid MX record exists for this domain.";
|
||||
$settings_errors[] = 'No valid MX record exists for this domain.';
|
||||
break;
|
||||
|
||||
case 'format':
|
||||
$settings_errors[] = "The given e-mail address was incorrectly formatted.";
|
||||
$settings_errors[] = 'The given e-mail address was incorrectly formatted.';
|
||||
break;
|
||||
|
||||
case 'in-use':
|
||||
$settings_errors[] = "This e-mail address has already been used by another user.";
|
||||
$settings_errors[] = 'This e-mail address has already been used by another user.';
|
||||
break;
|
||||
|
||||
default:
|
||||
$settings_errors[] = "Unknown e-mail validation error.";
|
||||
$settings_errors[] = 'Unknown e-mail validation error.';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
$settings_user->email = $_POST['email']['new'];
|
||||
$updatedUserFields['email'] = strtolower($_POST['email']['new']);
|
||||
}
|
||||
|
||||
if (!empty($_POST['password']['new'])) {
|
||||
|
@ -189,20 +211,26 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
break;
|
||||
}
|
||||
|
||||
$password_validate = User::validatePassword($_POST['password']['new']);
|
||||
$password_validate = user_validate_password($_POST['password']['new']);
|
||||
|
||||
if ($password_validate !== '') {
|
||||
$settings_errors[] = "The given passwords was too weak.";
|
||||
break;
|
||||
}
|
||||
|
||||
$settings_user->password = $_POST['password']['new'];
|
||||
$updatedUserFields['password'] = password_hash($_POST['password']['new'], PASSWORD_ARGON2I);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($settings_errors) < 1 && $settings_user->isDirty()) {
|
||||
$settings_user->save();
|
||||
if (count($settings_errors) < 1 && count($updatedUserFields) > 0) {
|
||||
$updateUser = $db->prepare('
|
||||
UPDATE `msz_users`
|
||||
SET ' . pdo_prepare_array_update($updatedUserFields, true) . '
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$updatedUserFields['user_id'] = $app->getUserId();
|
||||
$updateUser->execute($updatedUserFields);
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -308,23 +336,34 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
$session_id = (int)($_POST['session'] ?? 0);
|
||||
|
||||
if ($session_id < 1) {
|
||||
$settings_errors[] = 'no';
|
||||
$settings_errors[] = 'Invalid session.';
|
||||
break;
|
||||
}
|
||||
|
||||
$session = Session::find($session_id);
|
||||
$findSession = $db->prepare('
|
||||
SELECT `session_id`, `user_id`
|
||||
FROM `msz_sessions`
|
||||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$findSession->bindValue('session_id', $session_id);
|
||||
$session = $findSession->execute() ? $findSession->fetch() : null;
|
||||
|
||||
if ($session === null || $session->user_id !== $settings_user->user_id) {
|
||||
if (!$session || (int)$session['user_id'] !== $app->getUserId()) {
|
||||
$settings_errors[] = 'You may only end your own sessions.';
|
||||
break;
|
||||
}
|
||||
|
||||
if ($session->session_id === $app->getSession()->session_id) {
|
||||
if ((int)$session['session_id'] === $app->getSessionId()) {
|
||||
header('Location: /auth.php?m=logout&s=' . tmp_csrf_token());
|
||||
return;
|
||||
}
|
||||
|
||||
$session->delete();
|
||||
$deleteSession = $db->prepare('
|
||||
DELETE FROM `msz_sessions`
|
||||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$deleteSession->bindValue('session_id', $session['session_id']);
|
||||
$deleteSession->execute();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -334,11 +373,21 @@ $templating->var('settings_title', $settings_modes[$settings_mode]);
|
|||
|
||||
switch ($settings_mode) {
|
||||
case 'account':
|
||||
$getUserFields = $db->prepare('
|
||||
SELECT ' . pdo_prepare_array($settings_profile_fields, true, '`user_%s`') . '
|
||||
FROM `msz_users`
|
||||
WHERE `user_id` = :user_id
|
||||
');
|
||||
$getUserFields->bindValue('user_id', $app->getUserId());
|
||||
$userFields = $getUserFields->execute() ? $getUserFields->fetch() : [];
|
||||
|
||||
$templating->var('settings_profile_values', $userFields);
|
||||
$templating->vars(compact('settings_profile_fields', 'prevent_registration'));
|
||||
break;
|
||||
|
||||
case 'avatar':
|
||||
$user_has_avatar = File::exists($app->getStore('avatars/original')->filename($avatar_filename));
|
||||
$templating->var('avatar_user_id', $app->getUserId());
|
||||
$templating->vars(compact(
|
||||
'avatar_max_width',
|
||||
'avatar_max_height',
|
||||
|
@ -348,19 +397,44 @@ switch ($settings_mode) {
|
|||
break;
|
||||
|
||||
case 'sessions':
|
||||
$sessions = $settings_user->sessions()
|
||||
/*$sessions = $settings_user->sessions()
|
||||
->orderBy('session_id', 'desc')
|
||||
->paginate(15, ['*'], 'p', $page_id);
|
||||
->paginate(15, ['*'], 'p', $page_id);*/
|
||||
|
||||
$getSessions = $db->prepare('
|
||||
SELECT
|
||||
`session_id`, `session_country`, `user_agent`, `created_at`, `expires_on`,
|
||||
INET6_NTOA(`session_ip`) as `session_ip_decoded`
|
||||
FROM `msz_sessions`
|
||||
WHERE `user_id` = :user_id
|
||||
ORDER BY `session_id` DESC
|
||||
LIMIT 0, 15
|
||||
');
|
||||
$getSessions->bindValue('user_id', $app->getUserId());
|
||||
$sessions = $getSessions->execute() ? $getSessions->fetchAll() : [];
|
||||
|
||||
$templating->var('active_session_id', $app->getSessionId());
|
||||
$templating->var('user_sessions', $sessions);
|
||||
break;
|
||||
|
||||
case 'login-history':
|
||||
$login_attempts = $settings_user->loginAttempts()
|
||||
/*$login_attempts = $settings_user->loginAttempts()
|
||||
->orderBy('attempt_id', 'desc')
|
||||
->paginate(15, ['*'], 'p', $page_id);
|
||||
->paginate(15, ['*'], 'p', $page_id);*/
|
||||
|
||||
$templating->var('user_login_attempts', $login_attempts);
|
||||
$getLoginAttempts = $db->prepare('
|
||||
SELECT
|
||||
`attempt_id`, `attempt_country`, `was_successful`, `user_agent`, `created_at`,
|
||||
INET6_NTOA(`attempt_ip`) as `attempt_ip_decoded`
|
||||
FROM `msz_login_attempts`
|
||||
WHERE `user_id` = :user_id
|
||||
ORDER BY `attempt_id` DESC
|
||||
LIMIT 0, 15
|
||||
');
|
||||
$getLoginAttempts->bindValue('user_id', $app->getUserId());
|
||||
$loginAttempts = $getLoginAttempts->execute() ? $getLoginAttempts->fetchAll() : [];
|
||||
|
||||
$templating->var('user_login_attempts', $loginAttempts);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
23
routes.php
23
routes.php
|
@ -1,23 +0,0 @@
|
|||
<?php
|
||||
use Misuzu\Application;
|
||||
use Misuzu\Controllers\AuthController;
|
||||
use Misuzu\Controllers\HomeController;
|
||||
use Misuzu\Controllers\UserController;
|
||||
|
||||
$routes = Application::getInstance()->router;
|
||||
|
||||
$routes->get(['/', 'main.index'], [HomeController::class, 'index']);
|
||||
|
||||
$routes->group(['prefix' => '/auth'], function ($routes) {
|
||||
$routes->get(['/login', 'auth.login'], [AuthController::class, 'login']);
|
||||
$routes->post(['/login', 'auth.login'], [AuthController::class, 'login']);
|
||||
|
||||
$routes->get(['/logout', 'auth.logout'], [AuthController::class, 'logout']);
|
||||
|
||||
$routes->get(['/register', 'auth.register'], [AuthController::class, 'register']);
|
||||
$routes->post(['/register', 'auth.register'], [AuthController::class, 'register']);
|
||||
});
|
||||
|
||||
$routes->group(['prefix' => '/users'], function ($routes) {
|
||||
$routes->get(['/{id:i}', 'users.view'], [UserController::class, 'view']);
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Misuzu\Config\ConfigManager;
|
||||
use Misuzu\IO\Directory;
|
||||
use Misuzu\IO\DirectoryDoesNotExistException;
|
||||
|
@ -28,10 +29,16 @@ class Application extends ApplicationBase
|
|||
];
|
||||
|
||||
/**
|
||||
* Session instance.
|
||||
* @var \Misuzu\Users\Session
|
||||
* Active Session ID.
|
||||
* @var int
|
||||
*/
|
||||
private $sessionInstance = null;
|
||||
private $currentSessionId = 0;
|
||||
|
||||
/**
|
||||
* Active User ID.
|
||||
* @var int
|
||||
*/
|
||||
private $currentUserId = 0;
|
||||
|
||||
/**
|
||||
* Database instance.
|
||||
|
@ -39,12 +46,6 @@ class Application extends ApplicationBase
|
|||
*/
|
||||
private $databaseInstance = null;
|
||||
|
||||
/**
|
||||
* Database instance.
|
||||
* @var \Misuzu\Database
|
||||
*/
|
||||
private $database;
|
||||
|
||||
/**
|
||||
* ConfigManager instance.
|
||||
* @var \Misuzu\Config\ConfigManager
|
||||
|
@ -108,7 +109,7 @@ class Application extends ApplicationBase
|
|||
*/
|
||||
public function getPath(string $path): string
|
||||
{
|
||||
if (!starts_with($path, '/')) {
|
||||
if (!starts_with($path, '/') && substr($path, 1, 2) !== ':\\') {
|
||||
$path = __DIR__ . '/../' . $path;
|
||||
}
|
||||
|
||||
|
@ -161,40 +162,53 @@ class Application extends ApplicationBase
|
|||
|
||||
/**
|
||||
* Starts a user session.
|
||||
* @param int $user_id
|
||||
* @param string $session_key
|
||||
* @param int $userId
|
||||
* @param string $sessionKey
|
||||
*/
|
||||
public function startSession(int $user_id, string $session_key): void
|
||||
public function startSession(int $userId, string $sessionKey): void
|
||||
{
|
||||
$session = Session::where('session_key', $session_key)
|
||||
->where('user_id', $user_id)
|
||||
->first();
|
||||
$dbc = Database::connection();
|
||||
|
||||
if ($session !== null) {
|
||||
if ($session->hasExpired()) {
|
||||
$session->delete();
|
||||
$findSession = $dbc->prepare('
|
||||
SELECT `session_id`, `expires_on`
|
||||
FROM `msz_sessions`
|
||||
WHERE `user_id` = :user_id
|
||||
AND `session_key` = :session_key
|
||||
');
|
||||
$findSession->bindValue('user_id', $userId);
|
||||
$findSession->bindValue('session_key', $sessionKey);
|
||||
$sessionData = $findSession->execute() ? $findSession->fetch() : false;
|
||||
|
||||
if ($sessionData) {
|
||||
$expiresOn = new Carbon($sessionData['expires_on']);
|
||||
|
||||
if ($expiresOn->isPast()) {
|
||||
$deleteSession = $dbc->prepare('
|
||||
DELETE FROM `msz_sessions`
|
||||
WHERE `session_id` = :session_id
|
||||
');
|
||||
$deleteSession->bindValue('session_id', $sessionData['session_id']);
|
||||
$deleteSession->execute();
|
||||
} else {
|
||||
$this->setSession($session);
|
||||
$this->currentSessionId = (int)$sessionData['session_id'];
|
||||
$this->currentUserId = $userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current session instance.
|
||||
* @return Session|null
|
||||
*/
|
||||
public function getSession(): ?Session
|
||||
public function hasActiveSession(): bool
|
||||
{
|
||||
return $this->sessionInstance;
|
||||
return $this->getSessionId() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a session.
|
||||
* @param Session|null $sessionInstance
|
||||
*/
|
||||
public function setSession(?Session $sessionInstance): void
|
||||
public function getSessionId(): int
|
||||
{
|
||||
$this->sessionInstance = $sessionInstance;
|
||||
return $this->currentSessionId;
|
||||
}
|
||||
|
||||
public function getUserId(): int
|
||||
{
|
||||
return $this->currentUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -206,7 +220,7 @@ class Application extends ApplicationBase
|
|||
throw new UnexpectedValueException('Database module has already been started.');
|
||||
}
|
||||
|
||||
$this->database = new Database($this->configInstance, self::DATABASE_CONNECTIONS[0]);
|
||||
new Database($this->configInstance, self::DATABASE_CONNECTIONS[0]);
|
||||
$this->databaseInstance = new DatabaseV1($this->configInstance, self::DATABASE_CONNECTIONS[0]);
|
||||
$this->loadDatabaseConnections();
|
||||
}
|
||||
|
|
|
@ -136,7 +136,7 @@ final class Database
|
|||
|
||||
$dsn .= 'dbname=' . $this->configManager->get($section, 'database', 'string', 'misuzu') . ';';
|
||||
|
||||
$options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'";
|
||||
$options[PDO::MYSQL_ATTR_INIT_COMMAND] = "SET SESSION sql_mode='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'";
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ class DatabaseV1 extends LaravelDatabaseManager
|
|||
? $this->configManager->get($section, 'collation', 'string')
|
||||
: 'utf8mb4_bin';
|
||||
|
||||
$args['strict'] = true;
|
||||
$args['strict'] = false; // breaks mysql 8
|
||||
$args['engine'] = null;
|
||||
break;
|
||||
|
||||
|
|
|
@ -112,7 +112,7 @@ class ExceptionHandler
|
|||
$is_http = false;//$exception instanceof HttpException;
|
||||
|
||||
if (PHP_SAPI === 'cli' || (!$is_http && static::$debugMode)) {
|
||||
if (PHP_SAPI !== 'cli') {
|
||||
if (PHP_SAPI !== 'cli' && !headers_sent()) {
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/plain');
|
||||
}
|
||||
|
|
|
@ -94,13 +94,18 @@ class Directory
|
|||
throw new DirectoryExistsException;
|
||||
}
|
||||
|
||||
$on_windows = running_on_windows();
|
||||
$path = Directory::fixSlashes($path);
|
||||
$split_path = explode(self::SEPARATOR, $path);
|
||||
$existing_path = running_on_windows() ? '' : self::SEPARATOR;
|
||||
$existing_path = $on_windows ? '' : self::SEPARATOR;
|
||||
|
||||
foreach ($split_path as $path_part) {
|
||||
$existing_path .= $path_part . self::SEPARATOR;
|
||||
|
||||
if ($on_windows && substr($existing_path, 1, 2) === ':\\') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Directory::exists($existing_path)) {
|
||||
mkdir($existing_path);
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model as BaseModel;
|
||||
|
||||
/**
|
||||
* Class Model
|
||||
* @package Misuzu
|
||||
* @property-read \Carbon\Carbon|null $created_at
|
||||
* @property-read \Carbon\Carbon|null $updated_at
|
||||
*/
|
||||
abstract class Model extends BaseModel
|
||||
{
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\News;
|
||||
|
||||
use Misuzu\Model;
|
||||
|
||||
/**
|
||||
* Class NewsCategory
|
||||
* @package Misuzu\News
|
||||
* @property-read int $category_id
|
||||
* @property string $category_name
|
||||
* @property string $category_description
|
||||
* @property bool $is_hidden
|
||||
* @property-read array $posts
|
||||
*/
|
||||
final class NewsCategory extends Model
|
||||
{
|
||||
protected $table = 'news_categories';
|
||||
protected $primaryKey = 'category_id';
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function posts()
|
||||
{
|
||||
return $this->hasMany(NewsPost::class, 'category_id');
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\News;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Misuzu\Database;
|
||||
use Misuzu\Users\User;
|
||||
use Parsedown;
|
||||
|
||||
/**
|
||||
* Class NewsPost
|
||||
* @package Misuzu\News
|
||||
*/
|
||||
final class NewsPost
|
||||
{
|
||||
/**
|
||||
* Parses post_text and returns the final HTML.
|
||||
* @return string
|
||||
*/
|
||||
public function getHtml(): string
|
||||
{
|
||||
return (new Parsedown)->text($this->post_text);
|
||||
}
|
||||
|
||||
public function getUser(): ?User
|
||||
{
|
||||
if (empty($this->user_id) || $this->user_id < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return User::find($this->user_id);
|
||||
}
|
||||
|
||||
public function getCategory(): ?NewsCategory
|
||||
{
|
||||
if (empty($this->category_id) || $this->category_id < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return NewsCategory::find($this->category_id);
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Users;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Misuzu\Model;
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
/**
|
||||
* Class LoginAttempt
|
||||
* @package Misuzu\Users
|
||||
* @property-read int $attempt_id
|
||||
* @property bool $was_successful
|
||||
* @property IPAddress $attempt_ip
|
||||
* @property string $attempt_country
|
||||
* @property int $user_id
|
||||
* @property string $user_agent
|
||||
* @property-read User $user
|
||||
*/
|
||||
class LoginAttempt extends Model
|
||||
{
|
||||
/**
|
||||
* Primary table column.
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'attempt_id';
|
||||
|
||||
/**
|
||||
* Records a successful login attempt.
|
||||
* @param IPAddress $ipAddress
|
||||
* @param User $user
|
||||
* @param null|string $userAgent
|
||||
* @return LoginAttempt
|
||||
*/
|
||||
public static function recordSuccess(IPAddress $ipAddress, User $user, ?string $userAgent = null): LoginAttempt
|
||||
{
|
||||
return static::recordAttempt(true, $ipAddress, $user, $userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a failed login attempt.
|
||||
* @param IPAddress $ipAddress
|
||||
* @param User|null $user
|
||||
* @param null|string $userAgent
|
||||
* @return LoginAttempt
|
||||
*/
|
||||
public static function recordFail(IPAddress $ipAddress, ?User $user = null, ?string $userAgent = null): LoginAttempt
|
||||
{
|
||||
return static::recordAttempt(false, $ipAddress, $user, $userAgent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Records a login attempt.
|
||||
* @param bool $success
|
||||
* @param IPAddress $ipAddress
|
||||
* @param User|null $user
|
||||
* @param null|string $userAgent
|
||||
* @return LoginAttempt
|
||||
*/
|
||||
public static function recordAttempt(
|
||||
bool $success,
|
||||
IPAddress $ipAddress,
|
||||
?User $user = null,
|
||||
?string $userAgent = null
|
||||
): LoginAttempt {
|
||||
$attempt = new static;
|
||||
$attempt->was_successful = $success;
|
||||
$attempt->attempt_ip = $ipAddress;
|
||||
$attempt->user_agent = $userAgent ?? '';
|
||||
|
||||
if ($user !== null) {
|
||||
$attempt->user_id = $user->user_id;
|
||||
}
|
||||
|
||||
$attempt->save();
|
||||
|
||||
return $attempt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all login attempts from a given IP address.
|
||||
* @param IPAddress $ipAddress
|
||||
* @return Builder
|
||||
*/
|
||||
public static function fromIpAddress(IPAddress $ipAddress): Builder
|
||||
{
|
||||
return static::where('attempt_ip', $ipAddress->getRaw());
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the IP address property.
|
||||
* @param IPAddress $ipAddress
|
||||
*/
|
||||
public function setAttemptIpAttribute(IPAddress $ipAddress): void
|
||||
{
|
||||
$this->attributes['attempt_ip'] = $ipAddress->getRaw();
|
||||
$this->attributes['attempt_country'] = $ipAddress->getCountryCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the IP address property.
|
||||
* @param string $ipAddress
|
||||
* @return IPAddress
|
||||
*/
|
||||
public function getAttemptIpAttribute(string $ipAddress): IPAddress
|
||||
{
|
||||
return IPAddress::fromRaw($ipAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object relation definition for User.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
|
@ -1,114 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Users;
|
||||
|
||||
use Misuzu\Model;
|
||||
|
||||
/**
|
||||
* Class Role
|
||||
* @package Misuzu\Users
|
||||
* @property-read int $role_id
|
||||
* @property int $role_hierarchy
|
||||
* @property string $role_name
|
||||
* @property string $role_title
|
||||
* @property string $role_description
|
||||
* @property bool $role_secret
|
||||
* @property int $role_colour
|
||||
* @property-read array $users
|
||||
*/
|
||||
class Role extends Model
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'role_id';
|
||||
|
||||
/**
|
||||
* Creates a new role.
|
||||
* @param string $name
|
||||
* @param int|null $hierarchy
|
||||
* @param int|null $colour
|
||||
* @param null|string $title
|
||||
* @param null|string $description
|
||||
* @param bool $secret
|
||||
* @return Role
|
||||
*/
|
||||
public static function createRole(
|
||||
string $name,
|
||||
?int $hierarchy = null,
|
||||
?int $colour = null,
|
||||
?string $title = null,
|
||||
?string $description = null,
|
||||
bool $secret = false
|
||||
): Role {
|
||||
$hierarchy = $hierarchy ?? 1;
|
||||
$colour = $colour ?? colour_none();
|
||||
|
||||
$role = new Role;
|
||||
$role->role_hierarchy = $hierarchy;
|
||||
$role->role_name = $name;
|
||||
$role->role_title = $title;
|
||||
$role->role_description = $description;
|
||||
$role->role_secret = $secret;
|
||||
$role->role_colour = $colour;
|
||||
$role->save();
|
||||
|
||||
return $role;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds this role to a user.
|
||||
* @param User $user
|
||||
* @param bool $setDisplay
|
||||
*/
|
||||
public function addUser(User $user, bool $setDisplay = false): void
|
||||
{
|
||||
$user->addRole($this, $setDisplay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes this role from a user.
|
||||
* @param User $user
|
||||
*/
|
||||
public function removeUser(User $user): void
|
||||
{
|
||||
$user->removeRole($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this user has this role.
|
||||
* @param User $user
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUser(User $user): bool
|
||||
{
|
||||
return $user->hasRole($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the role_description attribute.
|
||||
* @param null|string $description
|
||||
* @return string
|
||||
*/
|
||||
public function getRoleDescriptionAttribute(?string $description): string
|
||||
{
|
||||
return empty($description) ? '' : $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the role_description attribute.
|
||||
* @param string $description
|
||||
*/
|
||||
public function setRoleDescriptionAttribute(string $description): void
|
||||
{
|
||||
$this->attributes['role_description'] = empty($description) ? null : $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Users relation.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function users()
|
||||
{
|
||||
return $this->hasMany(UserRole::class, 'role_id');
|
||||
}
|
||||
}
|
|
@ -1,108 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Users;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Misuzu\Model;
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
/**
|
||||
* Class Session
|
||||
* @package Misuzu\Users
|
||||
* @property-read int $session_id
|
||||
* @property int $user_id
|
||||
* @property string $session_key
|
||||
* @property IPAddress $session_ip
|
||||
* @property string $user_agent
|
||||
* @property Carbon $expires_on
|
||||
* @property string $session_country
|
||||
* @property-read User $user
|
||||
*/
|
||||
class Session extends Model
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'session_id';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $dates = ['expires_on'];
|
||||
|
||||
/**
|
||||
* Creates a new session object.
|
||||
* @param User $user
|
||||
* @param null|string $userAgent
|
||||
* @param Carbon|null $expires
|
||||
* @param IPAddress|null $ipAddress
|
||||
* @return Session
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createSession(
|
||||
User $user,
|
||||
?string $userAgent = null,
|
||||
?Carbon $expires = null,
|
||||
?IPAddress $ipAddress = null
|
||||
): Session {
|
||||
$ipAddress = $ipAddress ?? IPAddress::remote();
|
||||
$userAgent = $userAgent ?? 'Misuzu';
|
||||
$expires = $expires ?? Carbon::now()->addMonth();
|
||||
|
||||
$session = new Session;
|
||||
$session->user_id = $user->user_id;
|
||||
$session->session_ip = $ipAddress;
|
||||
$session->user_agent = $userAgent;
|
||||
$session->expires_on = $expires;
|
||||
$session->session_key = self::generateKey();
|
||||
$session->save();
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random key.
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function generateKey(): string
|
||||
{
|
||||
return bin2hex(random_bytes(32));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if a session has expired.
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExpired(): bool
|
||||
{
|
||||
return $this->expires_on->isPast();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the session_ip attribute.
|
||||
* @param string $ipAddress
|
||||
* @return IPAddress
|
||||
*/
|
||||
public function getSessionIpAttribute(string $ipAddress): IPAddress
|
||||
{
|
||||
return IPAddress::fromRaw($ipAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the session_ip attribute.
|
||||
* @param IPAddress $ipAddress
|
||||
*/
|
||||
public function setSessionIpAttribute(IPAddress $ipAddress): void
|
||||
{
|
||||
$this->attributes['session_ip'] = $ipAddress->getRaw();
|
||||
$this->attributes['session_country'] = $ipAddress->getCountryCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
|
@ -1,451 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Users;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Misuzu\DatabaseV1;
|
||||
use Misuzu\Model;
|
||||
use Misuzu\Net\IPAddress;
|
||||
|
||||
/**
|
||||
* Class User
|
||||
* @package Misuzu\Users
|
||||
* @property-read int $user_id
|
||||
* @property string $username
|
||||
* @property string $password
|
||||
* @property string $email
|
||||
* @property IPAddress $register_ip
|
||||
* @property IPAddress $last_ip
|
||||
* @property string $user_country
|
||||
* @property Carbon $user_registered
|
||||
* @property string $user_chat_key
|
||||
* @property int $display_role
|
||||
* @property string $user_website
|
||||
* @property string $user_twitter
|
||||
* @property string $user_github
|
||||
* @property string $user_skype
|
||||
* @property string $user_discord
|
||||
* @property string $user_youtube
|
||||
* @property string $user_steam
|
||||
* @property string $user_twitchtv
|
||||
* @property string $user_osu
|
||||
* @property string $user_lastfm
|
||||
* @property string $user_title
|
||||
* @property Carbon $last_seen
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property-read array $sessions
|
||||
* @property-read array $roles
|
||||
* @property-read array $loginAttempts
|
||||
*/
|
||||
class User extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
/**
|
||||
* Define the preferred password hashing algoritm to be used to password_hash.
|
||||
*/
|
||||
private const PASSWORD_HASH_ALGO = PASSWORD_ARGON2I;
|
||||
|
||||
/**
|
||||
* Minimum entropy value for passwords.
|
||||
*/
|
||||
public const PASSWORD_MIN_ENTROPY = 32;
|
||||
|
||||
/**
|
||||
* Minimum username length.
|
||||
*/
|
||||
public const USERNAME_MIN_LENGTH = 3;
|
||||
|
||||
/**
|
||||
* Maximum username length, unless your name is Flappyzor(WorldwideOnline2018).
|
||||
*/
|
||||
public const USERNAME_MAX_LENGTH = 16;
|
||||
|
||||
/**
|
||||
* Username character constraint.
|
||||
*/
|
||||
public const USERNAME_REGEX = '#^[A-Za-z0-9-_ ]+$#u';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $primaryKey = 'user_id';
|
||||
|
||||
/**
|
||||
* Whether the display role has been validated to still be assigned to this user.
|
||||
* @var bool
|
||||
*/
|
||||
private $displayRoleValidated = false;
|
||||
|
||||
/**
|
||||
* Instance of the display role.
|
||||
* @var Role
|
||||
*/
|
||||
private $displayRoleInstance;
|
||||
|
||||
/**
|
||||
* Displayed user title.
|
||||
* @var string
|
||||
*/
|
||||
private $userTitleValue;
|
||||
|
||||
/**
|
||||
* Created a new user.
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param string $email
|
||||
* @param IPAddress|null $ipAddress
|
||||
* @return User
|
||||
*/
|
||||
public static function createUser(
|
||||
string $username,
|
||||
string $password,
|
||||
string $email,
|
||||
?IPAddress $ipAddress = null
|
||||
): User {
|
||||
$ipAddress = $ipAddress ?? IPAddress::remote();
|
||||
|
||||
$user = new User;
|
||||
$user->username = $username;
|
||||
$user->password = $password;
|
||||
$user->email = $email;
|
||||
$user->register_ip = $ipAddress;
|
||||
$user->last_ip = $ipAddress;
|
||||
$user->user_country = $ipAddress->getCountryCode();
|
||||
$user->save();
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find a user for the login page.
|
||||
* @param string $usernameOrEmail
|
||||
* @return User|null
|
||||
*/
|
||||
public static function findLogin(string $usernameOrEmail): ?User
|
||||
{
|
||||
$usernameOrEmail = strtolower($usernameOrEmail);
|
||||
return User::whereRaw("LOWER(`username`) = '{$usernameOrEmail}'")
|
||||
->orWhere('email', $usernameOrEmail)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a username string.
|
||||
* @param string $username
|
||||
* @param bool $checkInUse
|
||||
* @return string
|
||||
*/
|
||||
public static function validateUsername(string $username, bool $checkInUse = false): string
|
||||
{
|
||||
$username_length = strlen($username);
|
||||
|
||||
if ($username !== trim($username)) {
|
||||
return 'trim';
|
||||
}
|
||||
|
||||
if ($username_length < self::USERNAME_MIN_LENGTH) {
|
||||
return 'short';
|
||||
}
|
||||
|
||||
if ($username_length > self::USERNAME_MAX_LENGTH) {
|
||||
return 'long';
|
||||
}
|
||||
|
||||
if (strpos($username, ' ') !== false) {
|
||||
return 'double-spaces';
|
||||
}
|
||||
|
||||
if (!preg_match(self::USERNAME_REGEX, $username)) {
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
if (strpos($username, '_') !== false && strpos($username, ' ') !== false) {
|
||||
return 'spacing';
|
||||
}
|
||||
|
||||
if ($checkInUse && static::whereRaw("LOWER(`username`) = LOWER('{$username}')")->count() > 0) {
|
||||
return 'in-use';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an e-mail string.
|
||||
* @param string $email
|
||||
* @param bool $checkInUse
|
||||
* @return string
|
||||
*/
|
||||
public static function validateEmail(string $email, bool $checkInUse = false): string
|
||||
{
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
return 'format';
|
||||
}
|
||||
|
||||
if (!check_mx_record($email)) {
|
||||
return 'dns';
|
||||
}
|
||||
|
||||
if ($checkInUse && static::whereRaw("LOWER(`email`) = LOWER('{$email}')")->count() > 0) {
|
||||
return 'in-use';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a password string.
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
public static function validatePassword(string $password): string
|
||||
{
|
||||
if (password_entropy($password) < self::PASSWORD_MIN_ENTROPY) {
|
||||
return 'weak';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user's display role, it's probably safe to assume that this will always return a valid role.
|
||||
* @return Role|null
|
||||
*/
|
||||
public function getDisplayRole(): ?Role
|
||||
{
|
||||
if ($this->displayRoleInstance === null) {
|
||||
$this->displayRoleInstance = Role::find($this->display_role);
|
||||
}
|
||||
|
||||
return $this->displayRoleInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display colour.
|
||||
* @return int
|
||||
*/
|
||||
public function getColour(): int
|
||||
{
|
||||
$role = $this->getDisplayRole();
|
||||
return $role === null ? colour_none() : $role->role_colour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the correct user title.
|
||||
* @return string
|
||||
*/
|
||||
private function getUserTitlePrivate(): string
|
||||
{
|
||||
if (!empty($this->user_title)) {
|
||||
return $this->user_title;
|
||||
}
|
||||
|
||||
$role = $this->getDisplayRole();
|
||||
|
||||
if ($role !== null && !empty($role->role_title)) {
|
||||
return $role->role_title;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user title (with memoization).
|
||||
* @return string
|
||||
*/
|
||||
public function getUserTitle(): string
|
||||
{
|
||||
if (empty($this->userTitleValue)) {
|
||||
$this->userTitleValue = $this->getUserTitlePrivate();
|
||||
}
|
||||
|
||||
return $this->userTitleValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a role.
|
||||
* @param Role $role
|
||||
* @param bool $setDisplay
|
||||
*/
|
||||
public function addRole(Role $role, bool $setDisplay = false): void
|
||||
{
|
||||
$relation = new UserRole;
|
||||
$relation->user_id = $this->user_id;
|
||||
$relation->role_id = $role->role_id;
|
||||
$relation->save();
|
||||
|
||||
if ($setDisplay) {
|
||||
$this->display_role = $role->role_id;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a role.
|
||||
* @param Role $role
|
||||
*/
|
||||
public function removeRole(Role $role): void
|
||||
{
|
||||
UserRole::where('user_id', $this->user_id)
|
||||
->where('role_id', $role->user_id)
|
||||
->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a role is assigned.
|
||||
* @param Role $role
|
||||
* @return bool
|
||||
*/
|
||||
public function hasRole(Role $role): bool
|
||||
{
|
||||
return UserRole::where('user_id', $this->user_id)
|
||||
->where('role_id', $role->role_id)
|
||||
->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a password.
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyPassword(string $password): bool
|
||||
{
|
||||
if (password_verify($password, $this->password) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (password_needs_rehash($this->password, self::PASSWORD_HASH_ALGO)) {
|
||||
$this->password = $password;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the display_role attribute.
|
||||
* @param int|null $value
|
||||
* @return int
|
||||
*/
|
||||
public function getDisplayRoleAttribute(?int $value): int
|
||||
{
|
||||
if (!$this->displayRoleValidated) {
|
||||
if ($value === null
|
||||
|| UserRole::where('user_id', $this->user_id)->where('role_id', $value)->count() < 1) {
|
||||
$highestRole = DatabaseV1::table('roles')
|
||||
->join('user_roles', 'roles.role_id', '=', 'user_roles.role_id')
|
||||
->where('user_id', $this->user_id)
|
||||
->orderBy('roles.role_hierarchy', 'desc')
|
||||
->first(['roles.role_id']);
|
||||
|
||||
$value = $highestRole->role_id;
|
||||
$this->display_role = $value;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
$this->displayRoleValidated = true;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the display_role attribute.
|
||||
* @param int $value
|
||||
*/
|
||||
public function setDisplayRoleAttribute(int $value): void
|
||||
{
|
||||
if (UserRole::where('user_id', $this->user_id)->where('role_id', $value)->count() > 0) {
|
||||
$this->attributes['display_role'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param null|string $dateTime
|
||||
* @return Carbon
|
||||
*/
|
||||
public function getLastSeenAttribute(?string $dateTime): Carbon
|
||||
{
|
||||
return $dateTime === null ? Carbon::createFromTimestamp(-1) : new Carbon($dateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the register_ip attribute.
|
||||
* @param string $ipAddress
|
||||
* @return IPAddress
|
||||
*/
|
||||
public function getRegisterIpAttribute(string $ipAddress): IPAddress
|
||||
{
|
||||
return IPAddress::fromRaw($ipAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the register_ip attribute.
|
||||
* @param IPAddress $ipAddress
|
||||
*/
|
||||
public function setRegisterIpAttribute(IPAddress $ipAddress): void
|
||||
{
|
||||
$this->attributes['register_ip'] = $ipAddress->getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the last_ip attribute.
|
||||
* @param string $ipAddress
|
||||
* @return IPAddress
|
||||
*/
|
||||
public function getLastIpAttribute(string $ipAddress): IPAddress
|
||||
{
|
||||
return IPAddress::fromRaw($ipAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the last_ip attribute.
|
||||
* @param IPAddress $ipAddress
|
||||
*/
|
||||
public function setLastIpAttribute(IPAddress $ipAddress): void
|
||||
{
|
||||
$this->attributes['last_ip'] = $ipAddress->getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the password attribute.
|
||||
* @param string $password
|
||||
*/
|
||||
public function setPasswordAttribute(string $password): void
|
||||
{
|
||||
$this->attributes['password'] = password_hash($password, self::PASSWORD_HASH_ALGO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the email attribute.
|
||||
* @param string $email
|
||||
*/
|
||||
public function setEmailAttribute(string $email): void
|
||||
{
|
||||
$this->attributes['email'] = strtolower($email);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function sessions()
|
||||
{
|
||||
return $this->hasMany(Session::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function roles()
|
||||
{
|
||||
return $this->hasMany(UserRole::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function loginAttempts()
|
||||
{
|
||||
return $this->hasMany(LoginAttempt::class, 'user_id');
|
||||
}
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
<?php
|
||||
namespace Misuzu\Users;
|
||||
|
||||
use Misuzu\Model;
|
||||
|
||||
/**
|
||||
* Class UserRole
|
||||
* @package Misuzu\Users
|
||||
* @property int $user_id
|
||||
* @property int $role_id
|
||||
* @property-read User $user
|
||||
* @property-read Role $role
|
||||
*/
|
||||
class UserRole extends Model
|
||||
{
|
||||
protected $primaryKey = null;
|
||||
public $incrementing = false;
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function role()
|
||||
{
|
||||
return $this->belongsTo(Role::class, 'role_id');
|
||||
}
|
||||
}
|
19
src/Users/login_attempt.php
Normal file
19
src/Users/login_attempt.php
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
|
||||
function user_login_attempt_record(bool $success, ?int $userId, string $ipAddress, string $userAgent): void
|
||||
{
|
||||
$storeAttempt = Database::connection()->prepare('
|
||||
INSERT INTO `msz_login_attempts`
|
||||
(`was_successful`, `attempt_ip`, `attempt_country`, `user_id`, `user_agent`, `created_at`)
|
||||
VALUES
|
||||
(:was_successful, INET6_ATON(:attempt_ip), :attempt_country, :user_id, :user_agent, NOW())
|
||||
');
|
||||
|
||||
$storeAttempt->bindValue('was_successful', $success ? 1 : 0);
|
||||
$storeAttempt->bindValue('attempt_ip', $ipAddress);
|
||||
$storeAttempt->bindValue('attempt_country', get_country_code($ipAddress));
|
||||
$storeAttempt->bindValue('user_agent', $userAgent);
|
||||
$storeAttempt->bindValue('user_id', $userId, $userId === null ? PDO::PARAM_NULL : PDO::PARAM_INT);
|
||||
$storeAttempt->execute();
|
||||
}
|
95
src/Users/validation.php
Normal file
95
src/Users/validation.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
use Misuzu\Database;
|
||||
|
||||
// Minimum username length.
|
||||
define('MSZ_USERNAME_MIN_LENGTH', 3);
|
||||
|
||||
// Maximum username length, unless your name is Flappyzor(WorldwideOnline2018).
|
||||
define('MSZ_USERNAME_MAX_LENGTH', 16);
|
||||
|
||||
// Username character constraint.
|
||||
define('MSZ_USERNAME_REGEX', '#^[A-Za-z0-9-_ ]+$#u');
|
||||
|
||||
// Minimum entropy value for passwords.
|
||||
define('MSZ_PASSWORD_MIN_ENTROPY', 32);
|
||||
|
||||
function user_validate_username(string $username, bool $checkInUse = false): string
|
||||
{
|
||||
$username_length = strlen($username);
|
||||
|
||||
if ($username !== trim($username)) {
|
||||
return 'trim';
|
||||
}
|
||||
|
||||
if ($username_length < MSZ_USERNAME_MIN_LENGTH) {
|
||||
return 'short';
|
||||
}
|
||||
|
||||
if ($username_length > MSZ_USERNAME_MAX_LENGTH) {
|
||||
return 'long';
|
||||
}
|
||||
|
||||
if (strpos($username, ' ') !== false) {
|
||||
return 'double-spaces';
|
||||
}
|
||||
|
||||
if (!preg_match(MSZ_USERNAME_REGEX, $username)) {
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
if (strpos($username, '_') !== false && strpos($username, ' ') !== false) {
|
||||
return 'spacing';
|
||||
}
|
||||
|
||||
if ($checkInUse) {
|
||||
$getUser = Database::connection()->prepare('
|
||||
SELECT COUNT(`user_id`)
|
||||
FROM `msz_users`
|
||||
WHERE LOWER(`username`) = LOWER(:username)
|
||||
');
|
||||
$getUser->bindValue('username', $username);
|
||||
$userId = $getUser->execute() ? $getUser->fetchColumn() : 0;
|
||||
|
||||
if ($userId > 0) {
|
||||
return 'in-use';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function user_validate_email(string $email, bool $checkInUse = false): string
|
||||
{
|
||||
if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) {
|
||||
return 'format';
|
||||
}
|
||||
|
||||
if (!check_mx_record($email)) {
|
||||
return 'dns';
|
||||
}
|
||||
|
||||
if ($checkInUse) {
|
||||
$getUser = Database::connection()->prepare('
|
||||
SELECT COUNT(`user_id`)
|
||||
FROM `msz_users`
|
||||
WHERE LOWER(`email`) = LOWER(:email)
|
||||
');
|
||||
$getUser->bindValue('email', $email);
|
||||
$userId = $getUser->execute() ? $getUser->fetchColumn() : 0;
|
||||
|
||||
if ($userId > 0) {
|
||||
return 'in-use';
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
function user_validate_password(string $password): string
|
||||
{
|
||||
if (password_entropy($password) < MSZ_PASSWORD_MIN_ENTROPY) {
|
||||
return 'weak';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
|
@ -2,18 +2,17 @@
|
|||
namespace MisuzuTests;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Misuzu\Users\User;
|
||||
|
||||
class UserTest extends TestCase
|
||||
{
|
||||
public function testUsernameValidation()
|
||||
{
|
||||
$this->assertEquals(User::validateUsername('flashwave'), '');
|
||||
$this->assertEquals(User::validateUsername(' flash '), 'trim');
|
||||
$this->assertEquals(User::validateUsername('f'), 'short');
|
||||
$this->assertEquals(User::validateUsername('flaaaaaaaaaaaaaaaash'), 'long');
|
||||
$this->assertEquals(User::validateUsername('F|@$h'), 'invalid');
|
||||
$this->assertEquals(User::validateUsername('fl ash_wave'), 'spacing');
|
||||
$this->assertEquals(User::validateUsername('fl ash'), 'double-spaces');
|
||||
$this->assertEquals(user_validate_username('flashwave'), '');
|
||||
$this->assertEquals(user_validate_username(' flash '), 'trim');
|
||||
$this->assertEquals(user_validate_username('f'), 'short');
|
||||
$this->assertEquals(user_validate_username('flaaaaaaaaaaaaaaaash'), 'long');
|
||||
$this->assertEquals(user_validate_username('F|@$h'), 'invalid');
|
||||
$this->assertEquals(user_validate_username('fl ash_wave'), 'spacing');
|
||||
$this->assertEquals(user_validate_username('fl ash'), 'double-spaces');
|
||||
}
|
||||
}
|
||||
|
|
36
utility.php
36
utility.php
|
@ -133,22 +133,14 @@ function get_country_name(string $code): string
|
|||
|
||||
// this is temporary, don't scream at me for using md5
|
||||
// BIG TODO: make these functions not dependent on sessions so they can be used outside of those.
|
||||
function tmp_csrf_verify(string $token, ?\Misuzu\Users\Session $session = null): bool
|
||||
function tmp_csrf_verify(string $token): bool
|
||||
{
|
||||
if ($session === null) {
|
||||
$session = \Misuzu\Application::getInstance()->getSession();
|
||||
}
|
||||
|
||||
return hash_equals(tmp_csrf_token($session), $token);
|
||||
return hash_equals(tmp_csrf_token(), $token);
|
||||
}
|
||||
|
||||
function tmp_csrf_token(?\Misuzu\Users\Session $session = null): string
|
||||
function tmp_csrf_token(): string
|
||||
{
|
||||
if ($session === null) {
|
||||
$session = \Misuzu\Application::getInstance()->getSession();
|
||||
}
|
||||
|
||||
return md5($session->session_key);
|
||||
return md5($_COOKIE['msz_sid'] ?? 'this is very insecure lmao');
|
||||
}
|
||||
|
||||
function crop_image_centred_path(string $filename, int $target_width, int $target_height): \Imagick
|
||||
|
@ -218,3 +210,23 @@ function is_valid_page(\Illuminate\Pagination\LengthAwarePaginator $paginator, i
|
|||
{
|
||||
return $attemptedPage >= 1 && $attemptedPage <= $paginator->lastPage();
|
||||
}
|
||||
|
||||
function pdo_prepare_array_update(array $keys, bool $useKeys = false, string $format = '%s'): string
|
||||
{
|
||||
return pdo_prepare_array($keys, $useKeys, sprintf($format, '`%1$s` = :%1$s'));
|
||||
}
|
||||
|
||||
function pdo_prepare_array(array $keys, bool $useKeys = false, string $format = '`%s`'): string
|
||||
{
|
||||
$parts = [];
|
||||
|
||||
if ($useKeys) {
|
||||
$keys = array_keys($keys);
|
||||
}
|
||||
|
||||
foreach ($keys as $key) {
|
||||
$parts[] = sprintf($format, $key);
|
||||
}
|
||||
|
||||
return implode(', ', $parts);
|
||||
}
|
||||
|
|
|
@ -136,10 +136,10 @@
|
|||
<div class="header__user">
|
||||
<div class="header__menu">
|
||||
<input type="checkbox" id="menu-user-state" class="header__menu__state">
|
||||
<label for="menu-user-state" class="header__menu__toggle header__menu__toggle--profile" style="background-image:url('/profile.php?u={{ app.session.user.user_id }}&m=avatar');color:{{ app.session.user.colour|colour_get_css }}">{{ app.session.user.username }}</label>
|
||||
<label for="menu-user-state" class="header__menu__toggle header__menu__toggle--profile" style="background-image:url('/profile.php?u={{ current_user.user_id }}&m=avatar');color:{{ current_user.colour|colour_get_css }}">{{ current_user.username }}</label>
|
||||
<div class="header__menu__options header__menu__options--user">
|
||||
<div class="header__menu__section">
|
||||
<a class="header__menu__link" href="/profile.php?u={{ app.session.user.user_id }}">Profile</a>
|
||||
<a class="header__menu__link" href="/profile.php?u={{ current_user.user_id }}">Profile</a>
|
||||
<a class="header__menu__link" href="/settings.php">Settings</a>
|
||||
</div>
|
||||
<div class="header__menu__section">
|
||||
|
|
|
@ -16,6 +16,6 @@
|
|||
</div>
|
||||
|
||||
<div class="container container--center">
|
||||
{{ paginate(manage_users, '?v=listing') }}
|
||||
{# paginate(manage_users, '?v=listing') #}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -11,13 +11,13 @@
|
|||
<a href="?v=role&r={{ role.role_id }}" class="listing__entry role-listing__entry"{% if not role.role_colour|colour_get_inherit %} style="border-color: {{ role.role_colour|colour_get_css }}"{% endif %}>
|
||||
<div class="listing__entry__content role-listing__entry__content">
|
||||
{{ role.role_name }}
|
||||
{{ role.users.count }} users
|
||||
{{ role.users }} users
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="container container--center">
|
||||
{{ paginate(manage_roles, '?v=roles') }}
|
||||
{# paginate(manage_roles, '?v=roles') #}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -23,14 +23,14 @@
|
|||
<label class="form__label">
|
||||
<div class="form__label__text">Register IP</div>
|
||||
<div class="form__label__input">
|
||||
<input class="input input--text" readonly type="text" value="{{ view_user.register_ip.string }}">
|
||||
<input class="input input--text" readonly type="text" value="{{ view_user.register_ip_decoded }}">
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="form__label">
|
||||
<div class="form__label__text">Last IP</div>
|
||||
<div class="form__label__input">
|
||||
<input class="input input--text" readonly type="text" value="{{ view_user.last_ip.string }}">
|
||||
<input class="input input--text" readonly type="text" value="{{ view_user.last_ip_decoded }}">
|
||||
</div>
|
||||
</label>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
{% set canonical_url = '/' %}
|
||||
|
||||
{% block content %}
|
||||
{% if app.session != null %}
|
||||
{% if app.hasActiveSession %}
|
||||
<div class="container">
|
||||
<div class="container__title">Welcome</div>
|
||||
<div class="container__content">
|
||||
|
|
|
@ -25,17 +25,17 @@
|
|||
</div>
|
||||
|
||||
<div class="header__menu">
|
||||
{% if app.session is not null %}
|
||||
{% if app.hasActiveSession %}
|
||||
<div class="container header__user">
|
||||
<div class="container__title">Hey, {{ app.session.user.username }}!</div>
|
||||
<div class="container__title">Hey, {{ current_user.username }}!</div>
|
||||
<div class="container__content header__user__content">
|
||||
<a href="/settings.php?m=avatar" class="avatar header__user__avatar" style="background-image:url('/profile.php?u={{ app.session.user.user_id }}&m=avatar');"></a>
|
||||
<a href="/settings.php?m=avatar" class="avatar header__user__avatar" style="background-image:url('/profile.php?u={{ current_user.user_id }}&m=avatar');"></a>
|
||||
|
||||
<div class="header__user__links__container">
|
||||
<ul class="header__user__links">
|
||||
<li class="header__user__option"><a class="header__user__link" href="/profile.php?u={{ app.session.user.user_id }}">Profile</a></li>
|
||||
<li class="header__user__option"><a class="header__user__link" href="/profile.php?u={{ current_user.user_id }}">Profile</a></li>
|
||||
<li class="header__user__option"><a class="header__user__link" href="/settings.php">Settings</a></li>
|
||||
{% if app.session.user.user_id == 1 %}
|
||||
{% if current_user.user_id == 1 %}
|
||||
<li class="header__user__option"><a class="header__user__link" href="{{ manage_link|default('/manage/index.php') }}">Manage</a></li>
|
||||
{% endif %}
|
||||
<li class="header__user__option"><a class="header__user__link" href="/auth.php?m=logout&s={{ csrf_token() }}">Logout</a></li>
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{{ paginate(posts, '?c=' ~ category.category_id, 'news__') }}
|
||||
{# paginate(posts, '?c=' ~ category.category_id, 'news__') #}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,22 +23,18 @@
|
|||
</div>
|
||||
<div class="container__content">
|
||||
{% for category in categories %}
|
||||
{% set post_count = category.posts.count %}
|
||||
|
||||
{% if post_count > 0 %}
|
||||
<a class="news__list__item news__list__item--kvp" href="/news.php?c={{ category.category_id }}">
|
||||
<div class="news__list__name">
|
||||
{{ category.category_name }}
|
||||
</div>
|
||||
<div class="news__list__value">
|
||||
{{ post_count }} post{{ post_count == 1 ? '' : 's' }}
|
||||
{{ category.count }} post{{ category.count == 1 ? '' : 's' }}
|
||||
</div>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{{ paginate(posts, '', 'news__') }}
|
||||
{# paginate(posts, '', 'news__') #}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,18 +5,18 @@
|
|||
</a>
|
||||
<div class="container__content news__preview__content">
|
||||
<div class="news__preview__text">
|
||||
{{ post.html|first_paragraph|raw }}
|
||||
{{ post.post_text|first_paragraph|raw }}
|
||||
<p><b><i><a href="/news.php?n={{ post.post_id }}">View full post</a></i></b></p>
|
||||
</div>
|
||||
<div class="news__preview__info">
|
||||
<div class="news__preview__date">
|
||||
{{ post.created_at }}
|
||||
</div>
|
||||
<a class="news__preview__user" href="/profile.php?u={{ post.user.user_id }}">
|
||||
<div class="news__preview__user__name" style="color:{{ post.user.colour|colour_get_css }}">
|
||||
{{ post.user.username }}
|
||||
<a class="news__preview__user" href="/profile.php?u={{ post.user_id }}">
|
||||
<div class="news__preview__user__name" style="color:{{ post.display_colour|colour_get_css }}">
|
||||
{{ post.username }}
|
||||
</div>
|
||||
<div class="avatar news__preview__user__avatar" style="background-image:url('/profile.php?u={{ post.user.user_id }}&m=avatar')"></div>
|
||||
<div class="avatar news__preview__user__avatar" style="background-image:url('/profile.php?u={{ post.user_id }}&m=avatar')"></div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -6,27 +6,27 @@
|
|||
{% block news_content %}
|
||||
<div class="container news__post">
|
||||
<div class="container__title">
|
||||
<a href="/news.php?c={{ post.category.category_id }}" class="container__title__link">{{ post.category.category_name }}</a> »
|
||||
<a href="/news.php?c={{ post.category_id }}" class="container__title__link">{{ post.category_name }}</a> »
|
||||
{{ post.post_title }}
|
||||
</div>
|
||||
|
||||
<div class="container__content news__post__content">
|
||||
<div class="news__post__text">
|
||||
{{ post.html|raw }}
|
||||
{{ post.post_text|raw }}
|
||||
</div>
|
||||
|
||||
<div class="news__sidebar news__post__details">
|
||||
<a class="news__post__detail news__post__user" href="/profile.php?u={{ post.user.user_id }}">
|
||||
<div class="news__post__username" style="color:post.user.colour|colour_get_css">{{ post.user.username }}</div>
|
||||
<div class="avatar news__post__avatar" style="background-image:url('/profile.php?u={{ post.user.user_id }}&m=avatar');"></div>
|
||||
<a class="news__post__detail news__post__user" href="/profile.php?u={{ post.user_id }}">
|
||||
<div class="news__post__username" style="color:{{ post.display_colour|colour_get_css }}">{{ post.username }}</div>
|
||||
<div class="avatar news__post__avatar" style="background-image:url('/profile.php?u={{ post.user_id }}&m=avatar');"></div>
|
||||
</a>
|
||||
|
||||
<a class="news__post__detail news__post__info news__post__info--link" href="/news.php?c={{ post.category.category_id }}">
|
||||
<a class="news__post__detail news__post__info news__post__info--link" href="/news.php?c={{ post.category_id }}">
|
||||
<div class="news__post__info__name">
|
||||
Category
|
||||
</div>
|
||||
<div class="news__post__info__value">
|
||||
{{ post.category.category_name }}
|
||||
{{ post.category_name }}
|
||||
</div>
|
||||
</a>
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
{{ props.name }}
|
||||
</div>
|
||||
<div class="settings__account__input__value">
|
||||
<input type="{{ props.type|default('text') }}" name="profile[{{ name }}]" value="{{ settings_user['user_' ~ name] }}" class="input__text settings__account__input__value__text">
|
||||
<input type="{{ props.type|default('text') }}" name="profile[{{ name }}]" value="{{ settings_profile_values['user_' ~ name] }}" class="input__text settings__account__input__value__text">
|
||||
</div>
|
||||
</label>
|
||||
{% endfor %}
|
||||
|
|
|
@ -28,12 +28,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="settings__avatar__preview__container">
|
||||
<div class="avatar settings__avatar__preview" id="avatar-preview" style="background-image:url('/profile.php?u={{ settings_user.user_id }}&m=avatar')"></div>
|
||||
<div class="avatar settings__avatar__preview" id="avatar-preview" style="background-image:url('/profile.php?u={{ avatar_user_id }}&m=avatar')"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function updateAvatarPreview(url, element) {
|
||||
url = url || "/profile.php?u={{ settings_user.user_id }}&m=avatar";
|
||||
url = url || "/profile.php?u={{ avatar_user_id }}&m=avatar";
|
||||
element = element || document.getElementById('avatar-preview');
|
||||
element.style.backgroundImage = 'url(\'' + url + '\')';
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
IP
|
||||
</div>
|
||||
<div class="settings__login-history__column__value">
|
||||
{{ attempt.attempt_ip.string }}
|
||||
{{ attempt.attempt_ip_decoded }}
|
||||
{% if attempt.attempt_country != 'XX' %}
|
||||
<img class="settings__login-history__country" src="https://static.flash.moe/flags/fff/{{ attempt.attempt_country|lower }}.png" alt="{{ attempt.attempt_country }}" title="{{ attempt.attempt_country|country_name }}">
|
||||
{% endif %}
|
||||
|
@ -28,12 +28,12 @@
|
|||
{{ attempt.was_successful ? 'Yes' : 'No' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings__login-history__column settings__login-history__column--created" onmouseenter="this.children[1].textContent = '{{ attempt.created_at }}';" onmouseleave="this.children[1].textContent = '{{ attempt.created_at.diffForHumans }}';">
|
||||
<div class="settings__login-history__column settings__login-history__column--created" onmouseenter="this.children[1].textContent = '{{ attempt.created_at }}';" onmouseleave="this.children[1].textContent = '{{ attempt.created_at }}';"> {#.diffForHumans #}
|
||||
<div class="settings__login-history__column__name">
|
||||
Attempted
|
||||
</div>
|
||||
<div class="settings__login-history__column__value">
|
||||
{{ attempt.created_at.diffForHumans }}
|
||||
{{ attempt.created_at }} {#.diffForHumans #}
|
||||
</div>
|
||||
</div>
|
||||
{% if attempt.user_agent|length > 0 %}
|
||||
|
@ -49,6 +49,6 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{{ paginate(user_login_attempts, '?m=login-history', 'settings__') }}
|
||||
{# paginate(user_login_attempts, '?m=login-history', 'settings__') #}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -8,34 +8,34 @@
|
|||
|
||||
<div class="settings__sessions">
|
||||
{% for session in user_sessions %}
|
||||
<div class="settings__sessions__entry{% if session.session_id == settings_session.session_id %} settings__sessions__entry--current{% endif %}" id="session-{{ session.session_id }}">
|
||||
<div class="settings__sessions__entry{% if session.session_id == active_session_id %} settings__sessions__entry--current{% endif %}" id="session-{{ session.session_id }}">
|
||||
<div class="settings__sessions__column settings__sessions__column--ip">
|
||||
<div class="settings__sessions__column__name">
|
||||
IP
|
||||
</div>
|
||||
<div class="settings__sessions__column__value">
|
||||
{{ session.session_ip.string }}
|
||||
{{ session.session_ip_decoded }}
|
||||
{% if session.session_country != 'XX' %}
|
||||
<img class="settings__sessions__country" src="https://static.flash.moe/flags/fff/{{ session.session_country|lower }}.png" alt="{{ session.session_country }}" title="{{ session.session_country|country_name }}">
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings__sessions__column settings__sessions__column--created" onmouseenter="this.children[1].textContent = '{{ session.created_at }}';" onmouseleave="this.children[1].textContent = '{{ session.created_at.diffForHumans }}';">
|
||||
<div class="settings__sessions__column settings__sessions__column--created" onmouseenter="this.children[1].textContent = '{{ session.created_at }}';" onmouseleave="this.children[1].textContent = '{{ session.created_at }}';"> {# .diffForHumans #}
|
||||
<div class="settings__sessions__column__name">
|
||||
Created
|
||||
</div>
|
||||
<div class="settings__sessions__column__value">
|
||||
{{ session.created_at.diffForHumans }}
|
||||
{{ session.created_at }} {# .diffForHumans #}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings__sessions__column settings__sessions__column--expires" onmouseenter="this.children[1].textContent = '{{ session.expires_on }}';" onmouseleave="this.children[1].textContent = '{{ session.expires_on.diffForHumans }}';">
|
||||
<div class="settings__sessions__column settings__sessions__column--expires" onmouseenter="this.children[1].textContent = '{{ session.expires_on }}';" onmouseleave="this.children[1].textContent = '{{ session.expires_on }}';"> {# .diffForHumans #}
|
||||
<div class="settings__sessions__column__name">
|
||||
Expires
|
||||
</div>
|
||||
<div class="settings__sessions__column__value">
|
||||
{{ session.expires_on.diffForHumans }}
|
||||
{{ session.expires_on }} {# .diffForHumans #}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -53,11 +53,13 @@
|
|||
<form class="settings__sessions__column settings__sessions__column--options" method="post" action="?m=sessions">
|
||||
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="session" value="{{ session.session_id }}">
|
||||
<button class="input__button settings__sessions__button">Kill</button>
|
||||
<button class="input__button settings__sessions__button">
|
||||
{{ session.session_id == active_session_id ? 'Logout' : 'Kill' }}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
{{ paginate(user_sessions, '?m=sessions', 'settings__') }}
|
||||
{# paginate(user_sessions, '?m=sessions', 'settings__') #}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -72,10 +72,10 @@
|
|||
<div class="profile__info">
|
||||
<div class="profile__info__section">
|
||||
<div class="profile__info__block">
|
||||
{% if profile.userTitle is not empty %}
|
||||
{% if profile.user_title is not empty %}
|
||||
<div class="profile__info__row">
|
||||
<div class="profile__info__column profile__info__column--user-title" style="color:{{ profile.colour|colour_get_css }}">
|
||||
{{ profile.userTitle }}
|
||||
<div class="profile__info__column profile__info__column--user-title" style="color:{{ profile.display_colour|colour_get_css }}">
|
||||
{{ profile.user_title }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -91,33 +91,34 @@
|
|||
</div>
|
||||
|
||||
<div class="profile__info__block">
|
||||
<div class="profile__info__row" title="{{ profile.created_at.format('r') }}">
|
||||
<div class="profile__info__row" title="{{ profile.created_at }}">{# .format('r') #}
|
||||
<div class="profile__info__column profile__info__column--heading">
|
||||
Joined
|
||||
</div>
|
||||
<div class="profile__info__column">
|
||||
{{ profile.created_at.diffForHumans }}
|
||||
{{ profile.created_at }}{# .diffForHumans #}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if profile.last_seen.timestamp > 0 %}
|
||||
<div class="profile__info__row" title="{{ profile.last_seen.format('r') }}">
|
||||
{# if profile.last_seen.timestamp > 0 #}
|
||||
<div class="profile__info__row" title="{{ profile.last_seen }}">
|
||||
<div class="profile__info__column profile__info__column--heading">
|
||||
Last Seen
|
||||
</div>
|
||||
<div class="profile__info__column">
|
||||
{% if profile.last_seen.addMinute.timestamp >= ''|date('U') %}
|
||||
{#{% if profile.last_seen.addMinute.timestamp >= ''|date('U') %}
|
||||
Online now
|
||||
{% else %}
|
||||
{{ profile.last_seen.diffForHumans }}
|
||||
{% endif %}
|
||||
{{ profile.last_seen }}{# .diffForHumans #\}
|
||||
{% endif %}#}
|
||||
{{ profile.last_seen }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{# endif #}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if app.session != null %}
|
||||
{% if app.hasActiveSession %}
|
||||
{% spaceless %}
|
||||
<div class="profile__info__section">
|
||||
<div class="profile__info__block profile__info__block--links">
|
||||
|
|
Loading…
Add table
Reference in a new issue