diff --git a/assets/less/manage/classes/button.less b/assets/less/manage/classes/button.less new file mode 100644 index 00000000..cf7b6206 --- /dev/null +++ b/assets/less/manage/classes/button.less @@ -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; + } +} diff --git a/assets/less/manage/classes/container.less b/assets/less/manage/classes/container.less index 81b7edd9..6276c85b 100644 --- a/assets/less/manage/classes/container.less +++ b/assets/less/manage/classes/container.less @@ -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; + } } diff --git a/assets/less/manage/classes/form.less b/assets/less/manage/classes/form.less new file mode 100644 index 00000000..77403787 --- /dev/null +++ b/assets/less/manage/classes/form.less @@ -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; + } +} diff --git a/assets/less/manage/classes/input.less b/assets/less/manage/classes/input.less new file mode 100644 index 00000000..2c06cb79 --- /dev/null +++ b/assets/less/manage/classes/input.less @@ -0,0 +1,9 @@ +.input { + background: #333; + border: 1px solid #444; + border-radius: 2px; + padding: 2px; + color: #fff; + width: 100%; + vertical-align: top; +} diff --git a/assets/less/manage/classes/listing.less b/assets/less/manage/classes/listing.less index bf53791f..7d4c39e7 100644 --- a/assets/less/manage/classes/listing.less +++ b/assets/less/manage/classes/listing.less @@ -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; diff --git a/assets/less/manage/classes/user-listing.less b/assets/less/manage/classes/user-listing.less index 44a537cf..6591e1a6 100644 --- a/assets/less/manage/classes/user-listing.less +++ b/assets/less/manage/classes/user-listing.less @@ -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; } } } diff --git a/assets/less/manage/main.less b/assets/less/manage/main.less index 13e28cda..81c91cd1 100644 --- a/assets/less/manage/main.less +++ b/assets/less/manage/main.less @@ -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"; diff --git a/misuzu.php b/misuzu.php index 1988ad50..9370023a 100644 --- a/misuzu.php +++ b/misuzu.php @@ -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; diff --git a/public/auth.php b/public/auth.php index fdc53cf6..25b29ecb 100644 --- a/public/auth.php +++ b/public/auth.php @@ -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; } diff --git a/public/manage/users-roles.php b/public/manage/users-roles.php new file mode 100644 index 00000000..59ee08d1 --- /dev/null +++ b/public/manage/users-roles.php @@ -0,0 +1,110 @@ + 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; +} diff --git a/src/Colour.php b/src/Colour.php index 5cd1436c..88f7a83f 100644 --- a/src/Colour.php +++ b/src/Colour.php @@ -9,83 +9,71 @@ 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; + return $this->rawValue; } - 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; + public function getInherit(): bool + { + return ($this->rawValue & self::INHERIT) > 0; + } - case 'inherit': - if (!is_bool($value)) { - break; - } - - if ($value) { - $this->rawValue |= self::INHERIT; - } else { - $this->rawValue &= ~self::INHERIT; - } - break; - - case 'red': - if (!is_byte($value)) { - break; - } - - $this->rawValue &= ~0xFF0000; - $this->rawValue |= $value << 16; - break; - - case 'green': - if (!is_byte($value)) { - break; - } - - $this->rawValue &= ~0xFF00; - $this->rawValue |= $value << 8; - break; - - case 'blue': - if (!is_byte($value)) { - break; - } - - $this->rawValue &= ~0xFF; - $this->rawValue |= $value; - break; + public function setInherit(bool $state): void + { + if ($state) { + $this->rawValue |= self::INHERIT; + } else { + $this->rawValue &= ~self::INHERIT; } } + public function getRed(): int + { + return $this->rawValue >> 16 & 0xFF; + } + + public function setRed(int $red): void + { + $red = $red & 0xFF; + $this->rawValue &= ~0xFF0000; + $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 |= $green << 8; + } + + public function getBlue(): int + { + return $this->rawValue & 0xFF; + } + + public function setBlue(int $blue): void + { + $blue = $blue & 0xFF; + $this->rawValue &= ~0xFF; + $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) { $this->rawValue = $raw ?? self::INHERIT; @@ -125,6 +113,6 @@ class Colour public function __toString() { - return "#{$this->hex}"; + return "#{$this->getHex()}"; } } diff --git a/src/IO/Directory.php b/src/IO/Directory.php index 26f3e7fb..160e9b9d 100644 --- a/src/IO/Directory.php +++ b/src/IO/Directory.php @@ -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); } } diff --git a/src/Users/Role.php b/src/Users/Role.php index c65de683..1beb32fb 100644 --- a/src/Users/Role.php +++ b/src/Users/Role.php @@ -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'); diff --git a/src/Users/User.php b/src/Users/User.php index 3dadee46..85758072 100644 --- a/src/Users/User.php +++ b/src/Users/User.php @@ -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; diff --git a/tests/ColourTest.php b/tests/ColourTest.php index 74be54d0..359c1c2e 100644 --- a/tests/ColourTest.php +++ b/tests/ColourTest.php @@ -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()); } /** diff --git a/utility.php b/utility.php index 3f74d746..38e87218 100644 --- a/utility.php +++ b/utility.php @@ -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'); } diff --git a/views/auth/lockdown.twig b/views/auth/lockdown.twig new file mode 100644 index 00000000..2a106d3a --- /dev/null +++ b/views/auth/lockdown.twig @@ -0,0 +1,9 @@ +{% extends '@auth/master.twig' %} + +{% block content %} +
+
+

The site is currently unavailable, try again later.

+
+
+{% endblock %} diff --git a/views/manage/macros.twig b/views/manage/macros.twig index 77dada0f..cd11efb3 100644 --- a/views/manage/macros.twig +++ b/views/manage/macros.twig @@ -14,57 +14,61 @@ {% 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 %} - {% set separator = '%3F' in base_url|default('')|url_encode ? '&' : '?' %} - {% set base_url = base_url ~ separator %} + {% if alwaysRender or paginator.hasMorePages %} + {% set separator = '%3F' in base_url|default('')|url_encode ? '&' : '?' %} + {% set base_url = base_url ~ separator %} - + {% endif %} {% endmacro %} diff --git a/views/manage/users/listing.twig b/views/manage/users/listing.twig index 53312740..474f21e6 100644 --- a/views/manage/users/listing.twig +++ b/views/manage/users/listing.twig @@ -4,12 +4,13 @@ {% block content %}
{% for user in manage_users %} -