From c28e0a90dd22b0045aeab52ce85ce24a9c5ed59a Mon Sep 17 00:00:00 2001 From: flashwave Date: Sat, 8 Feb 2025 02:43:54 +0000 Subject: [PATCH] Moved birthdate into separate table. --- ...08_013647_create_user_birthdates_table.php | 40 +++++++ public-legacy/profile.php | 15 ++- public-legacy/settings/data.php | 2 +- src/Home/HomeRoutes.php | 1 + src/Users/UserBirthdateInfo.php | 31 +++++ src/Users/UserBirthdatesData.php | 68 +++++++++++ src/Users/UserInfo.php | 23 +--- src/Users/UsersContext.php | 18 +++ src/Users/UsersData.php | 106 ++++++++---------- templates/home/home.twig | 4 +- templates/profile/_layout/header.twig | 5 +- templates/profile/index.twig | 6 +- 12 files changed, 225 insertions(+), 94 deletions(-) create mode 100644 database/2025_02_08_013647_create_user_birthdates_table.php create mode 100644 src/Users/UserBirthdateInfo.php create mode 100644 src/Users/UserBirthdatesData.php diff --git a/database/2025_02_08_013647_create_user_birthdates_table.php b/database/2025_02_08_013647_create_user_birthdates_table.php new file mode 100644 index 00000000..0ba107cc --- /dev/null +++ b/database/2025_02_08_013647_create_user_birthdates_table.php @@ -0,0 +1,40 @@ +execute(<<execute(<<execute(<< $perms, 'background_attachments' => UserBackgroundAsset::getAttachmentStringOptions(), + 'birthdate_info' => $msz->usersCtx->birthdates->getUserBirthdate($userInfo), ]); if(!empty($_POST)) { @@ -172,11 +173,14 @@ if($isEditing) { $birthYear = (int)($_POST['birthdate']['year'] ?? 0); $birthMonth = (int)($_POST['birthdate']['month'] ?? 0); $birthDay = (int)($_POST['birthdate']['day'] ?? 0); - $birthValid = $msz->usersCtx->users->validateBirthdate($birthYear, $birthMonth, $birthDay); + $birthValid = UsersContext::validateBirthdate($birthYear, $birthMonth, $birthDay); - if($birthValid === '') - $msz->usersCtx->users->updateUser($userInfo, birthYear: $birthYear, birthMonth: $birthMonth, birthDay: $birthDay); - else + if($birthValid === '') { + if($birthMonth === 0 && $birthDay === 0) + $msz->usersCtx->birthdates->deleteUserBirthdate($userInfo); + else + $msz->usersCtx->birthdates->updateUserBirthdate($userInfo, $birthYear === 0 ? null : $birthYear, $birthMonth, $birthDay); + } else $notices[] = $msz->usersCtx->users->validateBirthdateText($birthValid); } } @@ -381,4 +385,5 @@ Template::render('profile.index', [ 'profile_avatar_info' => $avatarInfo, 'profile_background_info' => $backgroundInfo, 'profile_can_send_messages' => $viewerPermsGlobal->check(Perm::G_MESSAGES_SEND), + 'profile_age' => $msz->usersCtx->birthdates->getUserAge($userInfo), ]); diff --git a/public-legacy/settings/data.php b/public-legacy/settings/data.php index 9103d162..495c14c3 100644 --- a/public-legacy/settings/data.php +++ b/public-legacy/settings/data.php @@ -143,7 +143,7 @@ if(isset($_POST['action']) && is_string($_POST['action'])) { $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_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_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_birthdate:s:n', '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_parser:i', 'user_signature_content:s:n', 'user_signature_parser:i', 'user_background_settings:i:n', '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_password_resets', ['reset_id:s', 'user_id:s', 'reset_remote_addr:a', 'reset_requested:t', 'reset_code:n']); $tmpFiles[] = db_to_zip($archive, $userInfo, 'users_warnings', ['warn_id:s', 'user_id:s', 'mod_id:n', 'warn_body:s', 'warn_created:t']); diff --git a/src/Home/HomeRoutes.php b/src/Home/HomeRoutes.php index e8cbc99c..8ba5d677 100644 --- a/src/Home/HomeRoutes.php +++ b/src/Home/HomeRoutes.php @@ -218,6 +218,7 @@ class HomeRoutes implements RouteHandler, UrlSource { $birthdays[] = [ 'info' => $birthdayInfo, 'colour' => $this->usersCtx->getUserColour($birthdayInfo), + 'age' => $this->usersCtx->birthdates->getUserAge($birthdayInfo), ]; $newestMember = []; diff --git a/src/Users/UserBirthdateInfo.php b/src/Users/UserBirthdateInfo.php new file mode 100644 index 00000000..2e1c34eb --- /dev/null +++ b/src/Users/UserBirthdateInfo.php @@ -0,0 +1,31 @@ +getString(0), + year: $result->getIntegerOrNull(1), + month: $result->getInteger(2), + day: $result->getInteger(3), + ); + } + + public string $birthdate { + get => sprintf('%04d-%02d-%02d', $this->year ?? 0, $this->month, $this->day); + } + + public CarbonImmutable $bornAt { + get => CarbonImmutable::createFromTimestampUTC('Y-m-d', $this->birthdate, 'UTC'); + } +} diff --git a/src/Users/UserBirthdatesData.php b/src/Users/UserBirthdatesData.php new file mode 100644 index 00000000..a20e20d6 --- /dev/null +++ b/src/Users/UserBirthdatesData.php @@ -0,0 +1,68 @@ +cache = new DbStatementCache($dbConn); + } + + public function getUserBirthdate(UserInfo|string $userInfo): ?UserBirthdateInfo { + $stmt = $this->cache->get(<<nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo); + $stmt->execute(); + + $result = $stmt->getResult(); + return $result->next() ? UserBirthdateInfo::fromResult($result) : null; + } + + public function deleteUserBirthdate(UserInfo|string $userInfo): void { + $stmt = $this->cache->get(<<nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo); + $stmt->execute(); + } + + public function updateUserBirthdate(UserInfo|string $userInfo, ?int $year, int $month, int $day): UserBirthdateInfo { + $stmt = $this->cache->get(<<nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo); + $stmt->nextParameter($year); + $stmt->nextParameter($month); + $stmt->nextParameter($day); + $stmt->execute(); + + $birthdateInfo = $this->getUserBirthdate($userInfo); + if($birthdateInfo === null) + throw new RuntimeException('failed to set birthdate'); + + return $birthdateInfo; + } + + public function getUserAge(UserInfo|string $userInfo): ?int { + $stmt = $this->cache->get(<<nextParameter($userInfo instanceof UserInfo ? $userInfo->id : $userInfo); + $stmt->execute(); + + $result = $stmt->getResult(); + return $result->next() ? $result->getIntegerOrNull(0) : null; + } +} diff --git a/src/Users/UserInfo.php b/src/Users/UserInfo.php index 17572754..7909e50b 100644 --- a/src/Users/UserInfo.php +++ b/src/Users/UserInfo.php @@ -25,7 +25,6 @@ class UserInfo { public private(set) int $aboutBodyParser, public private(set) ?string $signatureBody, public private(set) int $signatureBodyParser, - public private(set) ?string $birthdateRaw, public private(set) ?int $backgroundSettings, public private(set) ?string $title, ) {} @@ -49,9 +48,8 @@ class UserInfo { aboutBodyParser: $result->getInteger(14), signatureBody: $result->getStringOrNull(15), signatureBodyParser: $result->getInteger(16), - birthdateRaw: $result->getStringOrNull(17), - backgroundSettings: $result->getIntegerOrNull(18), - title: $result->getString(19), + backgroundSettings: $result->getIntegerOrNull(17), + title: $result->getString(18), ); } @@ -114,21 +112,4 @@ class UserInfo { public bool $isSignatureBodyMarkdown { get => $this->signatureBodyParser === Parser::MARKDOWN; } - - public bool $hasBirthdate { - get => $this->birthdateRaw !== null; - } - - public ?CarbonImmutable $birthdate { - get => $this->birthdateRaw === null ? null : CarbonImmutable::createFromFormat('Y-m-d', $this->birthdateRaw, 'UTC'); - } - - public int $age { - get { - $birthdate = $this->birthdate; - if($birthdate === null || (int)$birthdate->format('Y') < 1900) - return -1; - return (int)$birthdate->diff(CarbonImmutable::now())->format('%y'); - } - } } diff --git a/src/Users/UsersContext.php b/src/Users/UsersContext.php index 9cf10327..bd5741e9 100644 --- a/src/Users/UsersContext.php +++ b/src/Users/UsersContext.php @@ -3,6 +3,7 @@ namespace Misuzu\Users; use Index\Colour\Colour; use Index\Db\DbConnection; +use Misuzu\Tools; class UsersContext { public private(set) UsersData $users; @@ -11,6 +12,7 @@ class UsersContext { public private(set) WarningsData $warnings; public private(set) ModNotesData $modNotes; public private(set) UserTotpsData $totps; + public private(set) UserBirthdatesData $birthdates; /** @var array */ private array $userInfos = []; @@ -31,6 +33,7 @@ class UsersContext { $this->warnings = new WarningsData($dbConn); $this->modNotes = new ModNotesData($dbConn); $this->totps = new UserTotpsData($dbConn); + $this->birthdates = new UserBirthdatesData($dbConn); } public function getUserInfo(string $value, int|string|null $select = null): UserInfo { @@ -88,4 +91,19 @@ class UsersContext { ): bool { return $this->tryGetActiveBan($userInfo, $minimumSeverity) !== null; } + + public static function validateBirthdate(?int $year, int $month, int $day, int $yearRange = 100): string { + $year ??= 0; + + if($day !== 0 && $month !== 0) { + $currentYear = (int)date('Y'); + if($year > 0 && ($year < $currentYear - $yearRange || $year > $currentYear)) + return 'year'; + + if(!Tools::isValidDate($year, $month, $day)) + return 'date'; + } + + return ''; + } } diff --git a/src/Users/UsersData.php b/src/Users/UsersData.php index b14557d3..d16c28ed 100644 --- a/src/Users/UsersData.php +++ b/src/Users/UsersData.php @@ -57,19 +57,26 @@ class UsersData { $hasDeleted = $deleted !== null; $args = 0; - $query = 'SELECT COUNT(*) FROM msz_users'; + $query = << ?', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s u.user_id > ?', ++$args > 1 ? 'AND' : 'WHERE'); if($hasLastActiveInMinutes) - $query .= sprintf(' %s user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s u.user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE'); if($hasNewerThanDays) - $query .= sprintf(' %s user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s u.user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE'); if($hasDeleted) - $query .= sprintf(' %s user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS'); + $query .= sprintf(' %s u.user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS'); $stmt = $this->cache->get($query); if($hasRoleInfo) @@ -163,34 +170,43 @@ class UsersData { $args = 0; $query = << ?', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s u.user_id > ?', ++$args > 1 ? 'AND' : 'WHERE'); if($hasLastActiveInMinutes) - $query .= sprintf(' %s user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s u.user_active > NOW() - INTERVAL ? MINUTE', ++$args > 1 ? 'AND' : 'WHERE'); if($hasNewerThanDays) - $query .= sprintf(' %s user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s u.user_created > NOW() - INTERVAL ? DAY', ++$args > 1 ? 'AND' : 'WHERE'); if($hasDeleted) - $query .= sprintf(' %s user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS'); + $query .= sprintf(' %s u.user_deleted %s NULL', ++$args > 1 ? 'AND' : 'WHERE', $deleted ? 'IS NOT' : 'IS'); if($hasBirthdate) - $query .= sprintf(' %s user_birthdate LIKE ?', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s (ub.birth_month = ? AND ub.birth_day = ?)', ++$args > 1 ? 'AND' : 'WHERE'); if($hasSearchQuery) - $query .= sprintf(' %s user_name LIKE CONCAT("%%", ?, "%%")', ++$args > 1 ? 'AND' : 'WHERE'); + $query .= sprintf(' %s u.user_name LIKE CONCAT("%%", ?, "%%")', ++$args > 1 ? 'AND' : 'WHERE'); if($hasOrderBy) { $query .= sprintf(' ORDER BY %s', $orderBy[0]); if($orderBy[1] !== null) @@ -210,8 +226,10 @@ class UsersData { $stmt->nextParameter($lastActiveInMinutes); if($hasNewerThanDays) $stmt->nextParameter($newerThanDays); - if($hasBirthdate) - $stmt->nextParameter($birthdate->format('%-m-d')); + if($hasBirthdate) { + $stmt->nextParameter($birthdate->format('m')); + $stmt->nextParameter($birthdate->format('d')); + } if($hasSearchQuery) $stmt->nextParameter($searchQuery); if($hasPagination) @@ -267,9 +285,9 @@ class UsersData { UNIX_TIMESTAMP(user_created), UNIX_TIMESTAMP(user_active), UNIX_TIMESTAMP(user_deleted), - user_display_role_id, user_about_content, - user_about_parser, user_signature_content, - user_signature_parser, user_birthdate, + user_display_role_id, + user_about_content, user_about_parser, + user_signature_content, user_signature_parser, user_background_settings, user_title FROM msz_users SQL; @@ -343,9 +361,6 @@ class UsersData { ?int $aboutBodyParser = null, ?string $signatureBody = null, ?int $signatureBodyParser = null, - ?int $birthYear = null, - ?int $birthMonth = null, - ?int $birthDay = null, ?int $backgroundSettings = null, ?string $title = null ): void { @@ -413,18 +428,6 @@ class UsersData { $values[] = $signatureBodyParser; } - if($birthMonth !== null && $birthDay !== null) { - if(self::validateBirthdate($birthYear, $birthMonth, $birthDay) !== '') - throw new InvalidArgumentException('$birthYear, $birthMonth and $birthDay contain invalid data!'); - - // lowest leap year MariaDB accepts, used a 'no year' value - if($birthYear < 1004) - $birthYear = 1004; - - $fields[] = 'user_birthdate = ?'; - $values[] = $birthMonth < 1 || $birthDay < 1 ? null : sprintf('%04d-%02d-%02d', $birthYear, $birthMonth, $birthDay); - } - if($backgroundSettings !== null) { $fields[] = 'user_background_settings = ?'; $values[] = $backgroundSettings; @@ -697,21 +700,6 @@ class UsersData { }; } - public static function validateBirthdate(?int $year, int $month, int $day, int $yearRange = 100): string { - $year ??= 0; - - if($day !== 0 && $month !== 0) { - $currentYear = (int)date('Y'); - if($year > 0 && ($year < $currentYear - $yearRange || $year > $currentYear)) - return 'year'; - - if(!Tools::isValidDate($year, $month, $day)) - return 'date'; - } - - return ''; - } - public static function validateBirthdateText(string $error): string { return match($error) { 'year' => 'The year in your birthdate is too ridiculous.', diff --git a/templates/home/home.twig b/templates/home/home.twig index e65282ce..8feabf2b 100644 --- a/templates/home/home.twig +++ b/templates/home/home.twig @@ -77,14 +77,14 @@ {{ container_title(' Happy Birthday!') }} {% for birthday in birthdays %} - {% set age = birthday.info.age %} + {% set age = birthday.age %}
{{ avatar(birthday.info.id, 50, birthday.info.name) }}
{{ birthday.info.name }}
- {% if age > 0 %} + {% if age is not null %}
Turned {{ age }} today!
diff --git a/templates/profile/_layout/header.twig b/templates/profile/_layout/header.twig index 406bdacd..73a60318 100644 --- a/templates/profile/_layout/header.twig +++ b/templates/profile/_layout/header.twig @@ -42,13 +42,12 @@ {% endif %} {% set hasCountryCode = profile_user.countryCode != 'XX' %} - {% set age = profile_user.age %} - {% set hasAge = age > 0 %} + {% set hasAge = profile_age is not null %} {% if hasCountryCode or hasAge %}
{% if hasCountryCode %}
{% endif %}
- {% if hasCountryCode %}{{ profile_user.countryCode|country_name }}{% endif %}{% if hasAge %}{% if hasCountryCode %}, {% endif %}{{ age }} year{{ age != 's' ? 's' : '' }} old{% endif %} + {% if hasCountryCode %}{{ profile_user.countryCode|country_name }}{% endif %}{% if hasAge %}{% if hasCountryCode %}, {% endif %}{{ profile_age }} year{{ profile_age != 1 ? 's' : '' }} old{% endif %}
{% endif %} diff --git a/templates/profile/index.twig b/templates/profile/index.twig index eda19eaa..adaa190e 100644 --- a/templates/profile/index.twig +++ b/templates/profile/index.twig @@ -213,14 +213,14 @@
Day
- {{ input_select('birthdate[day]', ['-']|merge(range(1, 31)), profile_user.hasBirthdate ? profile_user.birthdate.day : 0, '', '', true, 'profile__birthdate__select profile__birthdate__select--day') }} + {{ input_select('birthdate[day]', ['-']|merge(range(1, 31)), birthdate_info.day|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--day') }}
@@ -229,7 +229,7 @@
Year (may be left empty)
- {{ input_select('birthdate[year]', ['-']|merge(range(null|date('Y'), null|date('Y') - 100)), profile_user.birthdate.year|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--year') }} + {{ input_select('birthdate[year]', ['-']|merge(range(null|date('Y'), null|date('Y') - 100)), birthdate_info.year|default(0), '', '', true, 'profile__birthdate__select profile__birthdate__select--year') }}