OAOAUOEUAOEUAOUEOAOOOOGAABOOOGAOGABOGA
This commit is contained in:
parent
e7c27c3770
commit
5491c98563
49
app/App/Config.php
Normal file
49
app/App/Config.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
abstract class Config
|
||||||
|
{
|
||||||
|
private static string $default_file;
|
||||||
|
private static string $override_file;
|
||||||
|
private static array $config;
|
||||||
|
|
||||||
|
static function file_location(string $default, string $override)
|
||||||
|
{
|
||||||
|
self::$default_file = $default;
|
||||||
|
self::$override_file = $override;
|
||||||
|
}
|
||||||
|
|
||||||
|
static function get(string $key): string|int|float|bool
|
||||||
|
{
|
||||||
|
if (isset(self::$config)) {
|
||||||
|
return self::$config[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$config = require self::$default_file;
|
||||||
|
|
||||||
|
if (!file_exists(self::$override_file)) {
|
||||||
|
return self::$config[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (require self::$override_file as $override_key => $override_value) {
|
||||||
|
if (!array_key_exists($override_key, self::$config)) {
|
||||||
|
trigger_error(
|
||||||
|
'Failed to validate config: ' .
|
||||||
|
"Undefined option '$override_key'"
|
||||||
|
, E_USER_ERROR);
|
||||||
|
}
|
||||||
|
if (gettype($override_value) !== gettype(self::$config[$override_key])) {
|
||||||
|
trigger_error(
|
||||||
|
'Failed to validate config: ' .
|
||||||
|
"Type mismatch in config file: '$override_key' should be of type "
|
||||||
|
. gettype(self::$config[$override_key]) .
|
||||||
|
' not '
|
||||||
|
. gettype($override_value)
|
||||||
|
, E_USER_ERROR);
|
||||||
|
}
|
||||||
|
self::$config[$override_key] = $override_value;
|
||||||
|
}
|
||||||
|
return self::$config[$key];
|
||||||
|
}
|
||||||
|
}
|
94
app/App/Controller/ChatController.php
Normal file
94
app/App/Controller/ChatController.php
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Config;
|
||||||
|
|
||||||
|
ChatController::init();
|
||||||
|
|
||||||
|
abstract class ChatController
|
||||||
|
{
|
||||||
|
private static array $messages;
|
||||||
|
|
||||||
|
static function init()
|
||||||
|
{
|
||||||
|
if (!file_exists(Config::get('path_to_chat_json_file'))) {
|
||||||
|
file_put_contents(
|
||||||
|
Config::get('path_to_chat_json_file'),
|
||||||
|
json_encode([])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self::$messages = json_decode(
|
||||||
|
file_get_contents(Config::get('path_to_chat_json_file')),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function index()
|
||||||
|
{
|
||||||
|
if (empty($_POST)) {
|
||||||
|
return view('pages/chat/index', [
|
||||||
|
'nick' => 'Willy',
|
||||||
|
'just_sent_message' => false,
|
||||||
|
'errmsg' => false
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$nick = filter_input(INPUT_POST, 'nick');
|
||||||
|
$text = filter_input(INPUT_POST, 'text');
|
||||||
|
$errmsg = (function() use (&$text, &$nick): string|false {
|
||||||
|
if (empty(trim($nick, ' '))) {
|
||||||
|
return 'You must choose a nickname.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$nick_max_chars = 128;
|
||||||
|
if (strlen($nick) > $nick_max_chars) {
|
||||||
|
return 'Your nickname is TOO LONG! ' . strlen($nick) . ' out of ' . $nick_max_chars. ' characters.';
|
||||||
|
}
|
||||||
|
|
||||||
|
$text_max_chars = 4096;
|
||||||
|
if (strlen($text) > $text_max_chars) {
|
||||||
|
return 'Your message is TOO LONG!!!! ' . strlen($text) . ' out of ' . $text_max_chars . ' characters.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty(trim($text, ' '))) {
|
||||||
|
return 'Message body cannot be empty.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (!$errmsg) {
|
||||||
|
if (count(self::$messages) > 100) {
|
||||||
|
array_pop(self::$messages);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_unshift(self::$messages, [
|
||||||
|
'nick' => $nick,
|
||||||
|
'date' => time(),
|
||||||
|
'text' => $text
|
||||||
|
]);
|
||||||
|
|
||||||
|
file_put_contents(Config::get('path_to_chat_json_file'),
|
||||||
|
json_encode(
|
||||||
|
self::$messages
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
view('pages/chat/index', [
|
||||||
|
'nick' => $nick,
|
||||||
|
'just_sent_message' => true,
|
||||||
|
'errmsg' => $errmsg
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function messages()
|
||||||
|
{
|
||||||
|
view('pages/chat/messages', ['messages' => self::$messages]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function sync()
|
||||||
|
{
|
||||||
|
json_response(hash('crc32', serialize(self::$messages)));
|
||||||
|
}
|
||||||
|
}
|
21
app/App/Controller/DefaultController.php
Normal file
21
app/App/Controller/DefaultController.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
abstract class DefaultController
|
||||||
|
{
|
||||||
|
static function index()
|
||||||
|
{
|
||||||
|
view('pages/home');
|
||||||
|
}
|
||||||
|
|
||||||
|
static function echo($text = 'You sent nothing...')
|
||||||
|
{
|
||||||
|
json_response($text);
|
||||||
|
}
|
||||||
|
|
||||||
|
static function error()
|
||||||
|
{
|
||||||
|
1 / 0;
|
||||||
|
}
|
||||||
|
}
|
21
app/App/Database.php
Normal file
21
app/App/Database.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use \PDO;
|
||||||
|
use App\Config;
|
||||||
|
|
||||||
|
abstract class Database
|
||||||
|
{
|
||||||
|
private static PDO $handle;
|
||||||
|
|
||||||
|
static function get_handle(): PDO
|
||||||
|
{
|
||||||
|
if (!isset(self::$handle)) {
|
||||||
|
self::$handle = new PDO(
|
||||||
|
'sqlite:' . Config::get('path_to_sqlite_file')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return self::$handle;
|
||||||
|
}
|
||||||
|
}
|
@ -2,37 +2,37 @@
|
|||||||
|
|
||||||
namespace WillySoft;
|
namespace WillySoft;
|
||||||
|
|
||||||
abstract class ErrorHandler {
|
abstract class ErrorHandler
|
||||||
private static array $error_messages = [];
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On error or exception erase the output buffer then hand the errors as an array of HTML formatted messages to the callback
|
* On error or exception erase the output buffer and hand the
|
||||||
|
* error message as an HTML formatted string to the callback
|
||||||
|
* then die
|
||||||
*/
|
*/
|
||||||
static function register(callable $callback) {
|
static function register(callable $callback)
|
||||||
|
{
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
set_error_handler(function($errno, $errstr, $errfile, $errline) {
|
set_error_handler(function($errno, $errstr, $errfile, $errline) use (&$callback) {
|
||||||
error_log("Error[$errno]: $errstr in $errfile at line $errline\n");
|
error_log("Error[$errno]: $errstr in $errfile at line $errline\n");
|
||||||
$errstr = htmlspecialchars($errstr);
|
$errstr = htmlspecialchars($errstr);
|
||||||
self::$error_messages[] = "<b>Error[$errno]:</b> $errstr in <b>$errfile</b> at line <b>$errline</b>";
|
self::oof($callback, "<b>Error[$errno]:</b> $errstr in <b>$errfile</b> at line <b>$errline</b>");
|
||||||
});
|
});
|
||||||
|
|
||||||
set_exception_handler(function($exception) {
|
set_exception_handler(function($exception) use (&$callback) {
|
||||||
error_log("Uncaught Exception: $exception\n");
|
error_log("Uncaught Exception: $exception\n");
|
||||||
self::$error_messages[] = "<b>Uncaught Exception:</b> {$exception}";
|
self::oof($callback, "<b>Uncaught Exception:</b> {$exception}");
|
||||||
});
|
});
|
||||||
|
|
||||||
register_shutdown_function(function() use(&$callback) {
|
|
||||||
if (!self::$error_messages) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function oof(callable $callback, string $error_message)
|
||||||
|
{
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
|
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
header_remove();
|
header_remove();
|
||||||
|
|
||||||
$callback(self::$error_messages);
|
$callback($error_message);
|
||||||
});
|
die();
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,11 +10,6 @@ abstract class Route
|
|||||||
|
|
||||||
static string $prefix = '';
|
static string $prefix = '';
|
||||||
|
|
||||||
/**
|
|
||||||
* Check to see if the methods and path satisfy the request.
|
|
||||||
* and do some magic to retrieve the parameters before calling
|
|
||||||
* the callback passing those as arguments
|
|
||||||
*/
|
|
||||||
static function match(string $methods, string $path, callable $callback)
|
static function match(string $methods, string $path, callable $callback)
|
||||||
{
|
{
|
||||||
if (!in_array($_SERVER['REQUEST_METHOD'],
|
if (!in_array($_SERVER['REQUEST_METHOD'],
|
||||||
@ -70,10 +65,6 @@ abstract class Route
|
|||||||
static function form(string $path, callable $callback) { self::match('get|post', $path, $callback); }
|
static function form(string $path, callable $callback) { self::match('get|post', $path, $callback); }
|
||||||
static function any(string $path, callable $callback) { self::match('get|post|put|patch|delete|options', $path, $callback); }
|
static function any(string $path, callable $callback) { self::match('get|post|put|patch|delete|options', $path, $callback); }
|
||||||
|
|
||||||
/**
|
|
||||||
* Require that a callable or an array of callables have to run
|
|
||||||
* if a route is matched within the current or all child groups
|
|
||||||
*/
|
|
||||||
static function middleware(callable|array $middlewares)
|
static function middleware(callable|array $middlewares)
|
||||||
{
|
{
|
||||||
if (!is_array($middlewares)) {
|
if (!is_array($middlewares)) {
|
||||||
@ -86,7 +77,6 @@ abstract class Route
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static function group(string $prefix = '', ?callable $callback = null)
|
static function group(string $prefix = '', ?callable $callback = null)
|
||||||
{
|
{
|
||||||
if (!str_starts_with($_SERVER['REQUEST_URI'], self::$prefix . $prefix)) {
|
if (!str_starts_with($_SERVER['REQUEST_URI'], self::$prefix . $prefix)) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* Do not modify this file!
|
* Do not edit this file!
|
||||||
* Copy and name it config.php to override these defaults.
|
* Copy and name it config.php to override these defaults.
|
||||||
*/
|
*/
|
||||||
return [
|
return [
|
||||||
'debug' => false
|
'path_to_chat_json_file' => '/dev/shm/database.json'
|
||||||
];
|
];
|
155
public/index.php
155
public/index.php
@ -1,15 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
|
||||||
* _____
|
|
||||||
* / \
|
|
||||||
* | () () |
|
|
||||||
* \ ^ /
|
|
||||||
* |||||
|
|
||||||
* |||||
|
|
||||||
*
|
|
||||||
* Tread carefully!
|
|
||||||
*/
|
|
||||||
|
|
||||||
// PSR-4 like autoloader
|
// PSR-4 like autoloader
|
||||||
spl_autoload_register(
|
spl_autoload_register(
|
||||||
function ($class_name) {
|
function ($class_name) {
|
||||||
@ -18,31 +7,6 @@ spl_autoload_register(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* Shitty config loader
|
|
||||||
*/
|
|
||||||
function config(string $key): bool|string|int|float {
|
|
||||||
static $config;
|
|
||||||
if (isset($config)) {
|
|
||||||
return $config[$key];
|
|
||||||
}
|
|
||||||
$config = require __DIR__ . '/../config/default.config.php';
|
|
||||||
$config_override_path = __DIR__ . '/../config/config.php';
|
|
||||||
if (!file_exists($config_override_path)) {
|
|
||||||
return $config[$key];
|
|
||||||
}
|
|
||||||
foreach (require $config_override_path as $override_key => $override_value) {
|
|
||||||
if (!array_key_exists($override_key, $config)) {
|
|
||||||
trigger_error('Undefined key in config file', E_USER_ERROR);
|
|
||||||
}
|
|
||||||
if (gettype($override_value) !== gettype($config[$override_key])) {
|
|
||||||
trigger_error('Type mismatch in config file', E_USER_ERROR);
|
|
||||||
}
|
|
||||||
$config[$override_key] = $override_value;
|
|
||||||
}
|
|
||||||
return $config[$key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper for evaluating/including views
|
* Helper for evaluating/including views
|
||||||
*/
|
*/
|
||||||
@ -86,121 +50,4 @@ function url(string $url, bool $full = false): string
|
|||||||
return isset($_SERVER['HTTPS']) ? 'https' : 'http' . '://' . $_SERVER['HTTP_HOST'] . $dir . $url;
|
return isset($_SERVER['HTTPS']) ? 'https' : 'http' . '://' . $_SERVER['HTTP_HOST'] . $dir . $url;
|
||||||
}
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------
|
require '../routes/start.php';
|
||||||
// Let's get routing!
|
|
||||||
//-----------------------------------------------------
|
|
||||||
|
|
||||||
use WillySoft\ErrorHandler;
|
|
||||||
use WillySoft\Route;
|
|
||||||
|
|
||||||
Route::get('/', function() {
|
|
||||||
view('pages/home');
|
|
||||||
});
|
|
||||||
|
|
||||||
ErrorHandler::register(function($error_messages) {
|
|
||||||
if (config('debug')) {
|
|
||||||
view('errors/500', ['error_messages' => $error_messages]);
|
|
||||||
} else {
|
|
||||||
view('errors/500', ['error_messages' => []]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::group(function() {
|
|
||||||
Route::get('/', fn() => view('pages/home'));
|
|
||||||
Route::get('/echo/$text?', fn($text= 'You sent nothing...') => json_response($text));
|
|
||||||
Route::get('/error', fn() => 1 / 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::group(function() {
|
|
||||||
if (!config('debug')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Route::get('/blog/', fn() => view('pages/blog/home'));
|
|
||||||
Route::get('/blog/test', fn() => view('pages/blog/test'));
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::group(function() {
|
|
||||||
$data_path = '/dev/shm/database.json';
|
|
||||||
$messages = [];
|
|
||||||
|
|
||||||
Route::middleware(function() use (&$data_path, &$messages) {
|
|
||||||
if (!file_exists($data_path)) {
|
|
||||||
file_put_contents(
|
|
||||||
$data_path,
|
|
||||||
json_encode([])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
$messages = json_decode(
|
|
||||||
file_get_contents($data_path),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::form('/willychat/', function() use (&$data_path, &$messages) {
|
|
||||||
if (empty($_POST)) {
|
|
||||||
return view('pages/willychat/index', [
|
|
||||||
'nick' => 'Willy',
|
|
||||||
'just_sent_message' => false,
|
|
||||||
'errmsg' => false
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$nick = filter_input(INPUT_POST, 'nick');
|
|
||||||
$text = filter_input(INPUT_POST, 'text');
|
|
||||||
$errmsg = (function() use (&$text, &$nick): string|false {
|
|
||||||
if (empty(trim($nick, ' '))) {
|
|
||||||
return 'You must choose a nickname.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$nick_max_chars = 128;
|
|
||||||
if (strlen($nick) > $nick_max_chars) {
|
|
||||||
return 'Your nickname is TOO LONG! ' . strlen($nick) . ' out of ' . $nick_max_chars. ' characters.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$text_max_chars = 4096;
|
|
||||||
if (strlen($text) > $text_max_chars) {
|
|
||||||
return 'Your message is TOO LONG!!!! ' . strlen($text) . ' out of ' . $text_max_chars . ' characters.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (empty(trim($text, ' '))) {
|
|
||||||
return 'Message body cannot be empty.';
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
})();
|
|
||||||
|
|
||||||
if (!$errmsg) {
|
|
||||||
if (count($messages) > 100) {
|
|
||||||
array_pop($messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
array_unshift($messages, [
|
|
||||||
'nick' => $nick,
|
|
||||||
'date' => time(),
|
|
||||||
'text' => $text
|
|
||||||
]);
|
|
||||||
|
|
||||||
file_put_contents($data_path,
|
|
||||||
json_encode(
|
|
||||||
$messages
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
view('pages/willychat/index', [
|
|
||||||
'nick' => $nick,
|
|
||||||
'just_sent_message' => true,
|
|
||||||
'errmsg' => $errmsg
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::get('/willychat/messages', function() use (&$messages) {
|
|
||||||
view('pages/willychat/messages', ['messages' => $messages]);
|
|
||||||
});
|
|
||||||
|
|
||||||
Route::get('/willychat/sync', function() use (&$messages) {
|
|
||||||
json_response(hash('crc32', serialize($messages)));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
http_response_code(404);
|
|
||||||
view('errors/404');
|
|
37
routes/start.php
Normal file
37
routes/start.php
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Config;
|
||||||
|
use WillySoft\ErrorHandler;
|
||||||
|
use WillySoft\Route as App;
|
||||||
|
|
||||||
|
Config::file_location(
|
||||||
|
__DIR__ . '/../config/default.config.php',
|
||||||
|
__DIR__ . '/../config/config.php'
|
||||||
|
);
|
||||||
|
|
||||||
|
ErrorHandler::register(function($error_message) {
|
||||||
|
view('errors/500', [
|
||||||
|
'error_message' => $error_message
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
use App\Controller\DefaultController;
|
||||||
|
|
||||||
|
App::get('/',
|
||||||
|
DefaultController::index(...));
|
||||||
|
App::get('/echo/$text?',
|
||||||
|
DefaultController::echo(...));
|
||||||
|
App::get('/error',
|
||||||
|
DefaultController::error(...));
|
||||||
|
|
||||||
|
use App\Controller\ChatController;
|
||||||
|
|
||||||
|
App::group('/chat', function() {
|
||||||
|
App::form('/',
|
||||||
|
ChatController::index(...));
|
||||||
|
App::get('/messages',
|
||||||
|
ChatController::messages(...));
|
||||||
|
});
|
||||||
|
|
||||||
|
http_response_code(404);
|
||||||
|
view('errors/404');
|
@ -2,27 +2,14 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>500 Internal Server Error</title>
|
<title>Internal Server Error</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<style>
|
<h1>Internal Server Error (×﹏×)</h1>
|
||||||
body {
|
<?php if(!empty($error_message)): ?>
|
||||||
margin: 1rem;
|
<div><?=$error_message?></div>
|
||||||
background: tomato;
|
<?php endif; ?>
|
||||||
line-height: 1.75;
|
|
||||||
font-family: "Comic Sans MS", sans-serif;
|
|
||||||
}
|
|
||||||
.error {
|
|
||||||
padding: 1rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background: white;
|
|
||||||
border: .1rem solid black;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<h1>Error!!1 (×﹏×)</h1>
|
|
||||||
<?php foreach($error_messages as $error_message): ?>
|
|
||||||
<div class="error"><?=$error_message?></div>
|
|
||||||
<?php endforeach; ?>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
@ -68,7 +68,7 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<div class="messages">
|
<div class="messages">
|
||||||
<iframe src="<?=url('/willychat/messages')?>" marginwidth="0" marginheight="0" scrolling="yes" frameborder="0"></iframe>
|
<iframe src="<?=url('./messages')?>" marginwidth="0" marginheight="0" scrolling="yes" frameborder="0"></iframe>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
@ -13,7 +13,7 @@
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<iframe class="willychat" src="<?=url('/willychat/')?>" frameborder="0" scrolling="no"></iframe>
|
<iframe class="willychat" src="<?=url('/chat/')?>" frameborder="0" scrolling="no"></iframe>
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<br>
|
<br>
|
||||||
|
Loading…
Reference in New Issue
Block a user