2022-09-13 13:14:49 +00:00
|
|
|
<?php
|
|
|
|
namespace Misuzu\Users;
|
|
|
|
|
|
|
|
use ArrayAccess;
|
2023-01-02 23:48:04 +00:00
|
|
|
use Index\Colour\Colour;
|
2022-09-13 13:14:49 +00:00
|
|
|
use Misuzu\DB;
|
|
|
|
use Misuzu\HasRankInterface;
|
|
|
|
use Misuzu\Memoizer;
|
|
|
|
use Misuzu\Pagination;
|
|
|
|
|
|
|
|
class UserRoleException extends UsersException {}
|
|
|
|
class UserRoleNotFoundException extends UserRoleException {}
|
|
|
|
class UserRoleCreationFailedException extends UserRoleException {}
|
|
|
|
|
|
|
|
class UserRole implements ArrayAccess, HasRankInterface {
|
|
|
|
public const DEFAULT = 1;
|
|
|
|
|
|
|
|
// Database fields
|
|
|
|
private $role_id = -1;
|
|
|
|
private $role_hierarchy = 1;
|
|
|
|
private $role_name = '';
|
|
|
|
private $role_title = null;
|
|
|
|
private $role_description = null;
|
|
|
|
private $role_hidden = 0;
|
|
|
|
private $role_can_leave = 0;
|
|
|
|
private $role_colour = null;
|
|
|
|
private $role_created = null;
|
|
|
|
|
|
|
|
public const TABLE = 'roles';
|
|
|
|
private const QUERY_SELECT = 'SELECT %1$s FROM `' . DB::PREFIX . self::TABLE . '` AS '. self::TABLE;
|
|
|
|
private const SELECT = '%1$s.`role_id`, %1$s.`role_hierarchy`, %1$s.`role_name`, %1$s.`role_title`, %1$s.`role_description`'
|
|
|
|
. ', %1$s.`role_hidden`, %1$s.`role_can_leave`, %1$s.`role_colour`'
|
|
|
|
. ', UNIX_TIMESTAMP(%1$s.`role_created`) AS `role_created`';
|
|
|
|
|
|
|
|
private $colour = null;
|
|
|
|
private $users = [];
|
|
|
|
private $userCount = -1;
|
|
|
|
|
|
|
|
public function getId(): int {
|
|
|
|
return $this->role_id < 1 ? -1 : $this->role_id;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getRank(): int {
|
|
|
|
return $this->role_hierarchy;
|
|
|
|
}
|
|
|
|
public function setRank(int $rank): self {
|
|
|
|
$this->role_hierarchy = $rank;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getName(): string {
|
|
|
|
return $this->role_name;
|
|
|
|
}
|
|
|
|
public function setName(string $name): self {
|
|
|
|
$this->role_name = $name;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getTitle(): string {
|
|
|
|
return $this->role_title ?? '';
|
|
|
|
}
|
|
|
|
public function setTitle(string $title): self {
|
|
|
|
$this->role_title = empty($title) ? null : $title;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getDescription(): string {
|
|
|
|
return $this->role_description ?? '';
|
|
|
|
}
|
|
|
|
public function setDescription(string $description): self {
|
|
|
|
$this->role_description = empty($description) ? null : $description;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isHidden(): bool {
|
|
|
|
return boolval($this->role_hidden);
|
|
|
|
}
|
|
|
|
public function setHidden(bool $hidden): self {
|
|
|
|
$this->role_hidden = $hidden ? 1 : 0;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCanLeave(): bool {
|
|
|
|
return boolval($this->role_can_leave);
|
|
|
|
}
|
|
|
|
public function setCanLeave(bool $canLeave): self {
|
|
|
|
$this->role_can_leave = $canLeave ? 1 : 0;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Provided just because, avoid using these for validations sake
|
|
|
|
public function getColourRaw(): ?int {
|
|
|
|
return $this->role_colour;
|
|
|
|
}
|
|
|
|
public function setColourRaw(?int $colour): self {
|
|
|
|
$this->role_colour = $colour;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getColour(): Colour {
|
2023-01-02 23:48:04 +00:00
|
|
|
if($this->colour === null || ($this->role_colour ?? 0x40000000) !== Colour::toMisuzu($this->colour))
|
|
|
|
$this->colour = Colour::fromMisuzu($this->role_colour ?? 0x40000000);
|
2022-09-13 13:14:49 +00:00
|
|
|
return $this->colour;
|
|
|
|
}
|
|
|
|
public function setColour(Colour $colour): self {
|
2023-01-02 23:48:04 +00:00
|
|
|
$this->role_colour = $colour->shouldInherit() ? null : Colour::toMisuzu($colour);
|
2022-09-13 13:14:49 +00:00
|
|
|
$this->colour = $this->colour;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getCreatedTime(): int {
|
|
|
|
return $this->role_created === null ? -1 : $this->role_created;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function getUserCount(): int {
|
|
|
|
if($this->userCount < 0)
|
|
|
|
$this->userCount = UserRoleRelation::countUsers($this);
|
|
|
|
return $this->userCount;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function isDefault(): bool {
|
|
|
|
return $this->getId() === self::DEFAULT;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function hasAuthorityOver(HasRankInterface $other): bool {
|
|
|
|
if($other instanceof User && $other->isSuper())
|
|
|
|
return false;
|
|
|
|
return $this->getRank() > $other->getRank();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function save(): void {
|
|
|
|
$isInsert = $this->role_id < 1;
|
|
|
|
if($isInsert) {
|
|
|
|
$set = DB::prepare(
|
|
|
|
'INSERT INTO `' . DB::PREFIX . self::TABLE . '` (`role_hierarchy`, `role_name`, `role_title`, `role_description`, `role_hidden`, `role_can_leave`, `role_colour`)'
|
|
|
|
. ' VALUES (:rank, :name, :title, :desc, :hide, :can_leave, :colour)'
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$set = DB::prepare(
|
|
|
|
'UPDATE `' . DB::PREFIX . self::TABLE . '` SET'
|
|
|
|
. ' `role_hierarchy` = :rank, `role_name` = :name, `role_title` = :title,'
|
|
|
|
. ' `role_description` = :desc, `role_hidden` = :hide, `role_can_leave` = :can_leave, `role_colour` = :colour'
|
|
|
|
. ' WHERE `role_id` = :role'
|
|
|
|
)->bind('role', $this->role_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
$set->bind('rank', $this->role_hierarchy)
|
|
|
|
->bind('name', $this->role_name)
|
|
|
|
->bind('title', $this->role_title)
|
|
|
|
->bind('desc', $this->role_description)
|
|
|
|
->bind('hide', $this->role_hidden)
|
|
|
|
->bind('can_leave', $this->role_can_leave)
|
|
|
|
->bind('colour', $this->role_colour);
|
|
|
|
|
|
|
|
if($isInsert) {
|
|
|
|
$this->role_id = $set->executeGetId();
|
|
|
|
$this->role_created = time();
|
|
|
|
} else $set->execute();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function countQueryBase(): string {
|
|
|
|
return sprintf(self::QUERY_SELECT, 'COUNT(*)');
|
|
|
|
}
|
|
|
|
public static function countAll(bool $showHidden = false): int {
|
|
|
|
return (int)DB::prepare(
|
|
|
|
self::countQueryBase()
|
|
|
|
. ($showHidden ? '' : ' WHERE `role_hidden` = 0')
|
|
|
|
)->fetchColumn();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function memoizer() {
|
|
|
|
static $memoizer = null;
|
|
|
|
if($memoizer === null)
|
|
|
|
$memoizer = new Memoizer;
|
|
|
|
return $memoizer;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static function byQueryBase(): string {
|
|
|
|
return sprintf(self::QUERY_SELECT, sprintf(self::SELECT, self::TABLE));
|
|
|
|
}
|
|
|
|
public static function byId(int $roleId): self {
|
|
|
|
return self::memoizer()->find($roleId, function() use ($roleId) {
|
|
|
|
$object = DB::prepare(
|
|
|
|
self::byQueryBase() . ' WHERE `role_id` = :role'
|
|
|
|
) ->bind('role', $roleId)
|
|
|
|
->fetchObject(self::class);
|
|
|
|
if(!$object)
|
|
|
|
throw new UserRoleNotFoundException;
|
|
|
|
return $object;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
public static function byDefault(): self {
|
|
|
|
return self::byId(self::DEFAULT);
|
|
|
|
}
|
|
|
|
public static function all(bool $showHidden = false, ?Pagination $pagination = null): array {
|
|
|
|
$query = self::byQueryBase();
|
|
|
|
|
|
|
|
if(!$showHidden)
|
|
|
|
$query .= ' WHERE `role_hidden` = 0';
|
|
|
|
|
|
|
|
if($pagination !== null)
|
|
|
|
$query .= ' LIMIT :range OFFSET :offset';
|
|
|
|
|
|
|
|
$getObjects = DB::prepare($query);
|
|
|
|
|
|
|
|
if($pagination !== null)
|
|
|
|
$getObjects->bind('range', $pagination->getRange())
|
|
|
|
->bind('offset', $pagination->getOffset());
|
|
|
|
|
|
|
|
return $getObjects->fetchObjects(self::class);
|
|
|
|
}
|
|
|
|
|
|
|
|
// to satisfy the fucked behaviour array_diff has
|
|
|
|
public function __toString() {
|
|
|
|
return md5($this->getId() . '#' . $this->getName());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Twig shim for the roles list on the members page, don't use this class as an array normally.
|
|
|
|
public function offsetExists($offset): bool {
|
|
|
|
return $offset === 'name' || $offset === 'id';
|
|
|
|
}
|
|
|
|
public function offsetGet($offset): mixed {
|
|
|
|
return $this->{'get' . ucfirst($offset)}();
|
|
|
|
}
|
|
|
|
public function offsetSet($offset, $value): void {}
|
|
|
|
public function offsetUnset($offset): void {}
|
|
|
|
}
|