Moved profile background settings out of the users table.

This commit is contained in:
flash 2025-02-08 21:20:44 +00:00
parent 9f5076cc77
commit 31d89a08bf
18 changed files with 285 additions and 234 deletions

View file

@ -0,0 +1,42 @@
<?php
use Index\Db\DbConnection;
use Index\Db\Migration\DbMigration;
final class CreateProfileBackgroundSettingsTable_20250208_175705 implements DbMigration {
public function migrate(DbConnection $conn): void {
$conn->execute(<<<SQL
CREATE TABLE msz_profile_backgrounds (
user_id INT UNSIGNED NOT NULL AUTO_INCREMENT,
bg_attach ENUM('cover','stretch','tile','contain') NOT NULL,
bg_blend TINYINT UNSIGNED NOT NULL,
bg_slide TINYINT UNSIGNED NOT NULL,
PRIMARY KEY (user_id),
CONSTRAINT profile_backgrounds_users_foreign
FOREIGN KEY (user_id)
REFERENCES msz_users (user_id)
ON UPDATE CASCADE
ON DELETE CASCADE
) COLLATE='utf8mb4_bin';
SQL);
$conn->execute(<<<SQL
INSERT INTO msz_profile_backgrounds
SELECT user_id,
CASE (user_background_settings & 0x0F)
WHEN 1 THEN 'cover'
WHEN 2 THEN 'stretch'
WHEN 3 THEN 'tile'
WHEN 4 THEN 'contain'
END,
IF(user_background_settings & 0x10, 1, 0),
IF(user_background_settings & 0x20, 1, 0)
FROM msz_users
WHERE (user_background_settings & 0x0F) BETWEEN 1 AND 4;
SQL);
$conn->execute(<<<SQL
ALTER TABLE msz_users
DROP COLUMN user_background_settings;
SQL);
}
}

View file

@ -6,6 +6,7 @@ use InvalidArgumentException;
use RuntimeException;
use Index\ByteFormat;
use Misuzu\Parsers\TextFormat;
use Misuzu\Profile\ProfileBackgroundAttach;
use Misuzu\Users\{User,UsersContext};
use Misuzu\Users\Assets\UserAvatarAsset;
use Misuzu\Users\Assets\UserBackgroundAsset;
@ -74,8 +75,9 @@ $canManageWarnings = $viewerPermsUser->check(Perm::U_WARNINGS_MANAGE);
$canEdit = !$viewingAsGuest && ((!$isBanned && $viewingOwnProfile) || $viewerInfo->super || (
$viewerPermsUser->check(Perm::U_USERS_MANAGE) && ($viewingOwnProfile || $viewerRank > $userRank)
));
$avatarInfo = new UserAvatarAsset($userInfo);
$backgroundInfo = new UserBackgroundAsset($userInfo);
$avatarAsset = new UserAvatarAsset($userInfo);
$backgroundInfo = $msz->profileCtx->backgrounds->getProfileBackground($userInfo);
$backgroundAsset = new UserBackgroundAsset($userInfo, $backgroundInfo);
if($isEditing) {
if(!$canEdit)
@ -92,7 +94,6 @@ if($isEditing) {
Template::set([
'perms' => $perms,
'background_attachments' => UserBackgroundAsset::getAttachmentStringOptions(),
'birthdate_info' => $msz->usersCtx->birthdates->getUserBirthdate($userInfo),
]);
@ -106,7 +107,7 @@ if($isEditing) {
if(!$perms->edit_profile) {
$notices[] = 'You\'re not allowed to edit your profile';
} else {
$profileFieldInfos = iterator_to_array($msz->profileFields->getFields());
$profileFieldInfos = iterator_to_array($msz->profileCtx->fields->getFields());
$profileFieldsSetInfos = [];
$profileFieldsSetValues = [];
$profileFieldsRemove = [];
@ -130,9 +131,9 @@ if($isEditing) {
}
if(!empty($profileFieldsRemove))
$msz->profileFields->removeFieldValues($userInfo, $profileFieldsRemove);
$msz->profileCtx->fields->removeFieldValues($userInfo, $profileFieldsRemove);
if(!empty($profileFieldsSetInfos))
$msz->profileFields->setFieldValues($userInfo, $profileFieldsSetInfos, $profileFieldsSetValues);
$msz->profileCtx->fields->setFieldValues($userInfo, $profileFieldsSetInfos, $profileFieldsSetValues);
}
}
@ -187,7 +188,7 @@ if($isEditing) {
if(!empty($_FILES['avatar'])) {
if(!empty($_POST['avatar']['delete'])) {
$avatarInfo->delete();
$avatarAsset->delete();
} else {
if(!$perms->edit_avatar) {
$notices[] = 'You aren\'t allow to change your avatar.';
@ -204,7 +205,7 @@ if($isEditing) {
break;
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
$notices[] = sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarInfo->getMaxBytes()));
$notices[] = sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarAsset->getMaxBytes()));
break;
default:
$notices[] = 'Unable to save your avatar, contact an administator!';
@ -212,14 +213,14 @@ if($isEditing) {
}
} else {
try {
$avatarInfo->setFromPath($_FILES['avatar']['tmp_name']['file']);
$avatarAsset->setFromPath($_FILES['avatar']['tmp_name']['file']);
} catch(InvalidArgumentException $ex) {
$exMessage = $ex->getMessage();
$notices[] = match($exMessage) {
'$path is not a valid image.' => 'The file you uploaded was not an image!',
'$path is not an allowed image file.' => 'This type of image is not supported, keep to PNG, JPG or GIF!',
'Dimensions of $path are too large.' => sprintf('Your avatar can\'t be larger than %dx%d!', $avatarInfo->getMaxWidth(), $avatarInfo->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarInfo->getMaxBytes())),
'Dimensions of $path are too large.' => sprintf('Your avatar can\'t be larger than %dx%d!', $avatarAsset->getMaxWidth(), $avatarAsset->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your avatar is not allowed to be larger in file size than %s!', ByteFormat::format($avatarAsset->getMaxBytes())),
default => $exMessage,
};
} catch(RuntimeException $ex) {
@ -230,16 +231,20 @@ if($isEditing) {
}
}
if(!empty($_FILES['background'])) {
if((int)($_POST['background']['attach'] ?? -1) === 0) {
$backgroundInfo->delete();
if(filter_has_var(INPUT_POST, 'bg_attach')) {
$bgFormat = ProfileBackgroundAttach::tryFrom((string)filter_input(INPUT_POST, 'bg_attach'));
if($bgFormat === null) {
$backgroundAsset->delete();
$msz->profileCtx->backgrounds->deleteProfileBackground($userInfo);
$backgroundAsset = null;
} else {
if(!$perms->edit_background) {
$notices[] = 'You aren\'t allow to change your background.';
} elseif(!empty($_FILES['background']) && is_array($_FILES['background'])) {
if(!empty($_FILES['background']['name']['file'])) {
if($_FILES['background']['error']['file'] !== UPLOAD_ERR_OK) {
switch($_FILES['background']['error']['file']) {
} elseif(!empty($_FILES['bg_file']) && is_array($_FILES['bg_file'])) {
if(!empty($_FILES['bg_file']['name'])) {
if($_FILES['bg_file']['error'] !== UPLOAD_ERR_OK) {
switch($_FILES['bg_file']['error']) {
case UPLOAD_ERR_NO_FILE:
$notices[] = 'Select a file before hitting upload!';
break;
@ -256,14 +261,14 @@ if($isEditing) {
}
} else {
try {
$backgroundInfo->setFromPath($_FILES['background']['tmp_name']['file']);
$backgroundAsset->setFromPath($_FILES['bg_file']['tmp_name']);
} catch(InvalidArgumentException $ex) {
$exMessage = $ex->getMessage();
$notices[] = match($exMessage) {
'$path is not a valid image.' => 'The file you uploaded was not an image!',
'$path is not an allowed image file.' => 'This type of image is not supported, keep to PNG, JPG or GIF!',
'Dimensions of $path are too large.' => sprintf('Your background can\'t be larger than %dx%d!', $backgroundInfo->getMaxWidth(), $backgroundInfo->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your background is not allowed to be larger in file size than %s!', ByteFormat::format($backgroundInfo->getMaxBytes())),
'Dimensions of $path are too large.' => sprintf('Your background can\'t be larger than %dx%d!', $backgroundAsset->getMaxWidth(), $backgroundAsset->getMaxHeight()),
'File size of $path is too large.' => sprintf('Your background is not allowed to be larger in file size than %s!', ByteFormat::format($backgroundAsset->getMaxBytes())),
default => $exMessage,
};
} catch(RuntimeException $ex) {
@ -272,13 +277,16 @@ if($isEditing) {
}
}
$backgroundInfo->setAttachment((int)($_POST['background']['attach'] ?? 0))
->setBlend(!empty($_POST['background']['attr']['blend']))
->setSlide(!empty($_POST['background']['attr']['slide']));
$backgroundInfo = $msz->profileCtx->backgrounds->updateProfileBackground(
$userInfo,
$bgFormat,
filter_has_var(INPUT_POST, 'bg_blend'),
filter_has_var(INPUT_POST, 'bg_slide')
);
}
}
$msz->usersCtx->users->updateUser($userInfo, backgroundSettings: $backgroundInfo->getSettings());
$backgroundAsset = new UserBackgroundAsset($userInfo, $backgroundInfo);
}
}
@ -319,9 +327,9 @@ if(!$viewingAsGuest) {
);
$activeTopicInfo = $activeTopicStats->success ? $msz->forumCtx->topics->getTopic(topicId: $activeTopicStats->topicId) : null;
$profileFieldValues = iterator_to_array($msz->profileFields->getFieldValues($userInfo));
$profileFieldInfos = $profileFieldInfos ?? iterator_to_array($msz->profileFields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues));
$profileFieldFormats = iterator_to_array($msz->profileFields->getFieldFormats(fieldValueInfos: $profileFieldValues));
$profileFieldValues = iterator_to_array($msz->profileCtx->fields->getFieldValues($userInfo));
$profileFieldInfos = $profileFieldInfos ?? iterator_to_array($msz->profileCtx->fields->getFields(fieldValueInfos: $isEditing ? null : $profileFieldValues));
$profileFieldFormats = iterator_to_array($msz->profileCtx->fields->getFieldFormats(fieldValueInfos: $profileFieldValues));
$profileFieldRawValues = [];
$profileFieldLinkValues = [];
@ -382,8 +390,9 @@ Template::render('profile.index', [
'profile_is_guest' => $viewingAsGuest,
'profile_is_deleted' => false,
'profile_ban_info' => $activeBanInfo,
'profile_avatar_info' => $avatarInfo,
'profile_avatar_asset' => $avatarAsset,
'profile_background_info' => $backgroundInfo,
'profile_background_asset' => $backgroundAsset,
'profile_can_send_messages' => $viewerPermsGlobal->check(Perm::G_MESSAGES_SEND),
'profile_age' => $msz->usersCtx->birthdates->getUserAge($userInfo),
]);

View file

@ -146,9 +146,10 @@ if(isset($_POST['action']) && is_string($_POST['action'])) {
$tmpFiles[] = db_to_zip($archive, $userInfo, 'oauth2_refresh', ['ref_id:s', 'app_id:s', 'user_id:s:n', 'acc_id:s:n', 'ref_token:n', 'ref_scope:s', 'ref_created:t', 'ref_expires:t']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'perms', ['user_id:s:n', 'role_id:s:n', 'forum_id:s:n', 'perms_category:s', 'perms_allow:i', 'perms_deny:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'perms_calculated', ['user_id:s:n', 'forum_id:s:n', 'perms_category:s', 'perms_calculated:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'profile_backgrounds', ['user_id:s', 'bg_attach:s', 'bg_blend:i', 'bg_slide:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'profile_fields_values', ['field_id:s', 'user_id:s', 'format_id:s', 'field_value:s']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'sessions', ['session_id:s', 'user_id:s', 'session_key:n', 'session_remote_addr_first:a', 'session_remote_addr_last:a:n', 'session_user_agent:s', 'session_country:s', 'session_expires:t', 'session_expires_bump:b', 'session_created:t', 'session_active:t:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users', ['user_id:s', 'user_name:s', 'user_password:n', 'user_email:s', 'user_remote_addr_first:a', 'user_remote_addr_last:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'user_display_role_id:s:n', 'user_about_content:s:n', 'user_about_content_format:s', 'user_signature_content:s:n', 'user_signature_content_format:s', 'user_background_settings:i:n', 'user_title:s:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users', ['user_id:s', 'user_name:s', 'user_password:n', 'user_email:s', 'user_remote_addr_first:a', 'user_remote_addr_last:a', 'user_super:b', 'user_country:s', 'user_colour:i:n', 'user_created:t', 'user_active:t:n', 'user_deleted:t:n', 'user_display_role_id:s:n', 'user_about_content:s:n', 'user_about_content_format:s', 'user_signature_content:s:n', 'user_signature_content_format:s', 'user_title:s:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_bans', ['ban_id:s', 'user_id:s', 'mod_id:n', 'ban_severity:i', 'ban_reason_public:s', 'ban_reason_private:s', 'ban_created:t', 'ban_expires:t:n']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_birthdates', ['user_id:s', 'birth_year:i:n', 'birth_month:i', 'birth_day:i']);
$tmpFiles[] = db_to_zip($archive, $userInfo, 'users_password_resets', ['reset_id:s', 'user_id:s', 'reset_remote_addr:a', 'reset_requested:t', 'reset_code:n']);

View file

@ -21,7 +21,7 @@ use Misuzu\Messages\MessagesContext;
use Misuzu\News\NewsData;
use Misuzu\OAuth2\OAuth2Context;
use Misuzu\Perms\PermissionsData;
use Misuzu\Profile\ProfileFieldsData;
use Misuzu\Profile\ProfileContext;
use Misuzu\Redirects\RedirectsContext;
use Misuzu\Routing\{BackedRoutingContext,RoutingContext};
use Misuzu\Users\{UsersContext,UserInfo};
@ -52,11 +52,10 @@ class MisuzuContext {
public private(set) ForumContext $forumCtx;
public private(set) MessagesContext $messagesCtx;
public private(set) OAuth2Context $oauth2Ctx;
public private(set) ProfileContext $profileCtx;
public private(set) UsersContext $usersCtx;
public private(set) RedirectsContext $redirectsCtx;
public private(set) ProfileFieldsData $profileFields;
public private(set) PermissionsData $perms;
public private(set) AuthInfo $authInfo;
public private(set) SiteInfo $siteInfo;
@ -84,6 +83,7 @@ class MisuzuContext {
$this->deps->register($this->forumCtx = $this->deps->constructLazy(ForumContext::class));
$this->deps->register($this->messagesCtx = $this->deps->constructLazy(MessagesContext::class));
$this->deps->register($this->oauth2Ctx = $this->deps->constructLazy(OAuth2Context::class, config: $config->scopeTo('oauth2')));
$this->deps->register($this->profileCtx = $this->deps->constructLazy(ProfileContext::class));
$this->deps->register($this->usersCtx = $this->deps->constructLazy(UsersContext::class));
$this->deps->register($this->redirectsCtx = $this->deps->constructLazy(RedirectsContext::class, config: $config->scopeTo('redirects')));
@ -93,7 +93,6 @@ class MisuzuContext {
$this->deps->register($this->counters = $this->deps->constructLazy(CountersData::class));
$this->deps->register($this->emotes = $this->deps->constructLazy(EmotesData::class));
$this->deps->register($this->news = $this->deps->constructLazy(NewsData::class));
$this->deps->register($this->profileFields = $this->deps->constructLazy(ProfileFieldsData::class));
}
public function getDbQueryCount(): int {

View file

@ -0,0 +1,9 @@
<?php
namespace Misuzu\Profile;
enum ProfileBackgroundAttach: string {
case Cover = 'cover';
case Stretch = 'stretch';
case Tile = 'tile';
case Contain = 'contain';
}

View file

@ -0,0 +1,38 @@
<?php
namespace Misuzu\Profile;
use Index\Db\DbResult;
class ProfileBackgroundInfo {
public function __construct(
public private(set) string $userId,
public private(set) ProfileBackgroundAttach $attach,
public private(set) bool $blend,
public private(set) bool $slide,
) {}
public static function fromResult(DbResult $result): ProfileBackgroundInfo {
return new ProfileBackgroundInfo(
userId: $result->getString(0),
attach: ProfileBackgroundAttach::tryFrom($result->getString(1)) ?? ProfileBackgroundAttach::Cover,
blend: $result->getBoolean(2),
slide: $result->getBoolean(3),
);
}
public bool $cover {
get => $this->attach === ProfileBackgroundAttach::Cover;
}
public bool $stretch {
get => $this->attach === ProfileBackgroundAttach::Stretch;
}
public bool $tile {
get => $this->attach === ProfileBackgroundAttach::Tile;
}
public bool $contain {
get => $this->attach === ProfileBackgroundAttach::Contain;
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Misuzu\Profile;
use RuntimeException;
use Index\Db\{DbConnection,DbStatementCache};
use Misuzu\Users\UserInfo;
class ProfileBackgroundsData {
private DbStatementCache $cache;
public function __construct(DbConnection $dbConn) {
$this->cache = new DbStatementCache($dbConn);
}
public function getProfileBackground(UserInfo|string $userInfo): ?ProfileBackgroundInfo {
$stmt = $this->cache->get(<<<SQL
SELECT user_id, bg_attach, bg_blend, bg_slide
FROM msz_profile_backgrounds
WHERE user_id = ?
SQL);
$stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
$stmt->execute();
$result = $stmt->getResult();
return $result->next() ? ProfileBackgroundInfo::fromResult($result) : null;
}
public function deleteProfileBackground(UserInfo|string $userInfo): void {
$stmt = $this->cache->get(<<<SQL
DELETE FROM msz_profile_backgrounds
WHERE user_id = ?
SQL);
$stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
$stmt->execute();
}
public function updateProfileBackground(
UserInfo|string $userInfo,
ProfileBackgroundAttach|string $attach,
bool $blend,
bool $slide
): ProfileBackgroundInfo {
if(is_string($attach))
$attach = ProfileBackgroundAttach::from($attach);
$stmt = $this->cache->get(<<<SQL
REPLACE INTO msz_profile_backgrounds (
user_id, bg_attach, bg_blend, bg_slide
) VALUES (?, ?, ?, ?)
SQL);
$stmt->nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo);
$stmt->nextParameter($attach->value);
$stmt->nextParameter($blend ? 1 : 0);
$stmt->nextParameter($slide ? 1 : 0);
$stmt->execute();
$bgInfo = $this->getProfileBackground($userInfo);
if($bgInfo === null)
throw new RuntimeException('failed to update profile background');
return $bgInfo;
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace Misuzu\Profile;
use Index\Db\DbConnection;
class ProfileContext {
public private(set) ProfileBackgroundsData $backgrounds;
public private(set) ProfileFieldsData $fields;
public function __construct(DbConnection $dbConn) {
$this->backgrounds = new ProfileBackgroundsData($dbConn);
$this->fields = new ProfileFieldsData($dbConn);
}
}

View file

@ -8,7 +8,7 @@ use Index\Http\{HttpRequest,HttpResponseBuilder};
use Index\Http\Routing\{HttpGet,HttpMiddleware,RouteHandler,RouteHandlerCommon};
use Misuzu\Pagination;
use Misuzu\Forum\ForumContext;
use Misuzu\Profile\ProfileFieldsData;
use Misuzu\Profile\ProfileContext;
use Misuzu\Users\UsersContext;
final class SatoriRoutes implements RouteHandler {
@ -18,7 +18,7 @@ final class SatoriRoutes implements RouteHandler {
private Config $config,
private UsersContext $usersCtx,
private ForumContext $forumCtx,
private ProfileFieldsData $profileFields
private ProfileContext $profileCtx
) {}
/** @return void|int */
@ -49,7 +49,7 @@ final class SatoriRoutes implements RouteHandler {
$fieldId = (string)$request->getParam('field', FILTER_SANITIZE_NUMBER_INT);
try {
$fieldValue = $this->profileFields->getFieldValue($fieldId, $userId);
$fieldValue = $this->profileCtx->fields->getFieldValue($fieldId, $userId);
} catch(RuntimeException $ex) {
return ['error' => 105];
}

View file

@ -8,6 +8,7 @@ use Index\Http\Routing\{HttpGet,RouteHandler,RouteHandlerCommon};
use Index\Urls\{UrlFormat,UrlRegistry,UrlSource,UrlSourceCommon};
use Misuzu\Perm;
use Misuzu\Auth\AuthInfo;
use Misuzu\Profile\ProfileContext;
use Misuzu\Users\{UsersContext,UserInfo};
class AssetsRoutes implements RouteHandler, UrlSource {
@ -16,7 +17,8 @@ class AssetsRoutes implements RouteHandler, UrlSource {
public function __construct(
private AuthInfo $authInfo,
private UrlRegistry $urls,
private UsersContext $usersCtx
private UsersContext $usersCtx,
private ProfileContext $profileCtx
) {}
private function canViewAsset(HttpRequest $request, UserInfo $assetUser): bool {
@ -63,7 +65,10 @@ class AssetsRoutes implements RouteHandler, UrlSource {
} catch(InvalidArgumentException $ex) {}
if(!empty($userInfo)) {
$userAssetInfo = new UserBackgroundAsset($userInfo);
$userAssetInfo = new UserBackgroundAsset(
$userInfo,
$this->profileCtx->backgrounds->getProfileBackground($userInfo)
);
if($userAssetInfo->isPresent() && $this->canViewAsset($request, $userInfo))
$assetInfo = $userAssetInfo;
}

View file

@ -1,14 +1,9 @@
<?php
namespace Misuzu\Users\Assets;
use InvalidArgumentException;
use Misuzu\Profile\ProfileBackgroundInfo;
use Misuzu\Users\UserInfo;
// attachment and attributes are to be stored in the same byte
// left half is for attributes, right half is for attachments
// this makes for 16 possible attachments and 4 possible attributes
// since attachments are just an incrementing number and attrs are flags
class UserBackgroundAsset extends UserImageAsset {
private const FORMAT = 'backgrounds/original/%d.msz';
@ -16,46 +11,11 @@ class UserBackgroundAsset extends UserImageAsset {
private const MAX_HEIGHT = 2160;
private const MAX_BYTES = 1500000;
public const ATTACH_NONE = 0x00;
public const ATTACH_COVER = 0x01;
public const ATTACH_STRETCH = 0x02;
public const ATTACH_TILE = 0x03;
public const ATTACH_CONTAIN = 0x04;
public const ATTRIB_BLEND = 0x10;
public const ATTRIB_SLIDE = 0x20;
public const ATTACHMENT_STRINGS = [
self::ATTACH_COVER => 'cover',
self::ATTACH_STRETCH => 'stretch',
self::ATTACH_TILE => 'tile',
self::ATTACH_CONTAIN => 'contain',
];
public const ATTRIBUTE_STRINGS = [
self::ATTRIB_BLEND => 'blend',
self::ATTRIB_SLIDE => 'slide',
];
/** @return array<int, string> */
public static function getAttachmentStringOptions(): array {
return [
self::ATTACH_COVER => 'Cover',
self::ATTACH_STRETCH => 'Stretch',
self::ATTACH_TILE => 'Tile',
self::ATTACH_CONTAIN => 'Contain',
];
}
private int $settings;
public function __construct(UserInfo $userInfo) {
public function __construct(
UserInfo $userInfo,
private ?ProfileBackgroundInfo $bgInfo
) {
parent::__construct($userInfo);
$this->settings = (int)$userInfo->backgroundSettings;
}
public function getSettings(): int {
return $this->settings;
}
public function getMaxWidth(): int {
@ -78,68 +38,4 @@ class UserBackgroundAsset extends UserImageAsset {
public function getRelativePath(): string {
return sprintf(self::FORMAT, $this->getUserId());
}
public function getAttachment(): int {
return $this->settings & 0x0F;
}
public function getAttachmentString(): string {
return self::ATTACHMENT_STRINGS[$this->getAttachment()] ?? '';
}
public function setAttachment(int $attach): self {
$this->settings = $this->getAttributes() | ($attach & 0x0F);
return $this;
}
public function setAttachmentString(string $attach): self {
if(!in_array($attach, self::ATTACHMENT_STRINGS))
throw new InvalidArgumentException;
$this->setAttachment(array_flip(self::ATTACHMENT_STRINGS)[$attach]);
return $this;
}
public function getAttributes(): int {
return $this->settings & 0xF0;
}
public function setAttributes(int $attrib): self {
$this->settings = $this->getAttachment() | ($attrib & 0xF0);
return $this;
}
public function isBlend(): bool {
return ($this->getAttributes() & self::ATTRIB_BLEND) > 0;
}
public function setBlend(bool $blend): self {
$this->settings = $blend
? ($this->settings | self::ATTRIB_BLEND)
: ($this->settings & ~self::ATTRIB_BLEND);
return $this;
}
public function isSlide(): bool {
return ($this->getAttributes() & self::ATTRIB_SLIDE) > 0;
}
public function setSlide(bool $slide): self {
$this->settings = $slide
? ($this->settings | self::ATTRIB_SLIDE)
: ($this->settings & ~self::ATTRIB_SLIDE);
return $this;
}
/** @return string[] */
public function getClassNames(string $format = '%s'): array {
$names = [];
$attachment = $this->getAttachment();
$attributes = $this->getAttributes();
if(array_key_exists($attachment, self::ATTACHMENT_STRINGS))
$names[] = sprintf($format, self::ATTACHMENT_STRINGS[$attachment]);
foreach(self::ATTRIBUTE_STRINGS as $flag => $name)
if(($attributes & $flag) > 0)
$names[] = sprintf($format, $name);
return $names;
}
public function delete(): void {
parent::delete();
$this->settings = 0;
}
}

View file

@ -25,7 +25,6 @@ class UserInfo {
public private(set) TextFormat $aboutBodyFormat,
public private(set) ?string $signatureBody,
public private(set) TextFormat $signatureBodyFormat,
public private(set) ?int $backgroundSettings,
public private(set) ?string $title,
) {}
@ -48,8 +47,7 @@ class UserInfo {
aboutBodyFormat: TextFormat::tryFrom($result->getString(14)) ?? TextFormat::Plain,
signatureBody: $result->getStringOrNull(15),
signatureBodyFormat: TextFormat::tryFrom($result->getString(16)) ?? TextFormat::Plain,
backgroundSettings: $result->getIntegerOrNull(17),
title: $result->getString(18),
title: $result->getString(17),
);
}

View file

@ -9,7 +9,7 @@ use Index\Colour\Colour;
use Index\Db\{DbConnection,DbStatementCache,DbTools};
use Misuzu\Pagination;
use Misuzu\Tools;
use Misuzu\Parsers\Parsers;
use Misuzu\Parsers\TextFormat;
class UsersData {
private DbStatementCache $cache;
@ -179,7 +179,7 @@ class UsersData {
u.user_display_role_id,
u.user_about_content, u.user_about_content_format,
u.user_signature_content, u.user_signature_content_format,
u.user_background_settings, u.user_title
u.user_title
FROM msz_users AS u
SQL;
if($hasRoleInfo)
@ -288,7 +288,7 @@ class UsersData {
user_display_role_id,
user_about_content, user_about_content_format,
user_signature_content, user_signature_content_format,
user_background_settings, user_title
user_title
FROM msz_users
SQL;
if($selectId) {
@ -358,10 +358,9 @@ class UsersData {
?Colour $colour = null,
RoleInfo|string|null $displayRoleInfo = null,
?string $aboutBody = null,
?int $aboutBodyFormat = null,
TextFormat|string|null $aboutBodyFormat = null,
?string $signatureBody = null,
?int $signatureBodyFormat = null,
?int $backgroundSettings = null,
TextFormat|string|null $signatureBodyFormat = null,
?string $title = null
): void {
if($userInfo instanceof UserInfo)
@ -409,28 +408,27 @@ class UsersData {
}
if($aboutBody !== null && $aboutBodyFormat !== null) {
if(is_string($aboutBodyFormat))
$aboutBodyFormat = TextFormat::tryFrom($aboutBodyFormat) ?? null;
if(self::validateProfileAbout($aboutBodyFormat, $aboutBody) !== '')
throw new InvalidArgumentException('$aboutBody and $aboutBodyFormat contain invalid data!');
$fields[] = 'user_about_content = ?';
$values[] = $aboutBody;
$fields[] = 'user_about_content_format = ?';
$values[] = $aboutBodyFormat;
$values[] = $aboutBodyFormat->value;
}
if($signatureBody !== null && $signatureBodyFormat !== null) {
if(is_string($signatureBodyFormat))
$signatureBodyFormat = TextFormat::tryFrom($signatureBodyFormat) ?? null;
if(self::validateForumSignature($signatureBodyFormat, $signatureBody) !== '')
throw new InvalidArgumentException('$signatureBody and $signatureBodyFormat contain invalid data!');
$fields[] = 'user_signature_content = ?';
$values[] = $signatureBody;
$fields[] = 'user_signature_content_format = ?';
$values[] = $signatureBodyFormat;
}
if($backgroundSettings !== null) {
$fields[] = 'user_background_settings = ?';
$values[] = $backgroundSettings;
$values[] = $signatureBodyFormat->value;
}
if($title !== null) {
@ -709,8 +707,8 @@ class UsersData {
};
}
public static function validateProfileAbout(int $parser, string $text): string {
if(!Parsers::isValid($parser))
public static function validateProfileAbout(?TextFormat $format, string $text): string {
if($format === null)
return 'parser';
$length = strlen($text);
@ -729,8 +727,8 @@ class UsersData {
};
}
public static function validateForumSignature(int $parser, string $text): string {
if(!Parsers::isValid($parser))
public static function validateForumSignature(?TextFormat $format, string $text): string {
if($format === null)
return 'parser';
$length = strlen($text);

View file

@ -30,7 +30,7 @@
{% if name|length > 0 %}name="{{ name }}"{% endif %}
{% if checked %}checked{% endif %}
{% if disabled %}disabled{% endif %}
{% if value|length > 0 %}value="{{ value }}"{% endif %}
{% if value is defined and value is not null %}value="{{ value }}"{% endif %}
{% for name, value in attributes|default([]) %}
{{ name }}{% if value|length > 0 %}="{{ value }}"{% endif %}
{% endfor %}>
@ -74,9 +74,9 @@
Click here to select a file!
</div>
<script>
const parent = document.currentScript.parentNode,
input = parent.querySelector('input[type="file"]'),
display = parent.querySelector('.input__upload__selection');
const parent = document.currentScript.parentNode;
const input = parent.querySelector('input[type="file"]');
const display = parent.querySelector('.input__upload__selection');
input.addEventListener('change', ev => display.textContent = Array.from(ev.target.files).map(f => f.name).join(', '));
</script>
</label>

View file

@ -4,7 +4,7 @@
{% set is_in_manage = true %}
{% set title = title|default('Broom Closet') %}
{% set site_logo = '/images/logos/imouto-broom.png' %}
{% set main_css_vars = main_css_vars|default([])|merge({'--site-logo': "url('/images/logos/imouto-broom.png')"}) %}
{% block content %}
<div class="manage">

View file

@ -5,26 +5,19 @@
<link href="/vendor/fontawesome/css/all.min.css" type="text/css" rel="stylesheet">
<link href="{{ asset('common.css') }}" type="text/css" rel="stylesheet">
<link href="{{ asset('misuzu.css') }}" type="text/css" rel="stylesheet">
{% if site_background is defined %}
{% if main_css_vars is defined and main_css_vars is iterable and main_css_vars is not empty %}
<style>
:root {
--background-width: {{ site_background.width }}px;
--background-height: {{ site_background.height }}px;
--background-image: url('{{ site_background_url|raw }}');
}
</style>
{% endif %}
{% if site_logo is defined %}
<style>
:root {
--site-logo: url('{{ site_logo }}');
{% for name, value in main_css_vars %}
{{ name }}: {{ value|raw }};
{% endfor %}
}
</style>
{% endif %}
{% endblock %}
{% set html_body_attrs = {
'class': 'main' ~ (site_background is defined ? (' ' ~ site_background.classNames('main--bg-%s')|join(' ')) : ''),
'class': html_classes('main', main_body_classes|default([])),
'style': global_accent_colour is defined ? ('--accent-colour: ' ~ global_accent_colour) : '',
} %}

View file

@ -47,8 +47,8 @@
{% if perms.edit_avatar %}
<ul class="profile__guidelines__section">
<li class="profile__guidelines__line profile__guidelines__line--header">Avatar</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ profile_avatar_info.maxBytes|format_filesize }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not be larger than <span class="profile__guidelines__emphasis">{{ profile_avatar_info.maxWidth }}x{{ profile_avatar_info.maxHeight }}</span>.</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ profile_avatar_asset.maxBytes|format_filesize }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not be larger than <span class="profile__guidelines__emphasis">{{ profile_avatar_asset.maxWidth }}x{{ profile_avatar_asset.maxHeight }}</span>.</li>
<li class="profile__guidelines__line">Will be centre cropped and scaled to at most <span class="profile__guidelines__emphasis">240x240</span>.</li>
<li class="profile__guidelines__line">Animated GIF images are allowed.</li>
</ul>
@ -57,8 +57,8 @@
{% if perms.edit_background %}
<ul class="profile__guidelines__section">
<li class="profile__guidelines__line profile__guidelines__line--header">Background</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ profile_background_info.maxBytes|format_filesize }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not be larger than <span class="profile__guidelines__emphasis">{{ profile_background_info.maxWidth }}x{{ profile_background_info.maxHeight }}</span>.</li>
<li class="profile__guidelines__line">May not exceed the <span class="profile__guidelines__emphasis">{{ profile_background_asset.maxBytes|format_filesize }}</span> file size limit.</li>
<li class="profile__guidelines__line">May not be larger than <span class="profile__guidelines__emphasis">{{ profile_background_asset.maxWidth }}x{{ profile_background_asset.maxHeight }}</span>.</li>
<li class="profile__guidelines__line">GIF images, in general, are only allowed when tiling.</li>
</ul>
{% endif %}
@ -90,15 +90,14 @@
{{ container_title('Background') }}
<div class="profile__background-settings__content">
{{ input_file('background[file]', '', ['image/png', 'image/jpeg', 'image/gif'], {'id':'background-selection'}) }}
{{ input_file('bg_file', '', ['image/png', 'image/jpeg', 'image/gif'], {'id':'background-selection'}) }}
{{ input_checkbox('background[attach]', 'None', true, '', 0, true, {'onchange':'profileChangeBackgroundAttach(this.value)'}) }}
{% for key, value in background_attachments %}
{{ input_checkbox('background[attach]', value, key == profile_background_info.attachment, '', key, true, {'onchange':'profileChangeBackgroundAttach(this.value)'}) }}
{% for key, value in { '': 'None', 'cover': 'Cover', 'stretch': 'Stretch', 'tile': 'Tile', 'contain': 'Contain' } %}
{{ input_checkbox('bg_attach', value, key == profile_background_info.attach.value|default(''), '', key, true, {'onchange':'profileChangeBackgroundAttach(this.value)'}) }}
{% endfor %}
{{ input_checkbox('background[attr][blend]', 'Blend', profile_background_info.blend, '', '', false, {'onchange':'profileToggleBackgroundAttr(\'blend\', this.checked)'}) }}
{{ input_checkbox('background[attr][slide]', 'Slide', profile_background_info.slide, '', '', false, {'onchange':'profileToggleBackgroundAttr(\'slide\', this.checked)'}) }}
{{ input_checkbox('bg_blend', 'Blend', profile_background_info.blend|default(false), '', '', false, {'onchange':'profileToggleBackgroundAttr(\'blend\', this.checked)'}) }}
{{ input_checkbox('bg_slide', 'Slide', profile_background_info.slide|default(false), '', '', false, {'onchange':'profileToggleBackgroundAttr(\'slide\', this.checked)'}) }}
</div>
</div>
{% endif %}
@ -304,10 +303,10 @@
function profileToggleBackground(checked) {
let currentBg = document.body.style.getPropertyValue('--background-image');
if(currentBg != 'initial' && checked) {
if(currentBg != '' && checked) {
profilePreviousBackground = currentBg;
currentBg = 'initial';
} else if(currentBg == 'initial' && !checked) {
currentBg = '';
} else if(currentBg == '' && !checked) {
currentBg = profilePreviousBackground;
}
@ -315,47 +314,23 @@
}
function profileChangeBackgroundAttach(mode) {
const modes = {
1: 'cover',
2: 'stretch',
3: 'tile',
4: 'contain',
};
const modes = ['cover', 'stretch', 'tile', 'contain'];
profileToggleBackground(mode == 0);
for(const m of modes)
document.body.classList.remove(`main--bg-${m}`);
for(let i = 1; i <= Object.keys(modes).length; i++)
document.body.classList.remove('main--bg-' + modes[i]);
if(!modes[mode])
return;
document.body.classList.add('main--bg-' + modes[mode]);
const valid = modes.includes(mode);
profileToggleBackground(valid);
if(valid)
document.body.classList.add(`main--bg-${mode}`);
}
function profileToggleBackgroundAttr(attr, mode) {
let className = '';
switch(attr) {
case 'blend':
className = 'main--bg-blend';
break;
case 'slide':
className = 'main--bg-slide';
break;
}
if(className) {
if(mode)
document.body.classList.add(className);
else
document.body.classList.remove(className);
}
document.body.classList.toggle(`main--bg-${attr}`, mode);
}
document.getElementById('background-selection').addEventListener('change', ev => {
const image = new Image();
const image = new Image;
image.src = URL.createObjectURL(ev.target.files[0]);
image.addEventListener('load', () => {
document.body.style.setProperty('--background-image', 'url(%)'.replace('%', image.src));

View file

@ -3,9 +3,20 @@
{% if profile_user is defined %}
{% set image = url('user-avatar', {'user': profile_user.id, 'res': 200}) %}
{% set manage_link = url('manage-user', {'user': profile_user.id}) %}
{% if (not profile_is_banned or profile_can_edit) %}
{% set site_background = profile_background_info %}
{% set site_background_url = url('user-background', {'user': profile_user.id}) %}
{% if (not profile_is_banned or profile_can_edit) and profile_background_info is not null %}
{% set main_body_classes = main_body_classes|default([])|merge({
'main--bg-cover': profile_background_info.cover,
'main--bg-stretch': profile_background_info.stretch,
'main--bg-tile': profile_background_info.tile,
'main--bg-contain': profile_background_info.contain,
'main--bg-blend': profile_background_info.blend,
'main--bg-slide': profile_background_info.slide,
}) %}
{% set main_css_vars = main_css_vars|default([])|merge({
'--background-width': '%dpx'|format(profile_background_asset.width),
'--background-height': '%dpx'|format(profile_background_asset.height),
'--background-image': "url('%s')"|format(url('user-background', {'user': profile_user.id})),
}) %}
{% endif %}
{% set stats = [
{