Removed wonky modules system and added documentation all over.
This commit is contained in:
parent
aa83955c7b
commit
e716af9b5a
34 changed files with 1176 additions and 183 deletions
|
@ -1 +0,0 @@
|
|||
|
16
misuzu.php
16
misuzu.php
|
@ -3,7 +3,7 @@ namespace Misuzu;
|
|||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
$app = Application::start(
|
||||
$app = new Application(
|
||||
__DIR__ . '/config/config.ini',
|
||||
IO\Directory::exists(__DIR__ . '/vendor/phpunit/phpunit')
|
||||
);
|
||||
|
@ -21,11 +21,11 @@ if (PHP_SAPI !== 'cli') {
|
|||
ob_start('ob_gzhandler');
|
||||
}
|
||||
|
||||
if ($app->config->get('Auth', 'lockdown', 'bool', false)) {
|
||||
if ($app->getConfig()->get('Auth', 'lockdown', 'bool', false)) {
|
||||
http_response_code(503);
|
||||
$app->startTemplating();
|
||||
$app->templating->addPath('auth', __DIR__ . '/views/auth');
|
||||
echo $app->templating->render('lockdown');
|
||||
$app->getTemplating()->addPath('auth', __DIR__ . '/views/auth');
|
||||
echo $app->getTemplating()->render('lockdown');
|
||||
exit;
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ if (PHP_SAPI !== 'cli') {
|
|||
|
||||
if ($session !== null) {
|
||||
$session->user->last_seen = \Carbon\Carbon::now();
|
||||
$session->user->last_ip = \Misuzu\Net\IPAddress::remote();
|
||||
$session->user->last_ip = Net\IPAddress::remote();
|
||||
$session->user->save();
|
||||
}
|
||||
}
|
||||
|
@ -43,15 +43,15 @@ if (PHP_SAPI !== 'cli') {
|
|||
$manage_mode = starts_with($_SERVER['REQUEST_URI'], '/manage');
|
||||
|
||||
$app->startTemplating();
|
||||
$app->templating->addPath('mio', __DIR__ . '/views/mio');
|
||||
$app->getTemplating()->addPath('mio', __DIR__ . '/views/mio');
|
||||
|
||||
if ($manage_mode) {
|
||||
if ($app->getSession() === null || $app->getSession()->user->user_id !== 1) {
|
||||
http_response_code(403);
|
||||
echo $app->templating->render('errors.403');
|
||||
echo $app->getTemplating()->render('errors.403');
|
||||
exit;
|
||||
}
|
||||
|
||||
$app->templating->addPath('manage', __DIR__ . '/views/manage');
|
||||
$app->getTemplating()->addPath('manage', __DIR__ . '/views/manage');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,16 +6,13 @@
|
|||
|
||||
namespace Misuzu;
|
||||
|
||||
use Illuminate\Database\Capsule\Manager;
|
||||
use Illuminate\Database\ConnectionResolver;
|
||||
use Illuminate\Database\Migrations\DatabaseMigrationRepository;
|
||||
use Illuminate\Database\Schema\Builder;
|
||||
use Illuminate\Database\Migrations\Migrator;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
|
||||
require_once __DIR__ . '/misuzu.php';
|
||||
|
||||
$repository = new DatabaseMigrationRepository(Application::getInstance()->database->getDatabaseManager(), 'migrations');
|
||||
$repository = new DatabaseMigrationRepository(Application::getInstance()->getDatabase()->getDatabaseManager(), 'migrations');
|
||||
$migrator = new Migrator($repository, $repository->getConnectionResolver(), new Filesystem);
|
||||
|
||||
if (!$migrator->repositoryExists()) {
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
<?php
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Database\Eloquent\ModelNotFoundException;
|
||||
use Misuzu\Application;
|
||||
use Misuzu\Database;
|
||||
use Misuzu\Net\IPAddress;
|
||||
use Misuzu\Users\Role;
|
||||
use Misuzu\Users\User;
|
||||
|
@ -23,16 +20,16 @@ $username_validation_errors = [
|
|||
|
||||
$mode = $_GET['m'] ?? 'login';
|
||||
$prevent_registration = $app->config->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$app->templating->var('auth_mode', $mode);
|
||||
$app->templating->addPath('auth', __DIR__ . '/../views/auth');
|
||||
$app->templating->var('prevent_registration', $prevent_registration);
|
||||
$app->getTemplating()->var('auth_mode', $mode);
|
||||
$app->getTemplating()->addPath('auth', __DIR__ . '/../views/auth');
|
||||
$app->getTemplating()->var('prevent_registration', $prevent_registration);
|
||||
|
||||
if (!empty($_REQUEST['username'])) {
|
||||
$app->templating->var('auth_username', $_REQUEST['username']);
|
||||
$app->getTemplating()->var('auth_username', $_REQUEST['username']);
|
||||
}
|
||||
|
||||
if (!empty($_REQUEST['email'])) {
|
||||
$app->templating->var('auth_email', $_REQUEST['email']);
|
||||
$app->getTemplating()->var('auth_email', $_REQUEST['email']);
|
||||
}
|
||||
|
||||
switch ($mode) {
|
||||
|
@ -52,7 +49,7 @@ switch ($mode) {
|
|||
return;
|
||||
}
|
||||
|
||||
echo $app->templating->render('@auth.logout');
|
||||
echo $app->getTemplating()->render('@auth.logout');
|
||||
break;
|
||||
|
||||
case 'login':
|
||||
|
@ -120,10 +117,10 @@ switch ($mode) {
|
|||
}
|
||||
|
||||
if (!empty($auth_login_error)) {
|
||||
$app->templating->var('auth_login_error', $auth_login_error);
|
||||
$app->getTemplating()->var('auth_login_error', $auth_login_error);
|
||||
}
|
||||
|
||||
echo $app->templating->render('auth');
|
||||
echo $app->getTemplating()->render('auth');
|
||||
break;
|
||||
|
||||
case 'register':
|
||||
|
@ -169,14 +166,14 @@ switch ($mode) {
|
|||
|
||||
$user = User::createUser($username, $password, $email);
|
||||
$user->addRole(Role::find(1), true);
|
||||
$app->templating->var('auth_register_message', 'Welcome to Flashii! You may now log in.');
|
||||
$app->getTemplating()->var('auth_register_message', 'Welcome to Flashii! You may now log in.');
|
||||
break;
|
||||
}
|
||||
|
||||
if (!empty($auth_register_error)) {
|
||||
$app->templating->var('auth_register_error', $auth_register_error);
|
||||
$app->getTemplating()->var('auth_register_error', $auth_register_error);
|
||||
}
|
||||
|
||||
echo $app->templating->render('@auth.auth');
|
||||
echo $app->getTemplating()->render('@auth.auth');
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -5,4 +5,4 @@ require_once __DIR__ . '/../misuzu.php';
|
|||
|
||||
$featured_news = NewsPost::where('is_featured', true)->orderBy('created_at', 'desc')->take(3)->get();
|
||||
|
||||
echo $app->templating->render('home.landing', compact('featured_news'));
|
||||
echo $app->getTemplating()->render('home.landing', compact('featured_news'));
|
||||
|
|
|
@ -3,7 +3,7 @@ require_once __DIR__ . '/../../misuzu.php';
|
|||
|
||||
switch ($_GET['v'] ?? null) {
|
||||
case 'overview':
|
||||
echo $app->templating->render('@manage.general.overview');
|
||||
echo $app->getTemplating()->render('@manage.general.overview');
|
||||
break;
|
||||
|
||||
case 'logs':
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
<?php
|
||||
use Misuzu\Application;
|
||||
use Misuzu\Colour;
|
||||
use Misuzu\Users\Role;
|
||||
use Misuzu\Users\User;
|
||||
|
@ -12,8 +11,8 @@ $page_id = (int)($_GET['p'] ?? 1);
|
|||
switch ($_GET['v'] ?? null) {
|
||||
case 'listing':
|
||||
$manage_users = User::paginate(32, ['*'], 'p', $page_id);
|
||||
$app->templating->vars(compact('manage_users'));
|
||||
echo $app->templating->render('@manage.users.listing');
|
||||
$app->getTemplating()->vars(compact('manage_users'));
|
||||
echo $app->getTemplating()->render('@manage.users.listing');
|
||||
break;
|
||||
|
||||
case 'view':
|
||||
|
@ -31,14 +30,14 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
}
|
||||
|
||||
$app->templating->var('view_user', $view_user);
|
||||
echo $app->templating->render('@manage.users.view');
|
||||
$app->getTemplating()->var('view_user', $view_user);
|
||||
echo $app->getTemplating()->render('@manage.users.view');
|
||||
break;
|
||||
|
||||
case 'roles':
|
||||
$manage_roles = Role::paginate(32, ['*'], 'p', $page_id);
|
||||
$app->templating->vars(compact('manage_roles'));
|
||||
echo $app->templating->render('@manage.users.roles');
|
||||
$app->getTemplating()->vars(compact('manage_roles'));
|
||||
echo $app->getTemplating()->render('@manage.users.roles');
|
||||
break;
|
||||
|
||||
case 'role':
|
||||
|
@ -121,9 +120,9 @@ switch ($_GET['v'] ?? null) {
|
|||
break;
|
||||
}
|
||||
|
||||
$app->templating->vars(compact('edit_role'));
|
||||
$app->getTemplating()->vars(compact('edit_role'));
|
||||
}
|
||||
|
||||
echo $app->templating->render('@manage.users.roles_create');
|
||||
echo $app->getTemplating()->render('@manage.users.roles_create');
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -13,11 +13,11 @@ if ($post_id !== null) {
|
|||
|
||||
if ($post === null) {
|
||||
http_response_code(404);
|
||||
echo $app->templating->render('errors.404');
|
||||
echo $app->getTemplating()->render('errors.404');
|
||||
return;
|
||||
}
|
||||
|
||||
echo $app->templating->render('news.post', compact('post'));
|
||||
echo $app->getTemplating()->render('news.post', compact('post'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ if ($category_id !== null) {
|
|||
|
||||
if ($category === null) {
|
||||
http_response_code(404);
|
||||
echo $app->templating->render('errors.404');
|
||||
echo $app->getTemplating()->render('errors.404');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -34,12 +34,12 @@ if ($category_id !== null) {
|
|||
|
||||
if (!is_valid_page($posts, $page_id)) {
|
||||
http_response_code(404);
|
||||
echo $app->templating->render('errors.404');
|
||||
echo $app->getTemplating()->render('errors.404');
|
||||
return;
|
||||
}
|
||||
|
||||
$featured = $category->posts()->where('is_featured', 1)->orderBy('created_at', 'desc')->take(10)->get();
|
||||
echo $app->templating->render('news.category', compact('category', 'posts', 'featured', 'page_id'));
|
||||
echo $app->getTemplating()->render('news.category', compact('category', 'posts', 'featured', 'page_id'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -48,8 +48,8 @@ $posts = NewsPost::where('is_featured', true)->orderBy('created_at', 'desc')->pa
|
|||
|
||||
if (!is_valid_page($posts, $page_id)) {
|
||||
http_response_code(404);
|
||||
echo $app->templating->render('errors.404');
|
||||
echo $app->getTemplating()->render('errors.404');
|
||||
return;
|
||||
}
|
||||
|
||||
echo $app->templating->render('news.index', compact('categories', 'posts', 'page_id'));
|
||||
echo $app->getTemplating()->render('news.index', compact('categories', 'posts', 'page_id'));
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<?php
|
||||
require_once __DIR__ . '/../misuzu.php';
|
||||
|
||||
echo $app->templating->render('errors.404');
|
||||
echo $app->getTemplating()->render('errors.404');
|
||||
|
|
|
@ -12,7 +12,7 @@ $profile_user = User::find($user_id);
|
|||
switch ($mode) {
|
||||
case 'avatar':
|
||||
$avatar_filename = $app->getPath(
|
||||
$app->config->get('Avatar', 'default_path', 'string', 'public/images/no-avatar.png')
|
||||
$app->getConfig()->get('Avatar', 'default_path', 'string', 'public/images/no-avatar.png')
|
||||
);
|
||||
|
||||
if ($profile_user !== null) {
|
||||
|
@ -46,11 +46,11 @@ switch ($mode) {
|
|||
default:
|
||||
if ($profile_user === null) {
|
||||
http_response_code(404);
|
||||
echo $app->templating->render('user.notfound');
|
||||
echo $app->getTemplating()->render('user.notfound');
|
||||
break;
|
||||
}
|
||||
|
||||
$app->templating->var('profile', $profile_user);
|
||||
echo $app->templating->render('user.view');
|
||||
$app->getTemplating()->var('profile', $profile_user);
|
||||
echo $app->getTemplating()->render('user.view');
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ $page_id = (int)($_GET['p'] ?? 1);
|
|||
|
||||
if (Application::getInstance()->getSession() === null) {
|
||||
http_response_code(403);
|
||||
echo $app->templating->render('errors.403');
|
||||
echo $app->getTemplating()->render('errors.403');
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -82,22 +82,22 @@ $settings_modes = [
|
|||
];
|
||||
$settings_mode = $_GET['m'] ?? key($settings_modes);
|
||||
|
||||
$app->templating->vars(compact('settings_mode', 'settings_modes', 'settings_user', 'settings_session'));
|
||||
$app->getTemplating()->vars(compact('settings_mode', 'settings_modes', 'settings_user', 'settings_session'));
|
||||
|
||||
if (!array_key_exists($settings_mode, $settings_modes)) {
|
||||
http_response_code(404);
|
||||
$app->templating->var('settings_title', 'Not Found');
|
||||
echo $app->templating->render('settings.notfound');
|
||||
$app->getTemplating()->var('settings_title', 'Not Found');
|
||||
echo $app->getTemplating()->render('settings.notfound');
|
||||
return;
|
||||
}
|
||||
|
||||
$settings_errors = [];
|
||||
|
||||
$prevent_registration = $app->config->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$prevent_registration = $app->getConfig()->get('Auth', 'prevent_registration', 'bool', false);
|
||||
$avatar_filename = "{$settings_user->user_id}.msz";
|
||||
$avatar_max_width = $app->config->get('Avatar', 'max_width', 'int', 4000);
|
||||
$avatar_max_height = $app->config->get('Avatar', 'max_height', 'int', 4000);
|
||||
$avatar_max_filesize = $app->config->get('Avatar', 'max_filesize', 'int', 1000000);
|
||||
$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);
|
||||
$avatar_max_filesize_human = byte_symbol($avatar_max_filesize, true);
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
|
@ -328,17 +328,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||
}
|
||||
}
|
||||
|
||||
$app->templating->vars(compact('settings_errors'));
|
||||
$app->templating->var('settings_title', $settings_modes[$settings_mode]);
|
||||
$app->getTemplating()->vars(compact('settings_errors'));
|
||||
$app->getTemplating()->var('settings_title', $settings_modes[$settings_mode]);
|
||||
|
||||
switch ($settings_mode) {
|
||||
case 'account':
|
||||
$app->templating->vars(compact('settings_profile_fields', 'prevent_registration'));
|
||||
$app->getTemplating()->vars(compact('settings_profile_fields', 'prevent_registration'));
|
||||
break;
|
||||
|
||||
case 'avatar':
|
||||
$user_has_avatar = File::exists($app->getStore('avatars/original')->filename($avatar_filename));
|
||||
$app->templating->vars(compact(
|
||||
$app->getTemplating()->vars(compact(
|
||||
'avatar_max_width',
|
||||
'avatar_max_height',
|
||||
'avatar_max_filesize',
|
||||
|
@ -351,7 +351,7 @@ switch ($settings_mode) {
|
|||
->orderBy('session_id', 'desc')
|
||||
->paginate(15, ['*'], 'p', $page_id);
|
||||
|
||||
$app->templating->var('user_sessions', $sessions);
|
||||
$app->getTemplating()->var('user_sessions', $sessions);
|
||||
break;
|
||||
|
||||
case 'login-history':
|
||||
|
@ -359,8 +359,8 @@ switch ($settings_mode) {
|
|||
->orderBy('attempt_id', 'desc')
|
||||
->paginate(15, ['*'], 'p', $page_id);
|
||||
|
||||
$app->templating->var('user_login_attempts', $login_attempts);
|
||||
$app->getTemplating()->var('user_login_attempts', $login_attempts);
|
||||
break;
|
||||
}
|
||||
|
||||
echo $app->templating->render("settings.{$settings_mode}");
|
||||
echo $app->getTemplating()->render("settings.{$settings_mode}");
|
||||
|
|
|
@ -3,12 +3,14 @@ namespace Misuzu;
|
|||
|
||||
use Misuzu\Config\ConfigManager;
|
||||
use Misuzu\IO\Directory;
|
||||
use Misuzu\IO\DirectoryDoesNotExistException;
|
||||
use Misuzu\Users\Session;
|
||||
use UnexpectedValueException;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Handles the set up procedures.
|
||||
* @package Misuzu
|
||||
*/
|
||||
class Application extends ApplicationBase
|
||||
{
|
||||
|
@ -29,31 +31,75 @@ class Application extends ApplicationBase
|
|||
* Session instance.
|
||||
* @var \Misuzu\Users\Session
|
||||
*/
|
||||
private $session = null;
|
||||
private $sessionInstance = null;
|
||||
|
||||
/**
|
||||
* Database instance.
|
||||
* @var \Misuzu\Database
|
||||
*/
|
||||
private $databaseInstance = null;
|
||||
|
||||
/**
|
||||
* ConfigManager instance.
|
||||
* @var \Misuzu\Config\ConfigManager
|
||||
*/
|
||||
private $configInstance = null;
|
||||
|
||||
/**
|
||||
* TemplatingEngine instance.
|
||||
* @var \Misuzu\TemplateEngine
|
||||
*/
|
||||
private $templatingInstance = null;
|
||||
|
||||
/**
|
||||
* Constructor, called by ApplicationBase::start() which also passes the arguments through.
|
||||
* @param ?string $configFile
|
||||
* @param bool $debug
|
||||
* @param null|string $configFile
|
||||
* @param bool $debug
|
||||
*/
|
||||
protected function __construct(?string $configFile = null, bool $debug = false)
|
||||
public function __construct(?string $configFile = null, bool $debug = false)
|
||||
{
|
||||
parent::__construct();
|
||||
$this->debugMode = $debug;
|
||||
ExceptionHandler::register();
|
||||
ExceptionHandler::debug($this->debugMode);
|
||||
$this->addModule('config', new ConfigManager($configFile));
|
||||
$this->configInstance = new ConfigManager($configFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets instance of the config manager.
|
||||
* @return ConfigManager
|
||||
*/
|
||||
public function getConfig(): ConfigManager
|
||||
{
|
||||
if (is_null($this->configInstance)){
|
||||
throw new UnexpectedValueException('Internal ConfigManager instance is null, how did you even manage to do this?');
|
||||
}
|
||||
|
||||
return $this->configInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shuts the application down.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
ExceptionHandler::unregister();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether we're in debug mode or not.
|
||||
* @return bool
|
||||
*/
|
||||
public function inDebugMode(): bool
|
||||
{
|
||||
return $this->debugMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a storage path.
|
||||
* @param string $path
|
||||
* @return string
|
||||
*/
|
||||
public function getPath(string $path): string
|
||||
{
|
||||
if (!starts_with($path, '/')) {
|
||||
|
@ -63,14 +109,19 @@ class Application extends ApplicationBase
|
|||
return Directory::fixSlashes(rtrim($path, '/'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data storage path, with config storage path prefix.
|
||||
* @param string $append
|
||||
* @return Directory
|
||||
* @throws DirectoryDoesNotExistException
|
||||
* @throws IO\DirectoryExistsException
|
||||
*/
|
||||
public function getStoragePath(string $append = ''): Directory
|
||||
{
|
||||
$path = '';
|
||||
|
||||
if (starts_with($append, '/')) {
|
||||
$path = $append;
|
||||
} else {
|
||||
$path = $this->config->get('Storage', 'path', 'string', __DIR__ . '/../store');
|
||||
$path = $this->getConfig()->get('Storage', 'path', 'string', __DIR__ . '/../store');
|
||||
|
||||
if (!empty($append)) {
|
||||
$path .= '/' . $append;
|
||||
|
@ -80,17 +131,33 @@ class Application extends ApplicationBase
|
|||
return Directory::createOrOpen($this->getPath($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a data store, with config overrides!
|
||||
* @param string $purpose
|
||||
* @return Directory
|
||||
* @throws DirectoryDoesNotExistException
|
||||
* @throws IO\DirectoryExistsException
|
||||
*/
|
||||
public function getStore(string $purpose): Directory
|
||||
{
|
||||
$override_key = 'override_' . str_replace('/', '_', $purpose);
|
||||
|
||||
if ($this->config->contains('Storage', $override_key)) {
|
||||
return new Directory($this->config->get('Storage', $override_key));
|
||||
if ($this->configInstance->contains('Storage', $override_key)) {
|
||||
try {
|
||||
return new Directory($this->configInstance->get('Storage', $override_key));
|
||||
} catch (DirectoryDoesNotExistException $ex) {
|
||||
// fall through and just get the default path.
|
||||
}
|
||||
}
|
||||
|
||||
return $this->getStoragePath($purpose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a user session.
|
||||
* @param int $user_id
|
||||
* @param string $session_key
|
||||
*/
|
||||
public function startSession(int $user_id, string $session_key): void
|
||||
{
|
||||
$session = Session::where('session_key', $session_key)
|
||||
|
@ -106,14 +173,22 @@ class Application extends ApplicationBase
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current session instance.
|
||||
* @return Session|null
|
||||
*/
|
||||
public function getSession(): ?Session
|
||||
{
|
||||
return $this->session;
|
||||
return $this->sessionInstance;
|
||||
}
|
||||
|
||||
public function setSession(?Session $session): void
|
||||
/**
|
||||
* Registers a session.
|
||||
* @param Session|null $sessionInstance
|
||||
*/
|
||||
public function setSession(?Session $sessionInstance): void
|
||||
{
|
||||
$this->session = $session;
|
||||
$this->sessionInstance = $sessionInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,21 +196,34 @@ class Application extends ApplicationBase
|
|||
*/
|
||||
public function startDatabase(): void
|
||||
{
|
||||
if ($this->hasDatabase) {
|
||||
if (!is_null($this->databaseInstance)) {
|
||||
throw new UnexpectedValueException('Database module has already been started.');
|
||||
}
|
||||
|
||||
$this->addModule('database', new Database($this->config, self::DATABASE_CONNECTIONS[0]));
|
||||
$this->databaseInstance = new Database($this->configInstance, self::DATABASE_CONNECTIONS[0]);
|
||||
$this->loadDatabaseConnections();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the active database instance.
|
||||
* @return Database
|
||||
*/
|
||||
public function getDatabase(): Database
|
||||
{
|
||||
if (is_null($this->databaseInstance)) {
|
||||
throw new UnexpectedValueException('Internal database instance is null, did you run startDatabase yet?');
|
||||
}
|
||||
|
||||
return $this->databaseInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the required database connections defined in the DATABASE_CONNECTIONS constant.
|
||||
*/
|
||||
private function loadDatabaseConnections(): void
|
||||
{
|
||||
$config = $this->config;
|
||||
$database = $this->database;
|
||||
$config = $this->getConfig();
|
||||
$database = $this->getDatabase();
|
||||
|
||||
foreach (self::DATABASE_CONNECTIONS as $name) {
|
||||
$section = "Database.{$name}";
|
||||
|
@ -153,31 +241,44 @@ class Application extends ApplicationBase
|
|||
*/
|
||||
public function startTemplating(): void
|
||||
{
|
||||
if ($this->hasTemplating) {
|
||||
if (!is_null($this->templatingInstance)) {
|
||||
throw new UnexpectedValueException('Templating module has already been started.');
|
||||
}
|
||||
|
||||
$this->addModule('templating', $twig = new TemplateEngine);
|
||||
$twig->debug($this->debugMode);
|
||||
$this->templatingInstance = new TemplateEngine;
|
||||
$this->templatingInstance->debug($this->debugMode);
|
||||
|
||||
$twig->var('globals', [
|
||||
'site_name' => $this->config->get('Site', 'name', 'string', 'Flashii'),
|
||||
'site_description' => $this->config->get('Site', 'description'),
|
||||
'site_twitter' => $this->config->get('Site', 'twitter'),
|
||||
'site_url' => $this->config->get('Site', 'url'),
|
||||
$this->templatingInstance->var('globals', [
|
||||
'site_name' => $this->configInstance->get('Site', 'name', 'string', 'Flashii'),
|
||||
'site_description' => $this->configInstance->get('Site', 'description'),
|
||||
'site_twitter' => $this->configInstance->get('Site', 'twitter'),
|
||||
'site_url' => $this->configInstance->get('Site', 'url'),
|
||||
]);
|
||||
|
||||
$twig->addFilter('json_decode');
|
||||
$twig->addFilter('byte_symbol');
|
||||
$twig->addFilter('country_name', 'get_country_name');
|
||||
$twig->addFilter('flip', 'array_flip');
|
||||
$twig->addFilter('create_pagination');
|
||||
$twig->addFilter('first_paragraph');
|
||||
$this->templatingInstance->addFilter('json_decode');
|
||||
$this->templatingInstance->addFilter('byte_symbol');
|
||||
$this->templatingInstance->addFilter('country_name', 'get_country_name');
|
||||
$this->templatingInstance->addFilter('flip', 'array_flip');
|
||||
$this->templatingInstance->addFilter('create_pagination');
|
||||
$this->templatingInstance->addFilter('first_paragraph');
|
||||
|
||||
$twig->addFunction('git_hash', [Application::class, 'gitCommitHash']);
|
||||
$twig->addFunction('git_branch', [Application::class, 'gitBranch']);
|
||||
$twig->addFunction('csrf_token', 'tmp_csrf_token');
|
||||
$this->templatingInstance->addFunction('git_hash', [Application::class, 'gitCommitHash']);
|
||||
$this->templatingInstance->addFunction('git_branch', [Application::class, 'gitBranch']);
|
||||
$this->templatingInstance->addFunction('csrf_token', 'tmp_csrf_token');
|
||||
|
||||
$twig->var('app', $this);
|
||||
$this->templatingInstance->var('app', $this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an instance of the templating engine.
|
||||
* @return TemplateEngine
|
||||
*/
|
||||
public function getTemplating(): TemplateEngine
|
||||
{
|
||||
if (is_null($this->templatingInstance)){
|
||||
throw new UnexpectedValueException('Internal templating engine instance is null, did you run startDatabase yet?');
|
||||
}
|
||||
|
||||
return $this->templatingInstance;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,18 +35,15 @@ abstract class ApplicationBase
|
|||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of whichever class extends ApplicationBase.
|
||||
* I have no idea how to make a param for the ... thingy so ech.
|
||||
* @return ApplicationBase
|
||||
* ApplicationBase constructor.
|
||||
*/
|
||||
public static function start(...$params): ApplicationBase
|
||||
public function __construct()
|
||||
{
|
||||
if (!is_null(self::$instance) || self::$instance instanceof ApplicationBase) {
|
||||
throw new UnexpectedValueException('An Application has already been set up.');
|
||||
}
|
||||
|
||||
self::$instance = new static(...$params);
|
||||
return self::getInstance();
|
||||
self::$instance = $this;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,42 +74,4 @@ abstract class ApplicationBase
|
|||
{
|
||||
return trim(shell_exec('git rev-parse --abbrev-ref HEAD'));
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
if (starts_with($name, 'has') && strlen($name) > 3 && ctype_upper($name[3])) {
|
||||
$name = lcfirst(substr($name, 3));
|
||||
return $this->hasModule($name);
|
||||
}
|
||||
|
||||
if ($this->hasModule($name)) {
|
||||
return $this->modules[$name];
|
||||
}
|
||||
|
||||
throw new InvalidArgumentException('Invalid property.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a module to this application.
|
||||
* @param string $name
|
||||
* @param mixed $module
|
||||
*/
|
||||
public function addModule(string $name, $module): void
|
||||
{
|
||||
if ($this->hasModule($name)) {
|
||||
throw new InvalidArgumentException('This module has already been registered.');
|
||||
}
|
||||
|
||||
$this->modules[$name] = $module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a module is registered.
|
||||
* @param string $name
|
||||
* @return bool
|
||||
*/
|
||||
public function hasModule(string $name): bool
|
||||
{
|
||||
return array_key_exists($name, $this->modules) && !is_null($this->modules[$name]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,27 +3,54 @@ namespace Misuzu;
|
|||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Colour
|
||||
* @package Misuzu
|
||||
*/
|
||||
class Colour
|
||||
{
|
||||
/**
|
||||
* Flag to set when this this should inherit a parent's colour.
|
||||
*/
|
||||
private const INHERIT = 0x40000000;
|
||||
|
||||
/**
|
||||
* Raw colour value, 32-bit integer (although only 25 bits are used).
|
||||
* @var int
|
||||
*/
|
||||
private $rawValue = 0;
|
||||
|
||||
/**
|
||||
* Gets the raw colour value.
|
||||
* @return int
|
||||
*/
|
||||
public function getRaw(): int
|
||||
{
|
||||
return $this->rawValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a raw colour value.
|
||||
* @param int $raw
|
||||
*/
|
||||
public function setRaw(int $raw): void
|
||||
{
|
||||
$this->rawValue = $raw;
|
||||
$this->rawValue = $raw & 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the inheritance flag is set.
|
||||
* @return bool
|
||||
*/
|
||||
public function getInherit(): bool
|
||||
{
|
||||
return ($this->rawValue & self::INHERIT) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the inheritance flag.
|
||||
* @param bool $state
|
||||
*/
|
||||
public function setInherit(bool $state): void
|
||||
{
|
||||
if ($state) {
|
||||
|
@ -33,11 +60,19 @@ class Colour
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the red colour byte.
|
||||
* @return int
|
||||
*/
|
||||
public function getRed(): int
|
||||
{
|
||||
return $this->rawValue >> 16 & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the red colour byte.
|
||||
* @param int $red
|
||||
*/
|
||||
public function setRed(int $red): void
|
||||
{
|
||||
$red = $red & 0xFF;
|
||||
|
@ -45,11 +80,19 @@ class Colour
|
|||
$this->rawValue |= $red << 16;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the green colour byte.
|
||||
* @return int
|
||||
*/
|
||||
public function getGreen(): int
|
||||
{
|
||||
return $this->rawValue >> 8 & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the green colour byte.
|
||||
* @param int $green
|
||||
*/
|
||||
public function setGreen(int $green): void
|
||||
{
|
||||
$green = $green & 0xFF;
|
||||
|
@ -57,11 +100,19 @@ class Colour
|
|||
$this->rawValue |= $green << 8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the blue colour byte.
|
||||
* @return int
|
||||
*/
|
||||
public function getBlue(): int
|
||||
{
|
||||
return $this->rawValue & 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blue colour byte.
|
||||
* @param int $blue
|
||||
*/
|
||||
public function setBlue(int $blue): void
|
||||
{
|
||||
$blue = $blue & 0xFF;
|
||||
|
@ -69,16 +120,30 @@ class Colour
|
|||
$this->rawValue |= $blue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hexidecimal value for this colour, without # prefix.
|
||||
* @return string
|
||||
*/
|
||||
public function getHex(): string
|
||||
{
|
||||
return dechex_pad($this->getRed()) . dechex_pad($this->getGreen()) . dechex_pad($this->getBlue());
|
||||
return dechex_pad($this->getRaw() & 0xFFFFFF, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Colour constructor.
|
||||
* @param int|null $raw
|
||||
*/
|
||||
public function __construct(?int $raw)
|
||||
{
|
||||
$this->rawValue = $raw ?? self::INHERIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $red
|
||||
* @param int $green
|
||||
* @param int $blue
|
||||
* @return Colour
|
||||
*/
|
||||
public static function fromRGB(int $red, int $green, int $blue): Colour
|
||||
{
|
||||
$raw = 0;
|
||||
|
@ -88,6 +153,10 @@ class Colour
|
|||
return new static($raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $hex
|
||||
* @return Colour
|
||||
*/
|
||||
public static function fromHex(string $hex): Colour
|
||||
{
|
||||
$hex = ltrim(strtolower($hex), '#');
|
||||
|
@ -106,11 +175,18 @@ class Colour
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Colour
|
||||
*/
|
||||
public static function none(): Colour
|
||||
{
|
||||
return new static(static::INHERIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hexidecimal value for this colour, with # prefix.
|
||||
* @return string
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
return "#{$this->getHex()}";
|
||||
|
|
|
@ -21,7 +21,7 @@ class ConfigManager
|
|||
|
||||
/**
|
||||
* Creates a file object with the given path and reloads the context.
|
||||
* @param string $filename
|
||||
* @param string|null $filename
|
||||
*/
|
||||
public function __construct(?string $filename = null)
|
||||
{
|
||||
|
@ -162,8 +162,11 @@ class ConfigManager
|
|||
}
|
||||
|
||||
/**
|
||||
* Serialises the $this->collection array to the human readable config format.
|
||||
* @return string
|
||||
* Serialises the $this->collection array to the human managable config format.
|
||||
* @param string $filename
|
||||
* @param array $collection
|
||||
* @throws \Misuzu\IO\FileDoesNotExistException
|
||||
* @throws \Misuzu\IO\IOException
|
||||
*/
|
||||
public static function write(string $filename, array $collection): void
|
||||
{
|
||||
|
@ -188,7 +191,10 @@ class ConfigManager
|
|||
|
||||
/**
|
||||
* Parses the config file.
|
||||
* @param string $config
|
||||
* @param string $filename
|
||||
* @return array
|
||||
* @throws \Misuzu\IO\FileDoesNotExistException
|
||||
* @throws \Misuzu\IO\IOException
|
||||
*/
|
||||
private static function read(string $filename): array
|
||||
{
|
||||
|
|
|
@ -5,10 +5,20 @@ use Illuminate\Database\Capsule\Manager as LaravelDatabaseManager;
|
|||
use Misuzu\Config\ConfigManager;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Database
|
||||
* @package Misuzu
|
||||
*/
|
||||
class Database extends LaravelDatabaseManager
|
||||
{
|
||||
/**
|
||||
* @var ConfigManager
|
||||
*/
|
||||
private $configManager;
|
||||
|
||||
/**
|
||||
* Array of supported abstraction layers, primarily depends on what Illuminate\Database supports in reality.
|
||||
*/
|
||||
private const SUPPORTED_DB_ALS = [
|
||||
'mysql',
|
||||
'sqlite',
|
||||
|
@ -16,12 +26,33 @@ class Database extends LaravelDatabaseManager
|
|||
'sqlsrv',
|
||||
];
|
||||
|
||||
/**
|
||||
* The default port for MySQL.
|
||||
*/
|
||||
private const DEFAULT_PORT_MYSQL = 3306;
|
||||
|
||||
/**
|
||||
* The default port for PostgreSQL.
|
||||
*/
|
||||
private const DEFAULT_PORT_PGSQL = 5432;
|
||||
|
||||
/**
|
||||
* Default port for Microsoft SQL Server.
|
||||
*/
|
||||
private const DEFAULT_PORT_MSSQL = 1433;
|
||||
|
||||
/**
|
||||
* Default hostname.
|
||||
*/
|
||||
private const DEFAULT_HOST = '127.0.0.1';
|
||||
|
||||
/**
|
||||
* Database constructor.
|
||||
* @param ConfigManager $config
|
||||
* @param string $default
|
||||
* @param bool $startEloquent
|
||||
* @param bool $setAsGlobal
|
||||
*/
|
||||
public function __construct(
|
||||
ConfigManager $config,
|
||||
string $default = 'default',
|
||||
|
@ -42,6 +73,11 @@ class Database extends LaravelDatabaseManager
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new connection using a ConfigManager instance.
|
||||
* @param string $section
|
||||
* @param string $name
|
||||
*/
|
||||
public function addConnectionFromConfig(string $section, string $name = 'default'): void
|
||||
{
|
||||
if (!$this->configManager->contains($section, 'driver')) {
|
||||
|
|
|
@ -78,10 +78,10 @@ class ExceptionHandler
|
|||
|
||||
/**
|
||||
* Converts regular errors to ErrorException instances.
|
||||
* @param int $severity
|
||||
* @param int $severity
|
||||
* @param string $message
|
||||
* @param string $file
|
||||
* @param int $line
|
||||
* @param int $line
|
||||
* @throws ErrorException
|
||||
*/
|
||||
public static function error(int $severity, string $message, string $file, int $line): void
|
||||
|
@ -91,7 +91,7 @@ class ExceptionHandler
|
|||
|
||||
/**
|
||||
* Shoots a POST request to the report URL.
|
||||
* @todo Implement this (depends on Aitemu\Net\WebClient).
|
||||
* @todo Implement this.
|
||||
* @param Throwable $exception
|
||||
*/
|
||||
private static function report(Throwable $exception): void
|
||||
|
|
|
@ -14,18 +14,31 @@ class Directory
|
|||
*/
|
||||
private $path;
|
||||
|
||||
/**
|
||||
* Directory separator used on this system, usually either \ for Windows or / for basically everything else.
|
||||
*/
|
||||
public const SEPARATOR = DIRECTORY_SEPARATOR;
|
||||
|
||||
/**
|
||||
* Get the path of this directory.
|
||||
* @return string
|
||||
*/
|
||||
public function getPath(): string
|
||||
{
|
||||
return $this->path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return is_readable($this->getPath());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return is_writable($this->getPath());
|
||||
|
@ -71,8 +84,9 @@ class Directory
|
|||
/**
|
||||
* Creates a directory if it doesn't already exist.
|
||||
* @param string $path
|
||||
* @throws DirectoryExistsException
|
||||
* @return Directory
|
||||
* @throws DirectoryDoesNotExistException
|
||||
* @throws DirectoryExistsException
|
||||
*/
|
||||
public static function create(string $path): Directory
|
||||
{
|
||||
|
@ -95,6 +109,12 @@ class Directory
|
|||
return new static($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $path
|
||||
* @return Directory
|
||||
* @throws DirectoryDoesNotExistException
|
||||
* @throws DirectoryExistsException
|
||||
*/
|
||||
public static function createOrOpen(string $path): Directory
|
||||
{
|
||||
if (static::exists($path)) {
|
||||
|
@ -107,8 +127,9 @@ class Directory
|
|||
/**
|
||||
* Deletes a directory, recursively if requested. Use $purge with care!
|
||||
* @param string $path
|
||||
* @param bool $purge
|
||||
* @param bool $purge
|
||||
* @throws DirectoryDoesNotExistException
|
||||
* @throws FileDoesNotExistException
|
||||
*/
|
||||
public static function delete(string $path, bool $purge = false): void
|
||||
{
|
||||
|
@ -147,6 +168,7 @@ class Directory
|
|||
/**
|
||||
* Fixes operating system specific slashing.
|
||||
* @param string $path
|
||||
* @param string $separator
|
||||
* @return string
|
||||
*/
|
||||
public static function fixSlashes(string $path, string $separator = self::SEPARATOR): string
|
||||
|
|
|
@ -10,11 +10,22 @@ use Exception;
|
|||
*/
|
||||
class File
|
||||
{
|
||||
/**
|
||||
* @param string $filename
|
||||
* @return FileStream
|
||||
* @throws FileDoesNotExistException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static function open(string $filename): FileStream
|
||||
{
|
||||
return new FileStream($filename, FileStream::MODE_READ_WRITE, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param bool $lock
|
||||
* @return string
|
||||
*/
|
||||
public static function readToEnd(string $filename, bool $lock = false): string
|
||||
{
|
||||
$output = '';
|
||||
|
@ -29,6 +40,12 @@ class File
|
|||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $filename
|
||||
* @param string $data
|
||||
* @throws FileDoesNotExistException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static function writeAll(string $filename, string $data): void
|
||||
{
|
||||
$file = new FileStream($filename, FileStream::MODE_TRUNCATE, true);
|
||||
|
@ -40,6 +57,8 @@ class File
|
|||
* Creates an instance of a temporary file.
|
||||
* @param string $prefix
|
||||
* @return FileStream
|
||||
* @throws FileDoesNotExistException
|
||||
* @throws IOException
|
||||
*/
|
||||
public static function temp(string $prefix = 'Misuzu'): FileStream
|
||||
{
|
||||
|
|
|
@ -3,14 +3,45 @@ namespace Misuzu\IO;
|
|||
|
||||
use ErrorException;
|
||||
|
||||
/**
|
||||
* Class FileStream
|
||||
* @package Misuzu\IO
|
||||
*/
|
||||
class FileStream extends Stream
|
||||
{
|
||||
/**
|
||||
* Open a file for reading only.
|
||||
*/
|
||||
public const MODE_READ = 0x1;
|
||||
|
||||
/**
|
||||
* Open a file for writing only.
|
||||
*/
|
||||
public const MODE_WRITE = 0x2;
|
||||
|
||||
/**
|
||||
* Truncate a file.
|
||||
*/
|
||||
private const MODE_TRUNCATE_RAW = 0x4;
|
||||
|
||||
/**
|
||||
* Append to a file.
|
||||
*/
|
||||
private const MODE_APPEND_RAW = 0x8;
|
||||
|
||||
/**
|
||||
* Open a file for reading and writing.
|
||||
*/
|
||||
public const MODE_READ_WRITE = self::MODE_READ | self::MODE_WRITE;
|
||||
|
||||
/**
|
||||
* Truncate and open a file for writing.
|
||||
*/
|
||||
public const MODE_TRUNCATE = self::MODE_TRUNCATE_RAW | self::MODE_WRITE;
|
||||
|
||||
/**
|
||||
* Open a file for writing and append to the end.
|
||||
*/
|
||||
public const MODE_APPEND = self::MODE_APPEND_RAW | self::MODE_WRITE;
|
||||
|
||||
protected $fileHandle;
|
||||
|
@ -18,6 +49,14 @@ class FileStream extends Stream
|
|||
protected $fileMode;
|
||||
protected $isLocked;
|
||||
|
||||
/**
|
||||
* FileStream constructor.
|
||||
* @param string $path
|
||||
* @param int $mode
|
||||
* @param bool $lock
|
||||
* @throws FileDoesNotExistException
|
||||
* @throws IOException
|
||||
*/
|
||||
public function __construct(string $path, int $mode, bool $lock = true)
|
||||
{
|
||||
$this->isLocked = $lock;
|
||||
|
@ -37,6 +76,9 @@ class FileStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears up the resources used by this stream.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (!is_resource($this->fileHandle)) {
|
||||
|
@ -46,6 +88,12 @@ class FileStream extends Stream
|
|||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file mode string from our own flags.
|
||||
* @param int $mode
|
||||
* @return string
|
||||
* @throws IOException
|
||||
*/
|
||||
protected static function constructFileMode(int $mode): string
|
||||
{
|
||||
$mode_read = ($mode & static::MODE_READ) > 0;
|
||||
|
@ -84,18 +132,25 @@ class FileStream extends Stream
|
|||
|
||||
// should be at least two characters because of the b flag
|
||||
if (strlen($mode_string) < 2) {
|
||||
throw IOException('Failed to construct mode???');
|
||||
throw new IOException('Failed to construct mode???');
|
||||
}
|
||||
|
||||
return $mode_string;
|
||||
}
|
||||
|
||||
public function getResource(): resource
|
||||
/**
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public function getResource(): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
return $this->fileHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function ensureHandleActive(): void
|
||||
{
|
||||
if (!is_resource($this->fileHandle)) {
|
||||
|
@ -103,6 +158,9 @@ class FileStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function ensureCanRead(): void
|
||||
{
|
||||
if (!$this->getCanRead()) {
|
||||
|
@ -110,6 +168,9 @@ class FileStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function ensureCanWrite(): void
|
||||
{
|
||||
if (!$this->getCanWrite()) {
|
||||
|
@ -117,6 +178,9 @@ class FileStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function ensureCanSeek(): void
|
||||
{
|
||||
if (!$this->getCanSeek()) {
|
||||
|
@ -124,54 +188,86 @@ class FileStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanRead(): bool
|
||||
{
|
||||
return ($this->fileMode & static::MODE_READ) > 0 && is_readable($this->filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanSeek(): bool
|
||||
{
|
||||
return ($this->fileMode & static::MODE_APPEND_RAW) == 0 && $this->getCanRead();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanTimeout(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanWrite(): bool
|
||||
{
|
||||
return ($this->fileMode & static::MODE_WRITE) > 0 && is_writable($this->filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public function getLength(): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
return fstat($this->fileHandle)['size'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public function getPosition(): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
return ftell($this->fileHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getReadTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWriteTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
public function flush(): void
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
fflush($this->fileHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
@ -183,6 +279,11 @@ class FileStream extends Stream
|
|||
fclose($this->fileHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $length
|
||||
* @return string
|
||||
* @throws IOException
|
||||
*/
|
||||
public function read(int $length): string
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
@ -197,6 +298,10 @@ class FileStream extends Stream
|
|||
return $read;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public function readChar(): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
@ -205,6 +310,11 @@ class FileStream extends Stream
|
|||
return ord(fgetc($this->fileHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public function write(string $data): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
@ -219,11 +329,20 @@ class FileStream extends Stream
|
|||
return $write;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $char
|
||||
* @throws IOException
|
||||
*/
|
||||
public function writeChar(int $char): void
|
||||
{
|
||||
$this->write(chr($char), 0, 1);
|
||||
$this->write(chr($char));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int $origin
|
||||
* @throws IOException
|
||||
*/
|
||||
public function seek(int $offset, int $origin): void
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
<?php
|
||||
namespace Misuzu\IO;
|
||||
|
||||
/**
|
||||
* Provides a wrapper for fsockopen.
|
||||
* Class NetworkStream
|
||||
* @package Misuzu\IO
|
||||
*/
|
||||
class NetworkStream extends Stream
|
||||
{
|
||||
protected $resourceHandle;
|
||||
|
@ -8,6 +13,13 @@ class NetworkStream extends Stream
|
|||
protected $port;
|
||||
protected $timeout;
|
||||
|
||||
/**
|
||||
* NetworkStream constructor.
|
||||
* @param string $host
|
||||
* @param int $port
|
||||
* @param int|null $timeout
|
||||
* @throws IOException
|
||||
*/
|
||||
public function __construct(string $host, int $port = -1, ?int $timeout = null)
|
||||
{
|
||||
$this->host = $host;
|
||||
|
@ -27,6 +39,9 @@ class NetworkStream extends Stream
|
|||
$this->ensureHandleActive();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans the resources used by this object up.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (!is_resource($this->resourceHandle)) {
|
||||
|
@ -36,12 +51,19 @@ class NetworkStream extends Stream
|
|||
$this->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return resource
|
||||
* @throws IOException
|
||||
*/
|
||||
public function getResource(): resource
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
return $this->resourceHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
protected function ensureHandleActive(): void
|
||||
{
|
||||
if (!is_resource($this->resourceHandle)) {
|
||||
|
@ -49,58 +71,93 @@ class NetworkStream extends Stream
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanRead(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanSeek(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanTimeout(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function getCanWrite(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getLength(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getPosition(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getReadTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getWriteTimeout(): int
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
public function flush(): void
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
fflush($this->resourceHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IOException
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
fclose($this->resourceHandle);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $length
|
||||
* @return string
|
||||
* @throws IOException
|
||||
*/
|
||||
public function read(int $length): string
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
@ -114,13 +171,21 @@ class NetworkStream extends Stream
|
|||
return $read;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public function readChar(): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
||||
return ord(fgetc($this->resourceHandle));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $data
|
||||
* @return int
|
||||
* @throws IOException
|
||||
*/
|
||||
public function write(string $data): int
|
||||
{
|
||||
$this->ensureHandleActive();
|
||||
|
@ -134,12 +199,19 @@ class NetworkStream extends Stream
|
|||
return $write;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $char
|
||||
* @throws IOException
|
||||
*/
|
||||
public function writeChar(int $char): void
|
||||
{
|
||||
$this->write(chr($char), 0, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $offset
|
||||
* @param int $origin
|
||||
* @throws IOException
|
||||
* @SuppressWarnings("unused")
|
||||
*/
|
||||
public function seek(int $offset, int $origin): void
|
||||
|
|
|
@ -3,6 +3,18 @@ namespace Misuzu\IO;
|
|||
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Stream
|
||||
* @package Misuzu\IO
|
||||
* @property-read bool $canRead
|
||||
* @property-read bool $canSeek
|
||||
* @property-read bool $canTimeout
|
||||
* @property-read bool $canWrite
|
||||
* @property-read int $length
|
||||
* @property-read int $position
|
||||
* @property-read int $readTimeout
|
||||
* @property-read int $writeTimeout
|
||||
*/
|
||||
abstract class Stream
|
||||
{
|
||||
public const ORIGIN_CURRENT = 0;
|
||||
|
|
|
@ -1,9 +1,16 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Closure;
|
||||
use Illuminate\Database\Eloquent\Model as BaseModel;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class Model
|
||||
* @package Misuzu
|
||||
* @property-read \Carbon\Carbon|null $created_at
|
||||
* @property-read \Carbon\Carbon|null $updated_at
|
||||
*/
|
||||
abstract class Model extends BaseModel
|
||||
{
|
||||
}
|
||||
|
|
|
@ -10,40 +10,84 @@ use InvalidArgumentException;
|
|||
*/
|
||||
final class IPAddress
|
||||
{
|
||||
/**
|
||||
* Default IP Address if $_SERVER['REMOTE_ADDR'] is not set.
|
||||
*/
|
||||
private const FALLBACK_ADDRESS = '::1';
|
||||
|
||||
/**
|
||||
* Fallback version number.
|
||||
*/
|
||||
public const UNKNOWN_VERSION = 0;
|
||||
|
||||
/**
|
||||
* IPv4.
|
||||
*/
|
||||
public const V4 = 4;
|
||||
|
||||
/**
|
||||
* IPv6.
|
||||
*/
|
||||
public const V6 = 6;
|
||||
|
||||
/**
|
||||
* String lengths of expanded IP addresses.
|
||||
*/
|
||||
public const BYTE_COUNT = [
|
||||
self::V4 => 4,
|
||||
self::V6 => 16,
|
||||
];
|
||||
|
||||
/**
|
||||
* IP address version.
|
||||
* @var int
|
||||
*/
|
||||
private $ipVersion = self::UNKNOWN_VERSION;
|
||||
|
||||
/**
|
||||
* Raw IP address.
|
||||
* @var null|string
|
||||
*/
|
||||
private $ipRaw = null;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getVersion(): int
|
||||
{
|
||||
return $this->ipVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRaw(): string
|
||||
{
|
||||
return $this->ipRaw;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getString(): string
|
||||
{
|
||||
return inet_ntop($this->ipRaw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets GeoIP country for this IP address.
|
||||
* @return string
|
||||
*/
|
||||
public function getCountryCode(): string
|
||||
{
|
||||
return get_country_code($this->getString());
|
||||
}
|
||||
|
||||
/**
|
||||
* IPAddress constructor.
|
||||
* @param int $version
|
||||
* @param string $rawIp
|
||||
*/
|
||||
public function __construct(int $version, string $rawIp)
|
||||
{
|
||||
if (!array_key_exists($version, self::BYTE_COUNT)) {
|
||||
|
@ -58,6 +102,12 @@ final class IPAddress
|
|||
$this->ipRaw = $rawIp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares one IP to another.
|
||||
* @param IPAddress $other
|
||||
* @return int
|
||||
* @throws InvalidArgumentException If the versions of the IP mismatch.
|
||||
*/
|
||||
public function compareTo(IPAddress $other): int
|
||||
{
|
||||
if ($other->getVersion() !== $this->getVersion()) {
|
||||
|
@ -83,6 +133,11 @@ final class IPAddress
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the remote address.
|
||||
* @param string $fallbackAddress
|
||||
* @return IPAddress
|
||||
*/
|
||||
public static function remote(string $fallbackAddress = self::FALLBACK_ADDRESS): IPAddress
|
||||
{
|
||||
try {
|
||||
|
@ -92,6 +147,11 @@ final class IPAddress
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an IPAddress instance from just a raw IP string.
|
||||
* @param string $rawIp
|
||||
* @return IPAddress
|
||||
*/
|
||||
public static function fromRaw(string $rawIp): IPAddress
|
||||
{
|
||||
$version = self::detectVersionFromRaw($rawIp);
|
||||
|
@ -103,6 +163,11 @@ final class IPAddress
|
|||
return new static($version, $rawIp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an IPAddress instance from a human readable address string.
|
||||
* @param string $ipAddress
|
||||
* @return IPAddress
|
||||
*/
|
||||
public static function fromString(string $ipAddress): IPAddress
|
||||
{
|
||||
$version = self::detectVersionFromString($ipAddress);
|
||||
|
@ -114,6 +179,11 @@ final class IPAddress
|
|||
return new static($version, inet_pton($ipAddress));
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the version of a raw address string.
|
||||
* @param string $rawIp
|
||||
* @return int
|
||||
*/
|
||||
public static function detectVersionFromRaw(string $rawIp): int
|
||||
{
|
||||
$rawLength = strlen($rawIp);
|
||||
|
@ -127,6 +197,11 @@ final class IPAddress
|
|||
return self::UNKNOWN_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects the version of a human readable address string.
|
||||
* @param string $ipAddress
|
||||
* @return int
|
||||
*/
|
||||
public static function detectVersionFromString(string $ipAddress): int
|
||||
{
|
||||
if (filter_var($ipAddress, FILTER_VALIDATE_IP) === false) {
|
||||
|
|
|
@ -5,19 +5,37 @@ use InvalidArgumentException;
|
|||
|
||||
final class IPAddressRange
|
||||
{
|
||||
/**
|
||||
* @var IPAddress
|
||||
*/
|
||||
private $maskAddress;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $cidrLength;
|
||||
|
||||
/**
|
||||
* @return IPAddress
|
||||
*/
|
||||
public function getMaskAddress(): IPAddress
|
||||
{
|
||||
return $this->maskAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getCidrLength(): int
|
||||
{
|
||||
return $this->cidrLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* IPAddressRange constructor.
|
||||
* @param IPAddress $maskAddress
|
||||
* @param int $cidrLength
|
||||
*/
|
||||
public function __construct(IPAddress $maskAddress, int $cidrLength)
|
||||
{
|
||||
if ($cidrLength > IPAddress::BYTE_COUNT[$maskAddress->getVersion()] * 8) {
|
||||
|
@ -28,11 +46,21 @@ final class IPAddressRange
|
|||
$this->cidrLength = $cidrLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a conventional <MASKED ADDRESS>/<CIDR LENGTH> format string.
|
||||
* @return string
|
||||
*/
|
||||
public function getMaskedString(): string
|
||||
{
|
||||
return $this->getMaskAddress()->getString() . '/' . $this->getCidrLength();
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches an IPAddress to this range.
|
||||
* @param IPAddress $ipAddress
|
||||
* @param bool $explicitExceptions
|
||||
* @return bool
|
||||
*/
|
||||
public function match(IPAddress $ipAddress, bool $explicitExceptions = false): bool
|
||||
{
|
||||
if ($ipAddress->getVersion() !== $this->getMaskAddress()->getVersion()) {
|
||||
|
@ -62,6 +90,11 @@ final class IPAddressRange
|
|||
return $this->getMaskAddress()->getRaw() === pack('N*', ...$ipParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an IPAddressRange instance using the conventional notation format.
|
||||
* @param string $maskedString
|
||||
* @return IPAddressRange
|
||||
*/
|
||||
public static function fromMaskedString(string $maskedString): IPAddressRange
|
||||
{
|
||||
if (strpos($maskedString, '/') === false) {
|
||||
|
@ -75,8 +108,14 @@ final class IPAddressRange
|
|||
return new static($maskedAddress, $cidrLength);
|
||||
}
|
||||
|
||||
// very uncertain about this logic in regards to any ip larger than 32 bits
|
||||
// if you _do_ know what you're doing, review this and call me an idiot please
|
||||
/**
|
||||
* Creates an IPAddresRange instance from a dash separated range.
|
||||
* I'm very uncertain about the logic here when it comes to addresses larger than 32 bits.
|
||||
* If you do know what you're doing, please review this and call me an idiot.
|
||||
*
|
||||
* @param string $rangeString
|
||||
* @return IPAddressRange
|
||||
*/
|
||||
public static function fromRangeString(string $rangeString): IPAddressRange
|
||||
{
|
||||
if (strpos($rangeString, '-') === false) {
|
||||
|
|
|
@ -3,11 +3,23 @@ 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');
|
||||
|
|
|
@ -6,23 +6,48 @@ use Misuzu\Users\User;
|
|||
use Misuzu\Model;
|
||||
use Parsedown;
|
||||
|
||||
/**
|
||||
* Class NewsPost
|
||||
* @package Misuzu\News
|
||||
* @property-read int $post_id
|
||||
* @property int $category_id
|
||||
* @property bool $is_featured
|
||||
* @property int $user_id
|
||||
* @property string $post_title
|
||||
* @property string $post_text
|
||||
* @property Carbon $scheduled_for
|
||||
* @property Carbon|null $deleted_at
|
||||
* @property-read User $user
|
||||
* @property-read NewsCategory $category
|
||||
*/
|
||||
final class NewsPost extends Model
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
protected $table = 'news_posts';
|
||||
protected $primaryKey = 'post_id';
|
||||
protected $dates = ['scheduled_for'];
|
||||
|
||||
/**
|
||||
* Parses post_text and returns the final HTML.
|
||||
* @return string
|
||||
*/
|
||||
public function getHtml(): string
|
||||
{
|
||||
return (new Parsedown)->text($this->post_text);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function category()
|
||||
{
|
||||
return $this->belongsTo(NewsCategory::class, 'category_id');
|
||||
|
|
|
@ -37,7 +37,11 @@ class TemplateEngine
|
|||
private $vars = [];
|
||||
|
||||
/**
|
||||
* Creates the twig environment.
|
||||
* TemplateEngine constructor.
|
||||
* @param null|string $cache
|
||||
* @param bool $strict
|
||||
* @param bool $autoReload
|
||||
* @param bool $debug
|
||||
*/
|
||||
public function __construct(
|
||||
?string $cache = null,
|
||||
|
@ -106,11 +110,14 @@ class TemplateEngine
|
|||
*/
|
||||
public function addPath(string $name, string $path): void
|
||||
{
|
||||
if (count($this->loader->getPaths()) < 1) {
|
||||
$this->loader->addPath($path);
|
||||
}
|
||||
try {
|
||||
if (count($this->loader->getPaths()) < 1) {
|
||||
$this->loader->addPath($path);
|
||||
}
|
||||
|
||||
$this->loader->addPath($path, $name);
|
||||
$this->loader->addPath($path, $name);
|
||||
} catch (\Twig_Error_Loader $e) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,9 +156,12 @@ class TemplateEngine
|
|||
|
||||
/**
|
||||
* Renders a template file.
|
||||
* @param string $path
|
||||
* @param array $vars
|
||||
* @param string $path
|
||||
* @param array|null $vars
|
||||
* @return string
|
||||
* @throws \Twig_Error_Loader
|
||||
* @throws \Twig_Error_Runtime
|
||||
* @throws \Twig_Error_Syntax
|
||||
*/
|
||||
public function render(string $path, ?array $vars = null): string
|
||||
{
|
||||
|
|
|
@ -5,20 +5,57 @@ 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,
|
||||
|
@ -31,7 +68,7 @@ class LoginAttempt extends Model
|
|||
$attempt->user_agent = $userAgent ?? '';
|
||||
|
||||
if ($user !== null) {
|
||||
$attempt->user_id = $user;
|
||||
$attempt->user_id = $user->user_id;
|
||||
}
|
||||
|
||||
$attempt->save();
|
||||
|
@ -39,32 +76,40 @@ class LoginAttempt extends Model
|
|||
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);
|
||||
}
|
||||
|
||||
public function setUserIdAttribute(User $user): void
|
||||
{
|
||||
$this->attributes['user_id'] = $user->user_id;
|
||||
}
|
||||
|
||||
public function getUserIdAttribute(int $userId): User
|
||||
{
|
||||
return User::findOrFail($userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Object relation definition for User.
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
|
|
|
@ -4,10 +4,35 @@ namespace Misuzu\Users;
|
|||
use Misuzu\Colour;
|
||||
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 Colour $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 Colour|null $colour
|
||||
* @param null|string $title
|
||||
* @param null|string $description
|
||||
* @param bool $secret
|
||||
* @return Role
|
||||
*/
|
||||
public static function createRole(
|
||||
string $name,
|
||||
?int $hierarchy = null,
|
||||
|
@ -25,47 +50,83 @@ class Role extends Model
|
|||
$role->role_title = $title;
|
||||
$role->role_description = $description;
|
||||
$role->role_secret = $secret;
|
||||
$role->role_colour = $colour->raw;
|
||||
$role->role_colour = $colour->getRaw();
|
||||
$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_colour attribute.
|
||||
* @param int $colour
|
||||
* @return Colour
|
||||
*/
|
||||
public function getRoleColourAttribute(int $colour): Colour
|
||||
{
|
||||
return new Colour($colour);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for the role_colour attribute.
|
||||
* @param Colour $colour
|
||||
*/
|
||||
public function setRoleColourAttribute(Colour $colour): void
|
||||
{
|
||||
$this->attributes['role_colour'] = $colour->getRaw();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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');
|
||||
|
|
|
@ -5,11 +5,39 @@ 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,
|
||||
|
@ -31,27 +59,48 @@ class Session extends Model
|
|||
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');
|
||||
|
|
|
@ -8,22 +8,96 @@ use Misuzu\Database;
|
|||
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,
|
||||
|
@ -44,6 +118,11 @@ class User extends Model
|
|||
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);
|
||||
|
@ -52,6 +131,12 @@ class User extends Model
|
|||
->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);
|
||||
|
@ -87,6 +172,12 @@ class User extends Model
|
|||
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) {
|
||||
|
@ -104,16 +195,24 @@ class User extends Model
|
|||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a password string.
|
||||
* @param string $password
|
||||
* @return string
|
||||
*/
|
||||
public static function validatePassword(string $password): string
|
||||
{
|
||||
if (password_entropy($password) < 32) {
|
||||
if (password_entropy($password) < self::PASSWORD_MIN_ENTROPY) {
|
||||
return 'weak';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
// it's probably safe to assume that this will always return a valid role
|
||||
/**
|
||||
* 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) {
|
||||
|
@ -123,12 +222,20 @@ class User extends Model
|
|||
return $this->displayRoleInstance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the display colour.
|
||||
* @return Colour
|
||||
*/
|
||||
public function getDisplayColour(): Colour
|
||||
{
|
||||
$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)) {
|
||||
|
@ -144,6 +251,10 @@ class User extends Model
|
|||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the user title (with memoization).
|
||||
* @return string
|
||||
*/
|
||||
public function getUserTitle(): string
|
||||
{
|
||||
if (empty($this->userTitleValue)) {
|
||||
|
@ -153,6 +264,11 @@ class User extends Model
|
|||
return $this->userTitleValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns a role.
|
||||
* @param Role $role
|
||||
* @param bool $setDisplay
|
||||
*/
|
||||
public function addRole(Role $role, bool $setDisplay = false): void
|
||||
{
|
||||
$relation = new UserRole;
|
||||
|
@ -165,6 +281,10 @@ class User extends Model
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a role.
|
||||
* @param Role $role
|
||||
*/
|
||||
public function removeRole(Role $role): void
|
||||
{
|
||||
UserRole::where('user_id', $this->user_id)
|
||||
|
@ -172,6 +292,11 @@ class User extends Model
|
|||
->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)
|
||||
|
@ -179,6 +304,11 @@ class User extends Model
|
|||
->count() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a password.
|
||||
* @param string $password
|
||||
* @return bool
|
||||
*/
|
||||
public function verifyPassword(string $password): bool
|
||||
{
|
||||
if (password_verify($password, $this->password) !== true) {
|
||||
|
@ -193,6 +323,11 @@ class User extends Model
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the display_role attribute.
|
||||
* @param int|null $value
|
||||
* @return int
|
||||
*/
|
||||
public function getDisplayRoleAttribute(?int $value): int
|
||||
{
|
||||
if (!$this->displayRoleValidated) {
|
||||
|
@ -215,6 +350,10 @@ class User extends Model
|
|||
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) {
|
||||
|
@ -222,51 +361,90 @@ class User extends Model
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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');
|
||||
|
|
|
@ -3,17 +3,31 @@ 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');
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
/**
|
||||
* Class Zalgo
|
||||
* @package Misuzu
|
||||
*/
|
||||
class Zalgo
|
||||
{
|
||||
/**
|
||||
* Characters that crawl upwards.
|
||||
*/
|
||||
private const ZALGO_CHARS_UP = [
|
||||
"\u{030d}", "\u{030e}", "\u{0304}", "\u{0305}", "\u{033f}",
|
||||
"\u{0311}", "\u{0306}", "\u{0310}", "\u{0352}", "\u{0357}",
|
||||
|
@ -16,6 +23,9 @@ class Zalgo
|
|||
"\u{033e}", "\u{035b}", "\u{0346}", "\u{031a}",
|
||||
];
|
||||
|
||||
/**
|
||||
* Characters that crawl downwards.
|
||||
*/
|
||||
private const ZALGO_CHARS_DOWN = [
|
||||
"\u{0316}", "\u{0317}", "\u{0318}", "\u{0319}", "\u{031c}",
|
||||
"\u{031d}", "\u{031e}", "\u{031f}", "\u{0320}", "\u{0324}",
|
||||
|
@ -27,6 +37,9 @@ class Zalgo
|
|||
"\u{0355}", "\u{0356}", "\u{0359}", "\u{035a}", "\u{0323}",
|
||||
];
|
||||
|
||||
/**
|
||||
* Characters that dwell in the middle.
|
||||
*/
|
||||
private const ZALGO_CHARS_MIDDLE = [
|
||||
"\u{0315}", "\u{031b}", "\u{0340}", "\u{0341}", "\u{0358}",
|
||||
"\u{0321}", "\u{0322}", "\u{0327}", "\u{0328}", "\u{0334}",
|
||||
|
@ -35,20 +48,50 @@ class Zalgo
|
|||
"\u{0337}", "\u{0361}", "\u{0489}",
|
||||
];
|
||||
|
||||
/**
|
||||
* Minimal Zalgo.
|
||||
*/
|
||||
public const ZALGO_MODE_MINI = 1;
|
||||
|
||||
/**
|
||||
* Medium Zalgo.
|
||||
*/
|
||||
public const ZALGO_MODE_NORMAL = 2;
|
||||
|
||||
/**
|
||||
* MAXIMUM ZALGO.
|
||||
*/
|
||||
public const ZALGO_MODE_MAX = 3;
|
||||
|
||||
/**
|
||||
* Index of valid modes.
|
||||
*/
|
||||
private const ZALGO_MODES = [
|
||||
self::ZALGO_MODE_MINI,
|
||||
self::ZALGO_MODE_NORMAL,
|
||||
self::ZALGO_MODE_MAX,
|
||||
];
|
||||
|
||||
/**
|
||||
* Flag for having characters crawl upwards.
|
||||
*/
|
||||
public const ZALGO_DIR_UP = 0x1;
|
||||
|
||||
/**
|
||||
* Flag for having characters dwell in the middle.
|
||||
*/
|
||||
public const ZALGO_DIR_MID = 0x2;
|
||||
|
||||
/**
|
||||
* Flag for having characters crawl downwards.
|
||||
*/
|
||||
public const ZALGO_DIR_DOWN = 0x4;
|
||||
|
||||
/**
|
||||
* (Try to) restore a Zalgofied string back to normal.
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public static function strip(string $text): string
|
||||
{
|
||||
$text = str_replace(self::ZALGO_CHARS_UP, '', $text);
|
||||
|
@ -58,6 +101,11 @@ class Zalgo
|
|||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a string for Zalgo characters.
|
||||
* @param string $char
|
||||
* @return bool
|
||||
*/
|
||||
private static function isZalgo(string $char): bool
|
||||
{
|
||||
return in_array($char, self::ZALGO_CHARS_UP)
|
||||
|
@ -65,6 +113,12 @@ class Zalgo
|
|||
|| in_array($char, self::ZALGO_CHARS_DOWN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a random Zalgo character.
|
||||
* @param array $array
|
||||
* @param int $length
|
||||
* @return string
|
||||
*/
|
||||
private static function getZalgo(array $array, int $length): string
|
||||
{
|
||||
$string = '';
|
||||
|
@ -76,6 +130,13 @@ class Zalgo
|
|||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs Zalgo over a string.
|
||||
* @param string $text
|
||||
* @param int $mode
|
||||
* @param int $direction
|
||||
* @return string
|
||||
*/
|
||||
public static function run(
|
||||
string $text,
|
||||
int $mode = self::ZALGO_MODE_MINI,
|
||||
|
@ -105,6 +166,9 @@ class Zalgo
|
|||
}
|
||||
|
||||
$str .= $char;
|
||||
$num_up = 0;
|
||||
$num_mid = 0;
|
||||
$num_down = 0;
|
||||
|
||||
switch ($mode) {
|
||||
case self::ZALGO_MODE_MINI:
|
||||
|
|
Loading…
Add table
Reference in a new issue