Backlog of things.

This commit is contained in:
flash 2018-04-01 00:28:32 +02:00
parent 5b828279a1
commit c1259ef503
22 changed files with 490 additions and 211 deletions

View file

@ -0,0 +1,20 @@
.button {
display: inline-block;
font: 12px/20px @manage-font;
background-color: #666;
border: 1px solid #555;
border-radius: 2px;
text-decoration: none;
color: #111;
padding: 2px 5px;
cursor: pointer;
transition: background-color .2s;
&:hover {
background-color: #777;
}
&:active {
background-color: #666;
}
}

View file

@ -5,4 +5,16 @@
&--center {
text-align: center;
}
&__title {
font-size: 2em;
line-height: 1.7em;
padding: 4px;
}
&__subtitle {
font-size: 1.5em;
line-height: 1.5em;
padding: 4px;
}
}

View file

@ -0,0 +1,14 @@
.form {
&__label {
display: inline-block;
background-color: #222;
border: 1px solid #333;
border-radius: 2px;
min-width: 250px;
margin: 2px;
padding: 2px;
min-height: 50px;
vertical-align: top;
}
}

View file

@ -0,0 +1,9 @@
.input {
background: #333;
border: 1px solid #444;
border-radius: 2px;
padding: 2px;
color: #fff;
width: 100%;
vertical-align: top;
}

View file

@ -3,15 +3,22 @@
flex-direction: column;
&__entry {
border-right: 4px solid #a00;
border-right: 4px solid #666;
padding-right: 1px;
display: block;
text-decoration: none;
color: inherit;
&:not(:last-child) {
margin-bottom: 2px;
}
&__selector {
vertical-align: top;
}
&__content {
padding: 2px;
width: 100%;
background-color: #333;
min-height: 50px;

View file

@ -4,13 +4,12 @@
justify-content: center;
&__entry {
min-width: 296px;
margin: 2px;
min-width: 296px;
&__content {
display: flex;
align-items: flex-start;
padding: 2px;
}
}
}

View file

@ -1,3 +1,5 @@
@manage-font: 'Open Sans', sans-serif;
* {
margin: 0;
padding: 0;
@ -13,13 +15,16 @@ body {
.manage {
background-color: #222;
font: 12px/20px 'Open Sans', sans-serif;
font: 12px/20px @manage-font;
color: #fff;
}
@import "classes/button";
@import "classes/container";
@import "classes/footer";
@import "classes/form";
@import "classes/header";
@import "classes/input";
@import "classes/listing";
@import "classes/pagination";

View file

@ -17,21 +17,29 @@ if (PHP_SAPI !== 'cli') {
exit;
}
if (isset($_COOKIE['msz_uid'], $_COOKIE['msz_sid'])) {
$app->startSession((int)$_COOKIE['msz_uid'], $_COOKIE['msz_sid']);
}
if (!$app->inDebugMode()) {
ob_start('ob_gzhandler');
}
if ($app->config->get('Auth', 'lockdown', 'bool', false)) {
http_response_code(503);
$app->startTemplating();
$app->templating->addPath('auth', __DIR__ . '/views/auth');
echo $app->templating->render('lockdown');
exit;
}
if (isset($_COOKIE['msz_uid'], $_COOKIE['msz_sid'])) {
$app->startSession((int)$_COOKIE['msz_uid'], $_COOKIE['msz_sid']);
}
$manage_mode = starts_with($_SERVER['REQUEST_URI'], '/manage');
$app->startTemplating();
$app->templating->addPath('mio', __DIR__ . '/views/mio');
if ($manage_mode) {
if (Application::getInstance()->getSession() === null) {
if (Application::getInstance()->getSession() === null || $_SERVER['HTTP_HOST'] !== 'misuzu.misaka.nl') {
http_response_code(403);
echo $app->templating->render('errors.403');
exit;

View file

@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\ModelNotFoundException;
use Misuzu\Application;
use Misuzu\Database;
use Misuzu\Net\IPAddress;
use Misuzu\Users\Role;
use Misuzu\Users\User;
use Misuzu\Users\Session;
use Misuzu\Users\LoginAttempt;
@ -166,7 +167,8 @@ switch ($mode) {
break;
}
User::createUser($username, $password, $email);
$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.');
break;
}

View file

@ -0,0 +1,110 @@
<?php
use Misuzu\Application;
use Misuzu\Colour;
use Misuzu\Users\Role;
require_once __DIR__ . '/../../misuzu.php';
$role_mode = (string)($_GET['m'] ?? 'list');
$role_id = (int)($_GET['i'] ?? 0);
while ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
echo 'csrf err';
break;
}
if (!in_array($role_mode, ['create', 'edit'], true)) {
echo 'invalid mode';
break;
}
if (!isset($_POST['role'])) {
echo 'no';
break;
}
$role_name = $_POST['role']['name'] ?? '';
$role_name_length = strlen($role_name);
if ($role_name_length < 1 || $role_name_length > 255) {
echo 'invalid name length';
break;
}
$role_secret = !empty($_POST['role']['secret']);
$role_hierarchy = (int)($_POST['role']['hierarchy'] ?? -1);
if ($role_hierarchy < 1 || $role_hierarchy > 100) {
echo 'Invalid hierarchy value.';
break;
}
$role_colour = Colour::none();
$role_colour->setInherit(!empty($_POST['role']['colour']['inherit']));
if (!$role_colour->getInherit()) {
foreach (['red', 'green', 'blue'] as $key) {
$value = (int)($_POST['role']['colour'][$key] ?? -1);
$setter = 'set' . ucfirst($key);
if ($value < 0 || $value > 0xFF) {
echo 'invalid colour value';
break 2;
}
$role_colour->{$setter}($value);
}
}
$role_description = $_POST['role']['description'] ?? '';
if (strlen($role_description) > 1000) {
echo 'description is too long';
break;
}
$edit_role = $role_id < 1 ? new Role : Role::find($role_id);
$edit_role->role_name = $role_name;
$edit_role->role_hierarchy = $role_hierarchy;
$edit_role->role_secret = $role_secret;
$edit_role->role_colour = $role_colour;
$edit_role->role_description = $role_description;
$edit_role->save();
header('Location: ?m=list');
break;
}
switch ($role_mode) {
case 'list':
$users_page = (int)($_GET['p'] ?? 1);
$manage_roles = Role::paginate(32, ['*'], 'p', $users_page);
$app->templating->vars(compact('manage_roles'));
echo $app->templating->render('@manage.users.roles');
break;
case 'edit':
if (!isset($edit_role)) {
if ($role_id < 1) {
echo 'no';
break;
}
$edit_role = Role::find($role_id);
}
if ($edit_role === null) {
echo 'invalid role';
break;
}
$app->templating->vars(compact('edit_role'));
// no break
case 'create':
echo $app->templating->render('@manage.users.roles_create');
break;
}

View file

@ -9,81 +9,69 @@ class Colour
private $rawValue = 0;
public function __get(string $name)
public function getRaw(): int
{
switch ($name) {
case 'raw':
return $this->rawValue;
case 'inherit':
return ($this->rawValue & self::INHERIT) > 0;
case 'red':
return $this->rawValue >> 16 & 0xFF;
case 'green':
return $this->rawValue >> 8 & 0xFF;
case 'blue':
return $this->rawValue & 0xFF;
case 'hex':
return dechex_pad($this->red) . dechex_pad($this->green) . dechex_pad($this->blue);
}
return null;
}
public function __set(string $name, $value): void
public function setRaw(int $raw): void
{
switch ($name) {
case 'raw':
if (!is_int32($value) && !is_uint32($value)) {
break;
$this->rawValue = $raw;
}
$this->rawValue = $value;
break;
case 'inherit':
if (!is_bool($value)) {
break;
public function getInherit(): bool
{
return ($this->rawValue & self::INHERIT) > 0;
}
if ($value) {
public function setInherit(bool $state): void
{
if ($state) {
$this->rawValue |= self::INHERIT;
} else {
$this->rawValue &= ~self::INHERIT;
}
break;
case 'red':
if (!is_byte($value)) {
break;
}
public function getRed(): int
{
return $this->rawValue >> 16 & 0xFF;
}
public function setRed(int $red): void
{
$red = $red & 0xFF;
$this->rawValue &= ~0xFF0000;
$this->rawValue |= $value << 16;
break;
case 'green':
if (!is_byte($value)) {
break;
$this->rawValue |= $red << 16;
}
public function getGreen(): int
{
return $this->rawValue >> 8 & 0xFF;
}
public function setGreen(int $green): void
{
$green = $green & 0xFF;
$this->rawValue &= ~0xFF00;
$this->rawValue |= $value << 8;
break;
case 'blue':
if (!is_byte($value)) {
break;
$this->rawValue |= $green << 8;
}
public function getBlue(): int
{
return $this->rawValue & 0xFF;
}
public function setBlue(int $blue): void
{
$blue = $blue & 0xFF;
$this->rawValue &= ~0xFF;
$this->rawValue |= $value;
break;
$this->rawValue |= $blue;
}
public function getHex(): string
{
return dechex_pad($this->getRed()) . dechex_pad($this->getGreen()) . dechex_pad($this->getBlue());
}
public function __construct(?int $raw)
@ -125,6 +113,6 @@ class Colour
public function __toString()
{
return "#{$this->hex}";
return "#{$this->getHex()}";
}
}

View file

@ -14,6 +14,8 @@ class Directory
*/
private $path;
public const SEPARATOR = DIRECTORY_SEPARATOR;
public function getPath(): string
{
return $this->path;
@ -58,12 +60,12 @@ class Directory
}
return realpath($path);
}, glob($this->path . '/' . $pattern));
}, glob($this->path . self::SEPARATOR . $pattern));
}
public function filename(string $filename): string
{
return $this->getPath() . '/' . $filename;
return $this->getPath() . self::SEPARATOR . $filename;
}
/**
@ -78,11 +80,12 @@ class Directory
throw new DirectoryExistsException;
}
$split_path = explode('/', $path);
$existing_path = '/';
$path = Directory::fixSlashes($path);
$split_path = explode(self::SEPARATOR, $path);
$existing_path = running_on_windows() ? '' : self::SEPARATOR;
foreach ($split_path as $path_part) {
$existing_path .= $path_part . '/';
$existing_path .= $path_part . self::SEPARATOR;
if (!Directory::exists($existing_path)) {
mkdir($existing_path);
@ -146,8 +149,8 @@ class Directory
* @param string $path
* @return string
*/
public static function fixSlashes(string $path): string
public static function fixSlashes(string $path, string $separator = self::SEPARATOR): string
{
return str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $path);
return str_replace(['/', '\\'], $separator, $path);
}
}

View file

@ -46,6 +46,26 @@ class Role extends Model
return $user->hasRole($this);
}
public function getRoleColourAttribute(int $colour): Colour
{
return new Colour($colour);
}
public function setRoleColourAttribute(Colour $colour): void
{
$this->attributes['role_colour'] = $colour->getRaw();
}
public function getRoleDescriptionAttribute(?string $description): string
{
return empty($description) ? '' : $description;
}
public function setRoleDescriptionAttribute(string $description): void
{
$this->attributes['role_description'] = empty($description) ? null : $description;
}
public function users()
{
return $this->hasMany(UserRole::class, 'role_id');

View file

@ -2,6 +2,7 @@
namespace Misuzu\Users;
use Illuminate\Database\Eloquent\SoftDeletes;
use Misuzu\Colour;
use Misuzu\Database;
use Misuzu\Model;
use Misuzu\Net\IPAddress;
@ -109,6 +110,12 @@ class User extends Model
return '';
}
public function getDisplayColour(): Colour
{
$role = Role::find($this->display_role);
return $role === null ? Colour::none() : $role->role_colour;
}
public function addRole(Role $role, bool $setDisplay = false): void
{
$relation = new UserRole;

View file

@ -25,72 +25,72 @@ class ColourTest extends TestCase
{
$colour = Colour::none();
$this->assertTrue($colour->inherit);
$this->assertEquals($colour->raw, 0x40000000);
$this->assertEquals($colour->red, 0);
$this->assertEquals($colour->green, 0);
$this->assertEquals($colour->blue, 0);
$this->assertEquals($colour->hex, '000000');
$this->assertTrue($colour->getInherit());
$this->assertEquals($colour->getRaw(), 0x40000000);
$this->assertEquals($colour->getRed(), 0);
$this->assertEquals($colour->getGreen(), 0);
$this->assertEquals($colour->getBlue(), 0);
$this->assertEquals($colour->getHex(), '000000');
}
public function testNull()
{
$colour = new Colour(null);
$this->assertTrue($colour->inherit);
$this->assertEquals($colour->raw, 0x40000000);
$this->assertEquals($colour->red, 0);
$this->assertEquals($colour->green, 0);
$this->assertEquals($colour->blue, 0);
$this->assertEquals($colour->hex, '000000');
$this->assertTrue($colour->getInherit());
$this->assertEquals($colour->getRaw(), 0x40000000);
$this->assertEquals($colour->getRed(), 0);
$this->assertEquals($colour->getGreen(), 0);
$this->assertEquals($colour->getBlue(), 0);
$this->assertEquals($colour->getHex(), '000000');
}
public function testFromRaw()
{
$colour = new Colour(static::RAW_HEX6);
$this->assertEquals($colour->hex, static::STR_HEX6);
$this->assertEquals($colour->raw, static::RAW_HEX6);
$this->assertEquals($colour->red, static::RED_HEX6);
$this->assertEquals($colour->green, static::GREEN_HEX6);
$this->assertEquals($colour->blue, static::BLUE_HEX6);
$this->assertFalse($colour->inherit);
$this->assertEquals($colour->getHex(), static::STR_HEX6);
$this->assertEquals($colour->getRaw(), static::RAW_HEX6);
$this->assertEquals($colour->getRed(), static::RED_HEX6);
$this->assertEquals($colour->getGreen(), static::GREEN_HEX6);
$this->assertEquals($colour->getBlue(), static::BLUE_HEX6);
$this->assertFalse($colour->getInherit());
}
public function testFromRGB()
{
$colour = Colour::fromRGB(static::RED_HEX6, static::GREEN_HEX6, static::BLUE_HEX6);
$this->assertEquals($colour->hex, static::STR_HEX6);
$this->assertEquals($colour->raw, static::RAW_HEX6);
$this->assertEquals($colour->red, static::RED_HEX6);
$this->assertEquals($colour->green, static::GREEN_HEX6);
$this->assertEquals($colour->blue, static::BLUE_HEX6);
$this->assertFalse($colour->inherit);
$this->assertEquals($colour->getHex(), static::STR_HEX6);
$this->assertEquals($colour->getRaw(), static::RAW_HEX6);
$this->assertEquals($colour->getRed(), static::RED_HEX6);
$this->assertEquals($colour->getGreen(), static::GREEN_HEX6);
$this->assertEquals($colour->getBlue(), static::BLUE_HEX6);
$this->assertFalse($colour->getInherit());
}
public function testFromHex()
{
$colour = Colour::fromHex(static::SSTR_HEX6);
$this->assertEquals($colour->hex, static::STR_HEX6);
$this->assertEquals($colour->raw, static::RAW_HEX6);
$this->assertEquals($colour->red, static::RED_HEX6);
$this->assertEquals($colour->green, static::GREEN_HEX6);
$this->assertEquals($colour->blue, static::BLUE_HEX6);
$this->assertFalse($colour->inherit);
$this->assertEquals($colour->getHex(), static::STR_HEX6);
$this->assertEquals($colour->getRaw(), static::RAW_HEX6);
$this->assertEquals($colour->getRed(), static::RED_HEX6);
$this->assertEquals($colour->getGreen(), static::GREEN_HEX6);
$this->assertEquals($colour->getBlue(), static::BLUE_HEX6);
$this->assertFalse($colour->getInherit());
}
public function testFromHex3()
{
$colour = Colour::fromHex(static::SSTR_HEX3);
$this->assertEquals($colour->hex, static::STR_HEX3);
$this->assertEquals($colour->raw, static::RAW_HEX3);
$this->assertEquals($colour->red, static::RED_HEX3);
$this->assertEquals($colour->green, static::GREEN_HEX3);
$this->assertEquals($colour->blue, static::BLUE_HEX3);
$this->assertFalse($colour->inherit);
$this->assertEquals($colour->getHex(), static::STR_HEX3);
$this->assertEquals($colour->getRaw(), static::RAW_HEX3);
$this->assertEquals($colour->getRed(), static::RED_HEX3);
$this->assertEquals($colour->getGreen(), static::GREEN_HEX3);
$this->assertEquals($colour->getBlue(), static::BLUE_HEX3);
$this->assertFalse($colour->getInherit());
}
/**

View file

@ -202,42 +202,7 @@ function create_pagination($paginator)
return \Illuminate\Pagination\UrlWindow::make($paginator);
}
function is_int_ex($value, int $boundary_low, int $boundary_high): bool
function running_on_windows(): bool
{
return is_int($value) && $value >= $boundary_low && $value <= $boundary_high;
}
function is_sbyte($value): bool
{
return is_int_ex($value, -0x80, 0x7F);
}
function is_byte($value): bool
{
return is_int_ex($value, 0x0, 0xFF);
}
function is_int16($value): bool
{
return is_int_ex($value, -0x8000, 0x7FFF);
}
function is_uint16($value): bool
{
return is_int_ex($value, 0x0, 0xFFFF);
}
function is_int32($value): bool
{
return is_int_ex($value, -0x80000000, 0x7FFFFFFF);
}
function is_uint32($value): bool
{
return is_int_ex($value, 0x0, 0xFFFFFFFF);
}
function is_int64($value): bool
{
return is_int_ex($value, -0x8000000000000000, 0x7FFFFFFFFFFFFFFF);
return starts_with(strtolower(PHP_OS), 'win');
}

9
views/auth/lockdown.twig Normal file
View file

@ -0,0 +1,9 @@
{% extends '@auth/master.twig' %}
{% block content %}
<div class="container logout">
<div class="logout__message">
<p class="logout__paragraph">The site is currently unavailable, try again later.</p>
</div>
</div>
{% endblock %}

View file

@ -14,10 +14,10 @@
{% endfor %}
{% endmacro %}
{% macro paginate(paginator, base_url, className) %}
{% from _self import pagination_segment %}
{% macro paginate(paginator, base_url, className, alwaysRender) %}
{% set alwaysRender = alwaysRender|default(false) %}
{% set url_window = paginator|create_pagination %}
{% if alwaysRender or paginator.hasMorePages %}
{% set separator = '%3F' in base_url|default('')|url_encode ? '&' : '?' %}
{% set base_url = base_url ~ separator %}
@ -38,6 +38,9 @@
<li class="pagination__separator"></li>
{% from _self import pagination_segment %}
{% set url_window = paginator|create_pagination %}
{% if url_window.first is iterable %}
{{ pagination_segment(url_window.first, base_url, paginator.currentPage) }}
<li class="pagination__separator"></li>
@ -67,4 +70,5 @@
</li>
{% endif %}
</ul>
{% endif %}
{% endmacro %}

View file

@ -4,12 +4,13 @@
{% block content %}
<div class="container listing user-listing">
{% for user in manage_users %}
<label class="listing__entry user-listing__entry">
<label class="listing__entry user-listing__entry"{% if not user.displayColour.inherit %} style="border-color: {{ user.displayColour }}"{% endif %}>
<div class="listing__entry__content user-listing__entry__content">
<input type="checkbox">
<input class="listing__entry__selector" type="checkbox">
<a href="/profile.php?u={{ user.user_id }}" class="listing__entry__column user-listing__entry__column user-listing__entry__column--username">
{{ user.username }}
</a>
<div class="user-listing__avatar" style="background-image:url('/profile.php?u={{ user.user_id }}&amp;m=avatar');"></div>
</div>
</label>
{% endfor %}

View file

@ -0,0 +1,23 @@
{% extends '@manage/users/master.twig' %}
{% from '@manage/macros.twig' import paginate %}
{% block content %}
<div class="container">
<a href="?m=create" class="button">Create new Role</a>
</div>
<div class="container listing role-listing">
{% for role in manage_roles %}
<a href="?m=edit&amp;i={{ role.role_id }}" class="listing__entry role-listing__entry"{% if not role.role_colour.inherit %} style="border-color: {{ role.role_colour }}"{% endif %}>
<div class="listing__entry__content role-listing__entry__content">
{{ role.role_name }}
{{ role.users.count }} users
</div>
</a>
{% endfor %}
</div>
<div class="container container--center">
{{ paginate(manage_roles) }}
</div>
{% endblock %}

View file

@ -0,0 +1,73 @@
{% extends '@manage/users/master.twig' %}
{% block content %}
<form action="?m={{ edit_role is defined ? 'edit&i=' ~ edit_role.role_id : 'create' }}" method="post">
<div class="container">
<h1 class="container__title container__title">Creating a new Role</h1>
<label class="form__label">
<div class="form__label__text">Role Name</div>
<div class="form__label__input">
<input class="input input--text" type="text" value="{{ edit_role is defined ? edit_role.role_name : '' }}" name="role[name]" maxlength="255">
</div>
</label>
<label class="form__label">
<div class="form__label__text">Hide Rank</div>
<div class="form__label__input">
<input class="input" type="checkbox" name="role[secret]"{% if edit_role is defined and edit_role.role_secret %} checked{% endif %}>
</div>
</label>
<label class="form__label">
<div class="form__label__text">Hierarchy</div>
<div class="form__label__input">
<input class="input input--number" type="number" value="{{ edit_role is defined ? edit_role.role_hierarchy : '1' }}" min="1" max="100" name="role[hierarchy]">
</div>
</label>
<h2 class="container__subtitle">Colour</h2>
<label class="form__label">
<div class="form__label__text">Inherit Colour</div>
<div class="form__label__input">
<input class="input" type="checkbox" name="role[colour][inherit]"{% if edit_role is defined and edit_role.role_colour.inherit %} checked{% endif %}>
</div>
</label>
<label class="form__label">
<div class="form__label__text">Red</div>
<div class="form__label__input">
<input class="input input--number" type="number" value="{{ edit_role is defined ? edit_role.role_colour.red : '0' }}" min="0" max="255" name="role[colour][red]">
</div>
</label>
<label class="form__label">
<div class="form__label__text">Green</div>
<div class="form__label__input">
<input class="input input--number" type="number" value="{{ edit_role is defined ? edit_role.role_colour.green : '0' }}" min="0" max="255" name="role[colour][green]">
</div>
</label>
<label class="form__label">
<div class="form__label__text">Blue</div>
<div class="form__label__input">
<input class="input input--number" type="number" value="{{ edit_role is defined ? edit_role.role_colour.blue : '0' }}" min="0" max="255" name="role[colour][blue]">
</div>
</label>
<h2 class="container__subtitle">Additional</h2>
<label class="form__label">
<div class="form__label__text">Description</div>
<div class="form__label__input">
<textarea class="input input--textarea" name="role[description]" maxlength="1000">{{ edit_role is defined ? edit_role.role_description : '' }}</textarea>
</div>
</label>
<div>
<button class="button" name="csrf" value="{{ csrf_token() }}">{{ edit_role is defined ? 'Update role' : 'Create role' }}</button>
</div>
</div>
</form>
{% endblock %}

View file

@ -64,10 +64,10 @@
{{ link('https://github.com/flashwave/misuzu/tree/' ~ git_branch(), git_branch(), 'mio__footer__copyright__link') }} # {{ link('https://github.com/flashwave/misuzu/commit/' ~ git_hash(true), git_hash(), 'mio__footer__copyright__link') }}
</div>
<div class="mio__footer__links">
{{ link('#', 'Terms of Service', 'mio__footer__links__link') }}
{{ link('#', 'Rules', 'mio__footer__links__link') }}
{{ link('#', 'Contact', 'mio__footer__links__link') }}
{{ link('#', 'Status', 'mio__footer__links__link') }}
{{ link('#', 'Terms of Service', 'mio__footer__links__link') }} |
{{ link('#', 'Rules', 'mio__footer__links__link') }} |
{{ link('#', 'Contact', 'mio__footer__links__link') }} |
{{ link('#', 'Status', 'mio__footer__links__link') }} |
{{ link('https://twitter.com/flashiinet', 'Twitter', 'mio__footer__links__link') }}
</div>
</footer>