Added field filtering to API endpoints and store colour presets in the database.
This commit is contained in:
parent
12d40e69a5
commit
a1398fb179
16 changed files with 727 additions and 117 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
20250326.1
|
||||
20250327
|
||||
|
|
20
database/2025_03_27_213508_colours_presets.php
Normal file
20
database/2025_03_27_213508_colours_presets.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
use Index\Db\DbConnection;
|
||||
use Index\Db\Migration\DbMigration;
|
||||
|
||||
final class ColoursPresets_20250327_213508 implements DbMigration {
|
||||
public function migrate(DbConnection $conn): void {
|
||||
$conn->execute(<<<SQL
|
||||
CREATE TABLE msz_colours_presets (
|
||||
preset_id INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
preset_order INT(11) NOT NULL DEFAULT '0',
|
||||
preset_name VARCHAR(255) NOT NULL COLLATE 'ascii_general_ci',
|
||||
preset_title VARCHAR(255) NULL DEFAULT NULL COLLATE 'utf8mb4_bin',
|
||||
preset_colour INT(11) UNSIGNED NOT NULL,
|
||||
PRIMARY KEY (preset_id),
|
||||
UNIQUE KEY msz_colours_presets_name_unique (preset_name),
|
||||
KEY msz_colours_presets_order_index (preset_order)
|
||||
) COLLATE='utf8mb4_bin' ENGINE=InnoDB;
|
||||
SQL);
|
||||
}
|
||||
}
|
|
@ -19,8 +19,8 @@ if(empty($emoteId))
|
|||
else
|
||||
try {
|
||||
$isNew = false;
|
||||
$emoteInfo = $msz->emotes->getEmote($emoteId);
|
||||
$emoteStrings = iterator_to_array($msz->emotes->getEmoteStrings($emoteInfo));
|
||||
$emoteInfo = $msz->emotesCtx->emotes->getEmote($emoteId);
|
||||
$emoteStrings = iterator_to_array($msz->emotesCtx->emotes->getEmoteStrings($emoteInfo));
|
||||
} catch(RuntimeException $ex) {
|
||||
Template::throwError(404);
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
$strings = explode(' ', !empty($_POST['em_strings']) && is_scalar($_POST['em_strings']) ? trim((string)$_POST['em_strings']) : '');
|
||||
|
||||
if($isNew || $url !== $emoteInfo->url) {
|
||||
$checkUrl = $msz->emotes->checkEmoteUrl($url);
|
||||
$checkUrl = $msz->emotesCtx->emotes->checkEmoteUrl($url);
|
||||
if($checkUrl !== '') {
|
||||
echo match($checkUrl) {
|
||||
'empty' => 'URL may not be empty.',
|
||||
|
@ -49,7 +49,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
$order = null;
|
||||
|
||||
if($isNew) {
|
||||
$emoteInfo = $msz->emotes->createEmote($url, $minRank, $order);
|
||||
$emoteInfo = $msz->emotesCtx->emotes->createEmote($url, $minRank, $order);
|
||||
} else {
|
||||
if($order === $emoteInfo->order)
|
||||
$order = null;
|
||||
|
@ -59,7 +59,7 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
$url = null;
|
||||
|
||||
if($order !== null || $minRank !== null || $url !== null)
|
||||
$msz->emotes->updateEmote($emoteInfo, $order, $minRank, $url);
|
||||
$msz->emotesCtx->emotes->updateEmote($emoteInfo, $order, $minRank, $url);
|
||||
}
|
||||
|
||||
$sCurrent = XArray::select($emoteStrings, fn($stringInfo) => $stringInfo->string);
|
||||
|
@ -69,16 +69,16 @@ while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
|
|||
foreach($sCurrent as $string)
|
||||
if(!in_array($string, $sApply)) {
|
||||
$sRemove[] = $string;
|
||||
$msz->emotes->removeEmoteString($string);
|
||||
$msz->emotesCtx->emotes->removeEmoteString($string);
|
||||
}
|
||||
|
||||
$sCurrent = array_diff($sCurrent, $sRemove);
|
||||
|
||||
foreach($sApply as $string)
|
||||
if(!in_array($string, $sCurrent)) {
|
||||
$checkString = $msz->emotes->checkEmoteString($string);
|
||||
$checkString = $msz->emotesCtx->emotes->checkEmoteString($string);
|
||||
if($checkString === '') {
|
||||
$msz->emotes->addEmoteString($emoteInfo, $string);
|
||||
$msz->emotesCtx->emotes->addEmoteString($emoteInfo, $string);
|
||||
} else {
|
||||
echo match($checkString) {
|
||||
'empty' => 'String may not be empty.',
|
||||
|
|
|
@ -13,26 +13,26 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
|
|||
$emoteId = !empty($_GET['emote']) && is_scalar($_GET['emote']) ? (string)$_GET['emote'] : '';
|
||||
|
||||
try {
|
||||
$emoteInfo = $msz->emotes->getEmote($emoteId);
|
||||
$emoteInfo = $msz->emotesCtx->emotes->getEmote($emoteId);
|
||||
} catch(RuntimeException $ex) {
|
||||
Template::throwError(404);
|
||||
}
|
||||
|
||||
if(!empty($_GET['delete'])) {
|
||||
$msz->emotes->deleteEmote($emoteInfo);
|
||||
$msz->emotesCtx->emotes->deleteEmote($emoteInfo);
|
||||
$msz->createAuditLog('EMOTICON_DELETE', [$emoteInfo->id]);
|
||||
} else {
|
||||
if(isset($_GET['order'])) {
|
||||
$order = !empty($_GET['order']) && is_scalar($_GET['order']) ? (string)$_GET['order'] : '';
|
||||
$offset = $order === 'i' ? 10 : ($order === 'd' ? -10 : 0);
|
||||
$msz->emotes->updateEmoteOrderOffset($emoteInfo, $offset);
|
||||
$msz->emotesCtx->emotes->updateEmoteOrderOffset($emoteInfo, $offset);
|
||||
$msz->createAuditLog('EMOTICON_ORDER', [$emoteInfo->id]);
|
||||
}
|
||||
|
||||
if(isset($_GET['alias'])) {
|
||||
$alias = !empty($_GET['alias']) && is_scalar($_GET['alias']) ? (string)$_GET['alias'] : '';
|
||||
if($msz->emotes->checkEmoteString($alias) === '') {
|
||||
$msz->emotes->addEmoteString($emoteInfo, $alias);
|
||||
if($msz->emotesCtx->emotes->checkEmoteString($alias) === '') {
|
||||
$msz->emotesCtx->emotes->addEmoteString($emoteInfo, $alias);
|
||||
$msz->createAuditLog('EMOTICON_ALIAS', [$emoteInfo->id, $alias]);
|
||||
}
|
||||
}
|
||||
|
@ -43,5 +43,5 @@ if(CSRF::validateRequest() && !empty($_GET['emote'])) {
|
|||
}
|
||||
|
||||
Template::render('manage.general.emoticons', [
|
||||
'emotes' => $msz->emotes->getEmotes(),
|
||||
'emotes' => $msz->emotesCtx->emotes->getEmotes(),
|
||||
]);
|
||||
|
|
8
src/Colours/ColourPresetGetField.php
Normal file
8
src/Colours/ColourPresetGetField.php
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
namespace Misuzu\Colours;
|
||||
|
||||
enum ColourPresetGetField {
|
||||
case Id;
|
||||
case Name;
|
||||
case IdOrName;
|
||||
}
|
29
src/Colours/ColourPresetInfo.php
Normal file
29
src/Colours/ColourPresetInfo.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
namespace Misuzu\Colours;
|
||||
|
||||
use Index\Colour\ColourRgb;
|
||||
use Index\Db\DbResult;
|
||||
|
||||
class ColourPresetInfo {
|
||||
public function __construct(
|
||||
public private(set) string $id,
|
||||
public private(set) int $order,
|
||||
public private(set) string $name,
|
||||
public private(set) ?string $title,
|
||||
public private(set) int $colour,
|
||||
) {}
|
||||
|
||||
public static function fromResult(DbResult $result): ColourPresetInfo {
|
||||
return new ColourPresetInfo(
|
||||
id: $result->getString(0),
|
||||
order: $result->getInteger(1),
|
||||
name: $result->getString(2),
|
||||
title: $result->getStringOrNull(3),
|
||||
colour: $result->getInteger(4),
|
||||
);
|
||||
}
|
||||
|
||||
public function toColour(): ColourRgb {
|
||||
return ColourRgb::fromRawArgb($this->colour);
|
||||
}
|
||||
}
|
145
src/Colours/ColourPresetsData.php
Normal file
145
src/Colours/ColourPresetsData.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
namespace Misuzu\Colours;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Index\Colour\Colour;
|
||||
use Index\Db\{DbConnection,DbStatementCache};
|
||||
|
||||
class ColourPresetsData {
|
||||
private DbStatementCache $cache;
|
||||
|
||||
public function __construct(DbConnection $dbConn) {
|
||||
$this->cache = new DbStatementCache($dbConn);
|
||||
}
|
||||
|
||||
/** @return iterable<ColourPresetInfo> */
|
||||
public function getPresets(): iterable {
|
||||
$query = <<<SQL
|
||||
SELECT preset_id, preset_order, preset_name, preset_title, preset_colour
|
||||
FROM msz_colours_presets
|
||||
ORDER BY preset_order
|
||||
SQL;
|
||||
|
||||
$stmt = $this->cache->get($query);
|
||||
$stmt->execute();
|
||||
|
||||
return $stmt->getResultIterator(ColourPresetInfo::fromResult(...));
|
||||
}
|
||||
|
||||
public function getPreset(
|
||||
string $value,
|
||||
ColourPresetGetField $field = ColourPresetGetField::Id
|
||||
): ColourPresetInfo {
|
||||
$field = match($field) {
|
||||
ColourPresetGetField::Id => 'preset_id = ?',
|
||||
ColourPresetGetField::Name => 'preset_name = ?',
|
||||
ColourPresetGetField::IdOrName => sprintf('preset_%s = ?', ctype_digit($value) ? 'id' : 'name'),
|
||||
};
|
||||
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
SELECT preset_id, preset_order, preset_name, preset_title, preset_colour
|
||||
FROM msz_colours_presets
|
||||
WHERE {$field}
|
||||
SQL);
|
||||
$stmt->nextParameter($value);
|
||||
$stmt->execute();
|
||||
|
||||
$result = $stmt->getResult();
|
||||
if(!$result->next())
|
||||
throw new RuntimeException('could not find that preset');
|
||||
|
||||
return ColourPresetInfo::fromResult($result);
|
||||
}
|
||||
|
||||
public function deletePreset(ColourPresetInfo|string $presetInfo): void {
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
DELETE FROM msz_colours_presets
|
||||
WHERE preset_id = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($presetInfo instanceof ColourPresetInfo ? $presetInfo->id : $presetInfo);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
public function createPreset(
|
||||
string $name,
|
||||
Colour|int $colour,
|
||||
?string $title = null,
|
||||
?int $order = null,
|
||||
): ColourPresetInfo {
|
||||
if(empty($name) || ctype_digit($name[0]))
|
||||
throw new InvalidArgumentException('$name may not be empty and may not start with a number');
|
||||
if($colour instanceof Colour)
|
||||
$colour = Colour::toRawArgb($colour);
|
||||
if($colour < 0 || $colour > 0xFFFFFFFF)
|
||||
throw new InvalidArgumentException('$colour must be a positive integer between 0 and 0xFFFFFFFF');
|
||||
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
INSERT INTO msz_colours_presets (preset_name, preset_colour, preset_title, preset_order)
|
||||
SELECT ?, ?, ?, COALESCE(?, (
|
||||
SELECT FLOOR(MAX(preset_order) / 10) * 10 + 10
|
||||
FROM msz_colours_presets
|
||||
), 10)
|
||||
SQL);
|
||||
$stmt->nextParameter($name);
|
||||
$stmt->nextParameter($colour);
|
||||
$stmt->nextParameter($title);
|
||||
$stmt->nextParameter($order);
|
||||
$stmt->execute();
|
||||
|
||||
return $this->getPreset((string)$stmt->lastInsertId);
|
||||
}
|
||||
|
||||
public function updatePreset(
|
||||
ColourPresetInfo|string $presetInfo,
|
||||
?string $name = null,
|
||||
Colour|int|null $colour = null,
|
||||
string|null|false $title = false,
|
||||
?int $order = null,
|
||||
): void {
|
||||
$fields = [];
|
||||
$values = [];
|
||||
|
||||
if($name !== null) {
|
||||
if(empty($name) || ctype_digit($name[0]))
|
||||
throw new InvalidArgumentException('$name may not be empty and may not start with a number');
|
||||
|
||||
$fields[] = 'preset_name = ?';
|
||||
$values[] = $name;
|
||||
}
|
||||
|
||||
if($colour !== null) {
|
||||
if($colour instanceof Colour)
|
||||
$colour = Colour::toRawArgb($colour);
|
||||
if($colour < 0 || $colour > 0xFFFFFFFF)
|
||||
throw new InvalidArgumentException('$colour must be a positive integer between 0 and 0xFFFFFFFF');
|
||||
|
||||
$fields[] = 'preset_colour = ?';
|
||||
$values[] = $colour;
|
||||
}
|
||||
|
||||
if($title !== false) {
|
||||
$fields[] = 'preset_title';
|
||||
$values[] = $title;
|
||||
}
|
||||
|
||||
if($order !== null) {
|
||||
$fields[] = 'preset_order';
|
||||
$values[] = $order;
|
||||
}
|
||||
|
||||
if(empty($fields))
|
||||
return;
|
||||
|
||||
$fields = implode(', ', $fields);
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
UPDATE msz_colours_presets
|
||||
SET {$fields}
|
||||
WHERE preset_id = ?
|
||||
SQL);
|
||||
foreach($values as $value)
|
||||
$stmt->nextParameter($value);
|
||||
$stmt->nextParameter($presetInfo instanceof ColourPresetInfo ? $presetInfo->id : $presetInfo);
|
||||
$stmt->execute();
|
||||
}
|
||||
}
|
83
src/Colours/ColoursApiRoutes.php
Normal file
83
src/Colours/ColoursApiRoutes.php
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
namespace Misuzu\Colours;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\XArray;
|
||||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
|
||||
use Index\Http\Routing\AccessControl\AccessControl;
|
||||
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
|
||||
use Misuzu\FieldTransformer;
|
||||
|
||||
final class ColoursApiRoutes implements RouteHandler {
|
||||
use RouteHandlerCommon;
|
||||
|
||||
public function __construct(
|
||||
private ColoursContext $coloursCtx
|
||||
) {}
|
||||
|
||||
/** @return FieldTransformer<ColourPresetInfo> */
|
||||
private function createPresetTransformer(): FieldTransformer {
|
||||
return new FieldTransformer([
|
||||
'id' => [
|
||||
'transform' => fn($preset) => $preset->id,
|
||||
],
|
||||
'order' => [
|
||||
'transform' => fn($preset) => $preset->order,
|
||||
],
|
||||
'name' => [
|
||||
'default' => true,
|
||||
'transform' => fn($preset) => $preset->name,
|
||||
],
|
||||
'title' => [
|
||||
'default' => true,
|
||||
'transform' => fn($preset) => $preset->title ?? $preset->name,
|
||||
],
|
||||
'argb' => [
|
||||
'default' => true,
|
||||
'transform' => fn($preset) => $preset->colour,
|
||||
],
|
||||
'css' => [
|
||||
'default' => true,
|
||||
'transform' => fn($preset) => (string)$preset->toColour(),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return int|mixed[] */
|
||||
#[AccessControl]
|
||||
#[ExactRoute('GET', '/api/v1/colours/presets')]
|
||||
public function getPresets(HttpRequest $request): array|int {
|
||||
$transformer = $this->createPresetTransformer();
|
||||
if(!$transformer->filter($request))
|
||||
return 400;
|
||||
|
||||
return XArray::select(
|
||||
$this->coloursCtx->presets->getPresets(),
|
||||
fn($preset) => $transformer->convert($preset),
|
||||
);
|
||||
}
|
||||
|
||||
/** @return int|mixed[] */
|
||||
#[AccessControl]
|
||||
#[PatternRoute('GET', '/api/v1/colours/presets/([A-Za-z0-9\-_]+)')]
|
||||
public function getPreset(HttpRequest $request, string $name): array|int {
|
||||
if(empty($name))
|
||||
return 404;
|
||||
|
||||
$transformer = $this->createPresetTransformer();
|
||||
if(!$transformer->filter($request))
|
||||
return 400;
|
||||
|
||||
try {
|
||||
$preset = $this->coloursCtx->presets->getPreset(
|
||||
$name,
|
||||
ColourPresetGetField::IdOrName,
|
||||
);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
return $transformer->convert($preset);
|
||||
}
|
||||
}
|
12
src/Colours/ColoursContext.php
Normal file
12
src/Colours/ColoursContext.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Misuzu\Colours;
|
||||
|
||||
use Index\Db\DbConnection;
|
||||
|
||||
class ColoursContext {
|
||||
public private(set) ColourPresetsData $presets;
|
||||
|
||||
public function __construct(DbConnection $dbConn) {
|
||||
$this->presets = new ColourPresetsData($dbConn);
|
||||
}
|
||||
}
|
|
@ -1,48 +1,116 @@
|
|||
<?php
|
||||
namespace Misuzu\Emoticons;
|
||||
|
||||
use RuntimeException;
|
||||
use Index\XArray;
|
||||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
|
||||
use Index\Http\Routing\AccessControl\AccessControl;
|
||||
use Index\Http\Routing\Routes\ExactRoute;
|
||||
use Index\Http\Routing\Routes\{ExactRoute,PatternRoute};
|
||||
use Misuzu\FieldTransformer;
|
||||
|
||||
final class EmotesApiRoutes implements RouteHandler {
|
||||
use RouteHandlerCommon;
|
||||
|
||||
public function __construct(
|
||||
private EmotesData $emotes
|
||||
private EmotesContext $emotesCtx
|
||||
) {}
|
||||
|
||||
/** @return mixed[] */
|
||||
/** @return FieldTransformer<EmoteInfo> */
|
||||
private function createEmoteTransformer(): FieldTransformer {
|
||||
return new FieldTransformer([
|
||||
'id' => [
|
||||
'transform' => fn($emote) => $emote->id,
|
||||
],
|
||||
'order' => [
|
||||
'transform' => fn($emote) => $emote->order,
|
||||
],
|
||||
'url' => [
|
||||
'default' => true,
|
||||
'transform' => fn($emote) => $emote->url,
|
||||
],
|
||||
'strings' => [
|
||||
'default' => true,
|
||||
'transform' => fn($emote) => XArray::select(
|
||||
$this->emotesCtx->emotes->getEmoteStrings($emote),
|
||||
fn($string) => $string->string
|
||||
),
|
||||
],
|
||||
'min_rank' => [
|
||||
'default' => true,
|
||||
'transform' => fn($emote) => $emote->minRank,
|
||||
'include' => fn($value) => $value !== 0,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return int|mixed[] */
|
||||
#[AccessControl]
|
||||
#[ExactRoute('GET', '/api/v1/emotes')]
|
||||
public function getEmotes(HttpResponseBuilder $response, HttpRequest $request): array {
|
||||
$includeId = !empty($request->getParam('include_id'));
|
||||
$includeOrder = !empty($request->getParam('include_order'));
|
||||
public function getEmotes(HttpRequest $request): array|int {
|
||||
$transformer = $this->createEmoteTransformer();
|
||||
if(!$transformer->filter($request))
|
||||
return 400;
|
||||
|
||||
return XArray::select(
|
||||
$this->emotes->getEmotes(orderBy: 'order'),
|
||||
function($emote) use ($includeId, $includeOrder) {
|
||||
$info = [
|
||||
'url' => $emote->url,
|
||||
'strings' => XArray::select(
|
||||
$this->emotes->getEmoteStrings($emote),
|
||||
fn($string) => $string->string
|
||||
),
|
||||
];
|
||||
$this->emotesCtx->emotes->getEmotes(orderBy: 'order'),
|
||||
fn($emote) => $transformer->convert($emote),
|
||||
);
|
||||
}
|
||||
|
||||
if($includeId)
|
||||
$info['id'] = $emote->id;
|
||||
if($includeOrder)
|
||||
$info['order'] = $emote->order;
|
||||
/** @return int|mixed[] */
|
||||
#[AccessControl]
|
||||
#[PatternRoute('GET', '/api/v1/emotes/([0-9]+)')]
|
||||
public function getEmote(HttpRequest $request, string $id): array|int {
|
||||
if(empty($id))
|
||||
return 404;
|
||||
|
||||
$rank = $emote->minRank;
|
||||
if($rank !== 0)
|
||||
$info['min_rank'] = $rank;
|
||||
$transformer = $this->createEmoteTransformer();
|
||||
if(!$transformer->filter($request))
|
||||
return 400;
|
||||
|
||||
return $info;
|
||||
}
|
||||
try {
|
||||
$emote = $this->emotesCtx->emotes->getEmote($id);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
return $transformer->convert($emote);
|
||||
}
|
||||
|
||||
/** @return FieldTransformer<EmoteStringInfo> */
|
||||
private function createEmoteStringTransformer(): FieldTransformer {
|
||||
return new FieldTransformer([
|
||||
'order' => [
|
||||
'transform' => fn($string) => $string->order,
|
||||
],
|
||||
'string' => [
|
||||
'default' => true,
|
||||
'transform' => fn($string) => $string->string,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/** @return int|mixed[] */
|
||||
#[AccessControl]
|
||||
#[PatternRoute('GET', '/api/v1/emotes/([0-9]+)/strings')]
|
||||
public function getEmoteStrings(HttpRequest $request, string $id): array|int {
|
||||
if(empty($id))
|
||||
return 404;
|
||||
|
||||
$transformer = $this->createEmoteStringTransformer();
|
||||
if(!$transformer->filter($request))
|
||||
return 400;
|
||||
|
||||
try {
|
||||
$emote = $this->emotesCtx->emotes->getEmote($id);
|
||||
} catch(RuntimeException $ex) {
|
||||
return 404;
|
||||
}
|
||||
|
||||
return XArray::select(
|
||||
$this->emotesCtx->emotes->getEmoteStrings($emote),
|
||||
fn($emote) => $transformer->convert($emote),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
12
src/Emoticons/EmotesContext.php
Normal file
12
src/Emoticons/EmotesContext.php
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
namespace Misuzu\Emoticons;
|
||||
|
||||
use Index\Db\DbConnection;
|
||||
|
||||
class EmotesContext {
|
||||
public private(set) EmotesData $emotes;
|
||||
|
||||
public function __construct(DbConnection $dbConn) {
|
||||
$this->emotes = new EmotesData($dbConn);
|
||||
}
|
||||
}
|
|
@ -19,7 +19,11 @@ class EmotesData {
|
|||
}
|
||||
|
||||
public function getEmote(string $emoteId): EmoteInfo {
|
||||
$stmt = $this->cache->get('SELECT emote_id, emote_order, emote_rank, emote_url FROM msz_emoticons WHERE emote_id = ?');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
SELECT emote_id, emote_order, emote_rank, emote_url
|
||||
FROM msz_emoticons
|
||||
WHERE emote_id = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($emoteId);
|
||||
$stmt->execute();
|
||||
|
||||
|
@ -49,7 +53,10 @@ class EmotesData {
|
|||
$hasOrderBy = $orderBy !== null;
|
||||
$hasReverse = $reverse !== null;
|
||||
|
||||
$query = 'SELECT emote_id, emote_order, emote_rank, emote_url FROM msz_emoticons';
|
||||
$query = <<<SQL
|
||||
SELECT emote_id, emote_order, emote_rank, emote_url
|
||||
FROM msz_emoticons
|
||||
SQL;
|
||||
if($hasMinRank)
|
||||
$query .= ' WHERE emote_rank <= ?';
|
||||
if($hasOrderBy) {
|
||||
|
@ -83,7 +90,11 @@ class EmotesData {
|
|||
if($check !== '')
|
||||
return $check;
|
||||
|
||||
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_emoticons WHERE emote_url = ?');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
SELECT COUNT(*)
|
||||
FROM msz_emoticons
|
||||
WHERE emote_url = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($url);
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
@ -99,7 +110,13 @@ class EmotesData {
|
|||
if($check !== '')
|
||||
throw new InvalidArgumentException('$url is not correctly formatted: ' . $check);
|
||||
|
||||
$stmt = $this->cache->get('INSERT INTO msz_emoticons (emote_url, emote_rank, emote_order) SELECT ?, ?, COALESCE(?, (SELECT FLOOR(MAX(emote_order) / 10) * 10 + 10 FROM msz_emoticons), 10)');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
INSERT INTO msz_emoticons (emote_url, emote_rank, emote_order)
|
||||
SELECT ?, ?, COALESCE(?, (
|
||||
SELECT FLOOR(MAX(emote_order) / 10) * 10 + 10
|
||||
FROM msz_emoticons
|
||||
), 10)
|
||||
SQL);
|
||||
$stmt->nextParameter($url);
|
||||
$stmt->nextParameter($minRank);
|
||||
$stmt->nextParameter($order);
|
||||
|
@ -112,7 +129,10 @@ class EmotesData {
|
|||
if($infoOrId instanceof EmoteInfo)
|
||||
$infoOrId = $infoOrId->id;
|
||||
|
||||
$stmt = $this->cache->get('DELETE FROM msz_emoticons WHERE emote_id = ?');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
DELETE FROM msz_emoticons
|
||||
WHERE emote_id = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($infoOrId);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
@ -132,7 +152,13 @@ class EmotesData {
|
|||
if($infoOrId instanceof EmoteInfo)
|
||||
$infoOrId = $infoOrId->id;
|
||||
|
||||
$stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = COALESCE(?, emote_order), emote_rank = COALESCE(?, emote_rank), emote_url = COALESCE(?, emote_url) WHERE emote_id = ?');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
UPDATE msz_emoticons
|
||||
SET emote_order = COALESCE(?, emote_order),
|
||||
emote_rank = COALESCE(?, emote_rank),
|
||||
emote_url = COALESCE(?, emote_url)
|
||||
WHERE emote_id = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($order);
|
||||
$stmt->nextParameter($minRank);
|
||||
$stmt->nextParameter($url);
|
||||
|
@ -145,7 +171,11 @@ class EmotesData {
|
|||
if($infoOrId instanceof EmoteInfo)
|
||||
$infoOrId = $infoOrId->id;
|
||||
|
||||
$stmt = $this->cache->get('UPDATE msz_emoticons SET emote_order = emote_order + ? WHERE emote_id = ?');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
UPDATE msz_emoticons
|
||||
SET emote_order = emote_order + ?
|
||||
WHERE emote_id = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($offset);
|
||||
$stmt->nextParameter($infoOrId);
|
||||
$stmt->execute();
|
||||
|
@ -156,7 +186,12 @@ class EmotesData {
|
|||
if($infoOrId instanceof EmoteInfo)
|
||||
$infoOrId = $infoOrId->id;
|
||||
|
||||
$stmt = $this->cache->get('SELECT emote_id, emote_string_order, emote_string FROM msz_emoticons_strings WHERE emote_id = ? ORDER BY emote_string_order');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
SELECT emote_id, emote_string_order, emote_string
|
||||
FROM msz_emoticons_strings
|
||||
WHERE emote_id = ?
|
||||
ORDER BY emote_string_order
|
||||
SQL);
|
||||
$stmt->nextParameter($infoOrId);
|
||||
$stmt->execute();
|
||||
|
||||
|
@ -181,7 +216,11 @@ class EmotesData {
|
|||
if($check !== '')
|
||||
return $check;
|
||||
|
||||
$stmt = $this->cache->get('SELECT COUNT(*) FROM msz_emoticons_strings WHERE emote_string = ?');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
SELECT COUNT(*)
|
||||
FROM msz_emoticons_strings
|
||||
WHERE emote_string = ?
|
||||
SQL);
|
||||
$stmt->nextParameter($string);
|
||||
$stmt->execute();
|
||||
$result = $stmt->getResult();
|
||||
|
@ -200,7 +239,14 @@ class EmotesData {
|
|||
if($infoOrId instanceof EmoteInfo)
|
||||
$infoOrId = $infoOrId->id;
|
||||
|
||||
$stmt = $this->cache->get('INSERT INTO msz_emoticons_strings (emote_id, emote_string, emote_string_order) SELECT ? AS target_emote_id, ?, COALESCE(?, (SELECT MAX(emote_string_order) + 1 FROM msz_emoticons_strings WHERE emote_id = target_emote_id), 1)');
|
||||
$stmt = $this->cache->get(<<<SQL
|
||||
INSERT INTO msz_emoticons_strings (emote_id, emote_string, emote_string_order)
|
||||
SELECT ? AS target_emote_id, ?, COALESCE(?, (
|
||||
SELECT MAX(emote_string_order) + 1
|
||||
FROM msz_emoticons_strings
|
||||
WHERE emote_id = target_emote_id
|
||||
), 1)
|
||||
SQL);
|
||||
$stmt->nextParameter($infoOrId);
|
||||
$stmt->nextParameter($string);
|
||||
$stmt->nextParameter($order);
|
||||
|
|
139
src/FieldTransformer.php
Normal file
139
src/FieldTransformer.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
namespace Misuzu;
|
||||
|
||||
use RuntimeException;
|
||||
use InvalidArgumentException;
|
||||
use Index\Http\HttpRequest;
|
||||
|
||||
/**
|
||||
* @template T
|
||||
*/
|
||||
class FieldTransformer {
|
||||
/** @var string[] */
|
||||
public private(set) array $available = [];
|
||||
|
||||
/** @var string[] */
|
||||
public private(set) array $defaults = [];
|
||||
|
||||
/** @var array<string, callable(T): mixed> */
|
||||
public private(set) array $transformers = [];
|
||||
|
||||
/** @var array<string, callable(mixed): bool> */
|
||||
public private(set) array $include = [];
|
||||
|
||||
/** @var string[] */
|
||||
public private(set) array $fields = [];
|
||||
|
||||
/**
|
||||
* @param array<string, array{
|
||||
* default?: bool,
|
||||
* transform: (callable(T): mixed),
|
||||
* include?: (callable(mixed): bool),
|
||||
* }> $fields
|
||||
*/
|
||||
public function __construct(array $fields) {
|
||||
foreach($fields as $name => $field) {
|
||||
$this->available[] = $name;
|
||||
$this->transformers[$name] = $field['transform'];
|
||||
|
||||
if(isset($field['default']) && is_bool($field['default']) && $field['default'])
|
||||
$this->defaults[] = $name;
|
||||
if(isset($field['include']) && is_callable($field['include']))
|
||||
$this->include[$name] = $field['include'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param HttpRequest|string|string[] $fields
|
||||
*/
|
||||
public function filter(HttpRequest|string|array $fields, string $param = 'fields'): bool {
|
||||
if($fields instanceof HttpRequest) {
|
||||
if(!$fields->hasParam($param)) {
|
||||
$this->fields = $this->defaults;
|
||||
return true;
|
||||
}
|
||||
|
||||
$fields = trim($fields->getParam($param));
|
||||
}
|
||||
|
||||
if(is_string($fields)) {
|
||||
if($fields === '*') {
|
||||
$this->fields = $this->available;
|
||||
return true;
|
||||
}
|
||||
|
||||
$fields = explode(',', $fields);
|
||||
}
|
||||
|
||||
$unfiltered = array_map(fn($value) => trim($value), $fields);
|
||||
$fields = [];
|
||||
|
||||
foreach($unfiltered as $field) {
|
||||
// this is a little bit crusty
|
||||
if(str_ends_with($field, '.*')) {
|
||||
$prefix = substr($field, 0, -1);
|
||||
$any = false;
|
||||
foreach($this->available as $name)
|
||||
if(str_starts_with($name, $prefix)) {
|
||||
if(in_array($name, $fields))
|
||||
return false;
|
||||
|
||||
$any = true;
|
||||
$fields[] = $name;
|
||||
}
|
||||
|
||||
if(!$any) return false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if(!in_array($field, $this->available))
|
||||
return false;
|
||||
if(in_array($field, $fields))
|
||||
return false;
|
||||
|
||||
$fields[] = $field;
|
||||
}
|
||||
|
||||
if(empty($fields)) {
|
||||
$this->fields = $this->defaults;
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->fields = $fields;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param T $value
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function convert(mixed $value): array {
|
||||
$output = [];
|
||||
|
||||
foreach($this->fields as $field) {
|
||||
$result = $this->transformers[$field]($value);
|
||||
if(array_key_exists($field, $this->include) ? !$this->include[$field]($result) : $result === null)
|
||||
continue;
|
||||
|
||||
$target = &$output;
|
||||
$steps = explode('.', $field);
|
||||
while(count($steps) > 1) {
|
||||
$step = array_shift($steps);
|
||||
|
||||
// PHPStan does not know how to deal with recursion and references
|
||||
if(!array_key_exists($step, $target)) // @phpstan-ignore function.impossibleType
|
||||
$target[$step] = [];
|
||||
if(!is_array($target[$step])) // @phpstan-ignore function.alreadyNarrowedType
|
||||
throw new RuntimeException(sprintf('"%s" is not an array', $field));
|
||||
if(!empty($target[$step]) && array_is_list($target[$step])) // @phpstan-ignore empty.offset
|
||||
throw new RuntimeException(sprintf('"%s" conflicts with a list', $field));
|
||||
|
||||
$target = &$target[$step];
|
||||
}
|
||||
|
||||
$target[$steps[0]] = $result;
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
}
|
|
@ -27,14 +27,15 @@ class MisuzuContext {
|
|||
public private(set) AuditLog\AuditLogData $auditLog;
|
||||
public private(set) Counters\CountersData $counters;
|
||||
|
||||
public private(set) Emoticons\EmotesData $emotes;
|
||||
public private(set) Changelog\ChangelogData $changelog;
|
||||
public private(set) News\NewsData $news;
|
||||
|
||||
public private(set) DatabaseContext $dbCtx;
|
||||
public private(set) Apps\AppsContext $appsCtx;
|
||||
public private(set) Auth\AuthContext $authCtx;
|
||||
public private(set) Colours\ColoursContext $coloursCtx;
|
||||
public private(set) Comments\CommentsContext $commentsCtx;
|
||||
public private(set) Emoticons\EmotesContext $emotesCtx;
|
||||
public private(set) Forum\ForumContext $forumCtx;
|
||||
public private(set) Messages\MessagesContext $messagesCtx;
|
||||
public private(set) OAuth2\OAuth2Context $oauth2Ctx;
|
||||
|
@ -93,7 +94,9 @@ class MisuzuContext {
|
|||
Auth\AuthContext::class,
|
||||
config: $this->config->scopeTo('auth'),
|
||||
));
|
||||
$this->deps->register($this->coloursCtx = $this->deps->constructLazy(Colours\ColoursContext::class));
|
||||
$this->deps->register($this->commentsCtx = $this->deps->constructLazy(Comments\CommentsContext::class));
|
||||
$this->deps->register($this->emotesCtx = $this->deps->constructLazy(Emoticons\EmotesContext::class));
|
||||
$this->deps->register($this->forumCtx = $this->deps->constructLazy(
|
||||
Forum\ForumContext::class,
|
||||
config: $this->config->scopeTo('forum'),
|
||||
|
@ -116,7 +119,6 @@ class MisuzuContext {
|
|||
$this->deps->register($this->auditLog = $this->deps->constructLazy(AuditLog\AuditLogData::class));
|
||||
$this->deps->register($this->changelog = $this->deps->constructLazy(Changelog\ChangelogData::class));
|
||||
$this->deps->register($this->counters = $this->deps->constructLazy(Counters\CountersData::class));
|
||||
$this->deps->register($this->emotes = $this->deps->constructLazy(Emoticons\EmotesData::class));
|
||||
$this->deps->register($this->news = $this->deps->constructLazy(News\NewsData::class));
|
||||
|
||||
$this->deps->register($this->storageCtx = $this->deps->construct(
|
||||
|
@ -215,6 +217,7 @@ class MisuzuContext {
|
|||
Auth\AuthApiRoutes::class,
|
||||
impersonateConfig: $this->config->scopeTo('impersonate')
|
||||
));
|
||||
$routingCtx->register($this->deps->constructLazy(Colours\ColoursApiRoutes::class));
|
||||
$routingCtx->register($this->deps->constructLazy(Emoticons\EmotesApiRoutes::class));
|
||||
$routingCtx->register($this->deps->constructLazy(Users\UsersApiRoutes::class));
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ use Index\Http\Routing\Routes\ExactRoute;
|
|||
use Index\Urls\UrlRegistry;
|
||||
use Misuzu\Auth\{AuthContext,AuthInfo,Sessions};
|
||||
use Misuzu\Counters\CountersData;
|
||||
use Misuzu\Emoticons\EmotesData;
|
||||
use Misuzu\Emoticons\EmotesContext;
|
||||
use Misuzu\OAuth2\{OAuth2AccessInfoGetField,OAuth2Context};
|
||||
use Misuzu\Perms\PermissionsData;
|
||||
use Misuzu\Users\{BansData,UsersContext,UserInfo};
|
||||
|
@ -30,7 +30,7 @@ final class SharpChatRoutes implements RouteHandler {
|
|||
private UsersContext $usersCtx,
|
||||
private AuthContext $authCtx,
|
||||
private OAuth2Context $oauth2Ctx,
|
||||
private EmotesData $emotes,
|
||||
private EmotesContext $emotesCtx,
|
||||
private PermissionsData $perms,
|
||||
private AuthInfo $authInfo,
|
||||
private CountersData $counters
|
||||
|
@ -44,13 +44,13 @@ final class SharpChatRoutes implements RouteHandler {
|
|||
public function getEmotes(): array {
|
||||
$this->counters->increment('dev:legacy_emotes_loads');
|
||||
|
||||
$emotes = $this->emotes->getEmotes(orderBy: 'order');
|
||||
$emotes = $this->emotesCtx->emotes->getEmotes(orderBy: 'order');
|
||||
$out = [];
|
||||
|
||||
foreach($emotes as $emoteInfo) {
|
||||
$strings = [];
|
||||
|
||||
foreach($this->emotes->getEmoteStrings($emoteInfo) as $stringInfo)
|
||||
foreach($this->emotesCtx->emotes->getEmoteStrings($emoteInfo) as $stringInfo)
|
||||
$strings[] = sprintf(':%s:', $stringInfo->string);
|
||||
|
||||
$out[] = [
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
namespace Misuzu\Users;
|
||||
|
||||
use RuntimeException;
|
||||
use Misuzu\SiteInfo;
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Users\Assets\UserAvatarAsset;
|
||||
use Index\XArray;
|
||||
use Index\Colour\{Colour,ColourRgb};
|
||||
use Index\Http\{HttpRequest,HttpResponseBuilder};
|
||||
|
@ -12,6 +9,10 @@ use Index\Http\Routing\{RouteHandler,RouteHandlerCommon};
|
|||
use Index\Http\Routing\AccessControl\AccessControl;
|
||||
use Index\Http\Routing\Routes\ExactRoute;
|
||||
use Index\Urls\UrlRegistry;
|
||||
use Misuzu\{FieldTransformer,SiteInfo};
|
||||
use Misuzu\Auth\AuthInfo;
|
||||
use Misuzu\Users\UserInfo;
|
||||
use Misuzu\Users\Assets\UserAvatarAsset;
|
||||
|
||||
final class UsersApiRoutes implements RouteHandler {
|
||||
use RouteHandlerCommon;
|
||||
|
@ -23,6 +24,105 @@ final class UsersApiRoutes implements RouteHandler {
|
|||
private AuthInfo $authInfo,
|
||||
) {}
|
||||
|
||||
/** @return FieldTransformer<UserInfo> */
|
||||
public function createUserTransformer(): FieldTransformer {
|
||||
$fields = [
|
||||
'id' => [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->id,
|
||||
],
|
||||
];
|
||||
|
||||
$openid = $this->authInfo->hasScope('openid');
|
||||
|
||||
if(!$openid || $this->authInfo->hasScope('profile')) {
|
||||
$fields['name'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->name,
|
||||
];
|
||||
$fields['colour_raw'] = [
|
||||
'default' => true,
|
||||
'transform' => function($user) {
|
||||
$colour = $this->usersCtx->getUserColour($user);
|
||||
return $colour->inherits ? null : Colour::toRawRgb($colour);
|
||||
},
|
||||
'include' => fn() => true,
|
||||
];
|
||||
$fields['colour_css'] = [
|
||||
'default' => true,
|
||||
'transform' => function($user) {
|
||||
$colour = $this->usersCtx->getUserColour($user);
|
||||
return $colour->inherits ? (string)$colour : (string)ColourRgb::convert($colour);
|
||||
},
|
||||
];
|
||||
$fields['country_code'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->countryCode,
|
||||
];
|
||||
$fields['rank'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => (
|
||||
$this->usersCtx->hasActiveBan($user)
|
||||
? 0
|
||||
: $this->usersCtx->getUserRank($user)
|
||||
),
|
||||
];
|
||||
$fields['roles'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => (
|
||||
$this->usersCtx->hasActiveBan($user)
|
||||
? ['x-banned']
|
||||
: XArray::select(
|
||||
$this->usersCtx->roles->getRoles(
|
||||
userInfo: $user,
|
||||
hasString: true,
|
||||
orderByRank: true
|
||||
),
|
||||
fn($roleInfo) => $roleInfo->string,
|
||||
)
|
||||
),
|
||||
];
|
||||
$fields['is_super'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->super,
|
||||
'include' => fn($value) => $value === true,
|
||||
];
|
||||
$fields['title'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => empty($user->title) ? null : $user->title,
|
||||
];
|
||||
$fields['created_at'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->createdAt->toIso8601ZuluString(),
|
||||
];
|
||||
$fields['last_active_at'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->lastActiveAt?->toIso8601ZuluString(),
|
||||
];
|
||||
$fields['profile_url'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => ($this->siteInfo->url . $this->urls->format('user-profile', ['user' => $user->id])),
|
||||
];
|
||||
$fields['avatar_url'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => ($this->siteInfo->url . $this->urls->format('user-avatar', ['user' => $user->id])),
|
||||
];
|
||||
$fields['is_deleted'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->deleted,
|
||||
'include' => fn($value) => $value === true,
|
||||
];
|
||||
}
|
||||
|
||||
if($this->authInfo->hasScope($openid ? 'email' : 'identify:email'))
|
||||
$fields['email'] = [
|
||||
'default' => true,
|
||||
'transform' => fn($user) => $user->emailAddress,
|
||||
];
|
||||
|
||||
return new FieldTransformer($fields);
|
||||
}
|
||||
|
||||
/** @return int|mixed[] */
|
||||
#[AccessControl]
|
||||
#[ExactRoute('GET', '/api/v1/me')]
|
||||
|
@ -35,10 +135,9 @@ final class UsersApiRoutes implements RouteHandler {
|
|||
&& !$this->authInfo->hasScope('beans'))
|
||||
return 403;
|
||||
|
||||
$includeProfile = !$openid || $this->authInfo->hasScope('profile');
|
||||
$includeEMail = $openid
|
||||
? $this->authInfo->hasScope('email')
|
||||
: $this->authInfo->hasScope('identify:email');
|
||||
$transformer = $this->createUserTransformer();
|
||||
if(!$transformer->filter($request))
|
||||
return 400;
|
||||
|
||||
try {
|
||||
$userInfo = $this->usersCtx->getUserInfo($this->authInfo->userId, UsersData::GET_USER_ID);
|
||||
|
@ -46,60 +145,6 @@ final class UsersApiRoutes implements RouteHandler {
|
|||
return 404;
|
||||
}
|
||||
|
||||
// TODO: there should be some kinda privacy controls for users
|
||||
|
||||
$output = ['id' => $userInfo->id];
|
||||
|
||||
if($includeProfile) {
|
||||
$output['name'] = $userInfo->name;
|
||||
|
||||
$colour = $this->usersCtx->getUserColour($userInfo);
|
||||
if($colour->inherits) {
|
||||
$colourRaw = null;
|
||||
$colourCSS = (string)$colour;
|
||||
} else {
|
||||
$colourRaw = Colour::toRawRgb($colour);
|
||||
$colourCSS = (string)ColourRgb::convert($colour);
|
||||
}
|
||||
|
||||
$output['colour_raw'] = $colourRaw;
|
||||
$output['colour_css'] = $colourCSS;
|
||||
$output['country_code'] = $userInfo->countryCode;
|
||||
|
||||
if($this->usersCtx->hasActiveBan($userInfo)) {
|
||||
$output['rank'] = 0;
|
||||
$output['roles'] = ['x-banned'];
|
||||
} else {
|
||||
$roles = XArray::select(
|
||||
$this->usersCtx->roles->getRoles(userInfo: $userInfo, hasString: true, orderByRank: true),
|
||||
fn($roleInfo) => $roleInfo->string,
|
||||
);
|
||||
|
||||
$output['rank'] = $this->usersCtx->getUserRank($userInfo);
|
||||
if(!empty($roles))
|
||||
$output['roles'] = $roles;
|
||||
if($userInfo->super)
|
||||
$output['is_super'] = true;
|
||||
}
|
||||
|
||||
if(!empty($userInfo->title))
|
||||
$output['title'] = $userInfo->title;
|
||||
|
||||
$output['created_at'] = $userInfo->createdAt->toIso8601ZuluString();
|
||||
if($userInfo->lastActiveTime !== null)
|
||||
$output['last_active_at'] = $userInfo->lastActiveAt->toIso8601ZuluString();
|
||||
|
||||
$baseUrl = $this->siteInfo->url;
|
||||
$output['profile_url'] = $baseUrl . $this->urls->format('user-profile', ['user' => $userInfo->id]);
|
||||
$output['avatar_url'] = $baseUrl . $this->urls->format('user-avatar', ['user' => $userInfo->id]);
|
||||
|
||||
if($userInfo->deleted)
|
||||
$output['is_deleted'] = true;
|
||||
}
|
||||
|
||||
if($includeEMail)
|
||||
$output['email'] = $userInfo->emailAddress;
|
||||
|
||||
return $output;
|
||||
return $transformer->convert($userInfo);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue