Template/Update (Part 46 - IV)

* account management rework: Personal Settings functionality
 * email, password, username update
 * email updates now also mails the old address for confirmation
This commit is contained in:
Sarjuuk
2025-08-28 17:55:08 +02:00
parent 8fadce88ad
commit 258ac19f0a
36 changed files with 628 additions and 15 deletions

View File

@@ -24,6 +24,7 @@ Also, this project is not meant to be used for commercial purposes of any kind!
+ [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php) + [MySQL Improved](https://www.php.net/manual/en/book.mysqli.php)
+ [Multibyte String](https://www.php.net/manual/en/book.mbstring.php) + [Multibyte String](https://www.php.net/manual/en/book.mbstring.php)
+ [File Information](https://www.php.net/manual/en/book.fileinfo.php) + [File Information](https://www.php.net/manual/en/book.fileinfo.php)
+ [Internationalization](https://www.php.net/manual/en/book.intl.php)
+ [GNU Multiple Precision](https://www.php.net/manual/en/book.gmp.php) (When using TrinityCore as auth source) + [GNU Multiple Precision](https://www.php.net/manual/en/book.gmp.php) (When using TrinityCore as auth source)
+ MySQL ≥ 5.7.0 OR MariaDB ≥ 10.6.4 OR similar + MySQL ≥ 5.7.0 OR MariaDB ≥ 10.6.4 OR similar
+ [TDB 335.21101](https://github.com/TrinityCore/TrinityCore/releases/tag/TDB335.21101) (no other other providers are supported at this time) + [TDB 335.21101](https://github.com/TrinityCore/TrinityCore/releases/tag/TDB335.21101) (no other other providers are supported at this time)

View File

@@ -28,6 +28,7 @@ class AccountBaseResponse extends TemplateResponse
public string $curEmail = ''; public string $curEmail = '';
public string $curName = ''; public string $curName = '';
public string $renameCD = ''; public string $renameCD = '';
public string $activeCD = '';
public array $description = []; public array $description = [];
public array $signature = []; public array $signature = [];
public int $avMode = 0; public int $avMode = 0;
@@ -51,7 +52,7 @@ class AccountBaseResponse extends TemplateResponse
{ {
array_unshift($this->title, Lang::account('settings')); array_unshift($this->title, Lang::account('settings'));
$user = DB::Aowow()->selectRow('SELECT `debug`, `email`, `description`, `avatar`, `wowicon` FROM ?_account WHERE `id` = ?d', User::$id); $user = DB::Aowow()->selectRow('SELECT `debug`, `email`, `description`, `avatar`, `wowicon`, `renameCooldown` FROM ?_account WHERE `id` = ?d', User::$id);
Lang::sort('game', 'ra'); Lang::sort('game', 'ra');
@@ -109,9 +110,12 @@ class AccountBaseResponse extends TemplateResponse
// Username // Username
$this->curName = User::$username; $this->curName = User::$username;
$this->renameCD = Util::formatTime(Cfg::get('ACC_RENAME_DECAY') * 1000);
// todo localize date format; store time if ($user['renameCooldown'] > time())
// $this->renameCD = date('F j, o', time() + 7 * DAY); {
$locCode = implode('_', str_split(Lang::getLocale()->json(), 2)); // ._.
$this->activeCD = (new \IntlDateFormatter($locCode, pattern: Lang::main('dateFmtIntl')))->format($user['renameCooldown']);
}
/* COMMUNITY */ /* COMMUNITY */

View File

@@ -0,0 +1,62 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
* accessed via confirmation email link
* write status to session and redirect to account settings
*/
// ?auth=email-change
class AccountConfirmemailaddressResponse extends TemplateResponse
{
protected string $template = 'text-page-generic';
protected string $pageName = 'confirm-email-address';
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']]
);
private bool $success = false;
protected function generate() : void
{
parent::generate();
if (User::isBanned())
return;
$msg = $this->change();
$this->inputbox = ['inputbox-status', array(
'head' => Lang::account('inputbox', 'head', $this->success ? 'success' : 'error'),
'message' => $this->success ? $msg : '',
'error' => $this->success ? '' : $msg,
)];
}
// this should probably leave change info intact for revert
// todo - move personal settings changes to separate table
private function change() : string
{
if (!$this->assertGET('key'))
return Lang::main('intError');
$acc = DB::Aowow()->selectRow('SELECT `updateValue`, `status`, `statusTimer` FROM ?_account WHERE `token` = ?', $this->_get['key']);
if (!$acc || $acc['status'] != ACC_STATUS_CHANGE_EMAIL || $acc['statusTimer'] < time())
return Lang::account('inputbox', 'error', 'mailTokenUsed');
// 0 changes == error
if (!DB::Aowow()->query('UPDATE ?_account SET `email` = `updateValue`, `status` = ?d, `statusTimer` = 0, `token` = "", `updateValue` = "" WHERE `token` = ?', ACC_STATUS_NONE, $this->_get['key']))
return Lang::main('intError');
$this->success = true;
return Lang::account('inputbox', 'message', 'mailChangeOk');
}
}
?>

View File

@@ -0,0 +1,60 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
* accessed via confirmation email link
* write status to session and redirect to account settings
*/
// 2025 - no longer in use?
class AccountConfirmpasswordResponse extends TemplateResponse
{
protected string $template = 'text-page-generic';
protected string $pageName = 'confirm-password';
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']]
);
private bool $success = false;
protected function generate() : void
{
parent::generate();
if (User::isBanned())
return;
$msg = $this->confirm();
$this->inputbox = ['inputbox-status', array(
'head' => Lang::account('inputbox', 'head', $this->success ? 'success' : 'error'),
'message' => $this->success ? $msg : '',
'error' => $this->success ? '' : $msg,
)];
}
private function confirm() : string
{
if (!$this->assertGET('key'))
return Lang::main('intError');
$acc = DB::Aowow()->selectRow('SELECT `updateValue`, `status`, `statusTimer` FROM ?_account WHERE `token` = ?', $this->_get['key']);
if (!$acc || $acc['status'] != ACC_STATUS_CHANGE_PASS || $acc['statusTimer'] < time())
return Lang::account('inputbox', 'error', 'passTokenUsed');
// 0 changes == error
if (!DB::Aowow()->query('UPDATE ?_account SET `passHash` = `updateValue`, `status` = ?d, `statusTimer` = 0, `token` = "", `updateValue` = "" WHERE `token` = ?', ACC_STATUS_NONE, $this->_get['key']))
return Lang::main('intError');
$this->success = true;
return Lang::account('inputbox', 'message', 'passChangeOk');
}
}
?>

View File

@@ -0,0 +1,62 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
* accessed via revert email link
* write status to session and redirect to account settings
*/
// ?auth=email-revert
class AccountRevertemailaddressResponse extends TemplateResponse
{
protected string $template = 'text-page-generic';
protected string $pageName = 'revert-email-address';
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => '/^[a-zA-Z0-9]{40}$/']]
);
private bool $success = false;
protected function generate() : void
{
parent::generate();
if (User::isBanned())
return;
$msg = $this->revert();
$this->inputbox = ['inputbox-status', array(
'head' => Lang::account('inputbox', 'head', $this->success ? 'success' : 'error'),
'message' => $this->success ? $msg : '',
'error' => $this->success ? '' : $msg,
)];
}
// this should probably take precedence over email-change
// todo - move personal settings changes to separate table
private function revert() : string
{
if (!$this->assertGET('key'))
return Lang::main('intError');
$acc = DB::Aowow()->selectRow('SELECT `updateValue`, `status`, `statusTimer` FROM ?_account WHERE `token` = ?', $this->_get['key']);
if (!$acc || $acc['status'] != ACC_STATUS_CHANGE_EMAIL || $acc['statusTimer'] < time())
return Lang::account('inputbox', 'error', 'mailTokenUsed');
// 0 changes == error
if (!DB::Aowow()->query('UPDATE ?_account SET `status` = ?d, `statusTimer` = 0, `token` = "", `updateValue` = "" WHERE `token` = ?', ACC_STATUS_NONE, $this->_get['key']))
return Lang::main('intError');
$this->success = true;
return Lang::account('inputbox', 'message', 'mailRevertOk');
}
}
?>

View File

@@ -0,0 +1,80 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
* accessed via account settings form submit
* write status to session and redirect to account settings
*/
class AccountUpdateemailResponse extends TextResponse
{
protected ?string $redirectTo = '?account#personal';
protected bool $requiresLogin = true;
protected array $expectedPOST = array(
'newemail' => ['filter' => FILTER_VALIDATE_EMAIL, 'flags' => FILTER_FLAG_STRIP_AOWOW]
);
private bool $success = false;
public function __construct(string $pageParam)
{
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
(new TemplateResponse())->generateError();
parent::__construct($pageParam);
}
protected function generate() : void
{
if (User::isBanned())
return;
if ($msg = $this->updateMail())
$_SESSION['msg'] = ['email', $this->success, $msg];
}
private function updateMail() : string
{
// no input yet
if (is_null($this->_post['newemail']))
return Lang::main('intError');
// truncated due to validation fail
if (!$this->_post['newemail'])
return Lang::account('emailInvalid');
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_account WHERE `email` = ? AND `id` <> ?d', $this->_post['newemail'], User::$id))
return Lang::account('mailInUse');
$status = DB::Aowow()->selectCell('SELECT `status` FROM ?_account WHERE `statusTimer` > UNIX_TIMESTAMP() AND `id` = ?d', User::$id);
if ($status != ACC_STATUS_NONE && $status != ACC_STATUS_CHANGE_EMAIL)
return Lang::account('isRecovering', [Util::formatTime(Cfg::get('ACC_RECOVERY_DECAY') * 1000)]);
$oldEmail = DB::Aowow()->selectCell('SELECT `email` FROM ?_account WHERE `id` = ?d', User::$id);
if ($this->_post['newemail'] == $oldEmail)
return Lang::account('newMailDiff');
$token = Util::createHash();
// store new mail in updateValue field, exchange when confirmation mail gets confirmed
if (!DB::Aowow()->query('UPDATE ?_account SET `updateValue` = ?, `status` = ?d, `statusTimer` = UNIX_TIMESTAMP() + ?d, `token` = ? WHERE `id` = ?d',
$this->_post['newemail'], ACC_STATUS_CHANGE_EMAIL, Cfg::get('ACC_RECOVERY_DECAY'), $token, User::$id))
return Lang::main('intError');
if (!Util::sendMail($this->_post['newemail'], 'change-email', [$token, $this->_post['newemail']], Cfg::get('ACC_RECOVERY_DECAY')))
return Lang::main('intError2', ['send mail']);
if (!Util::sendMail($oldEmail, 'revert-email', [$token, $oldEmail], Cfg::get('ACC_RECOVERY_DECAY')))
return Lang::main('intError2', ['send mail']);
$this->success = true;
return Lang::account('updateMessage', 'personal', [$this->_post['newemail']]);
}
}
?>

View File

@@ -0,0 +1,86 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
* accessed via account settings form submit
* write status to session and redirect to account settings
*/
class AccountUpdatepasswordResponse extends TextResponse
{
protected ?string $redirectTo = '?account#personal';
protected bool $requiresLogin = true;
protected array $expectedPOST = array(
'currentPassword' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']],
'newPassword' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']],
'confirmPassword' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']],
'globalLogout' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkCheckbox']]
);
private bool $success = false;
public function __construct(string $pageParam)
{
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
(new TemplateResponse())->generateError();
parent::__construct($pageParam);
}
protected function generate() : void
{
if (User::isBanned())
return;
if ($msg = $this->updatePassword())
$_SESSION['msg'] = ['password', $this->success, $msg];
}
private function updatePassword() : string
{
if (!$this->assertPOST('currentPassword', 'newPassword', 'confirmPassword'))
return Lang::main('intError');
if (!Util::validatePassword($this->_post['newPassword'], $e))
return Lang::account($e == 1 ? 'errPassLength' : 'errPassChars');
if ($this->_post['newPassword'] !== $this->_post['confirmPassword'])
return Lang::account('passMismatch');
$userData = DB::Aowow()->selectRow('SELECT `status`, `passHash`, `statusTimer` FROM ?_account WHERE `id` = ?d', User::$id);
if ($userData['status'] != ACC_STATUS_NONE && $userData['status'] != ACC_STATUS_CHANGE_PASS && $userData['statusTimer'] > time())
return Lang::account('isRecovering', [Util::formatTime(Cfg::get('ACC_RECOVERY_DECAY') * 1000)]);
if (!User::verifyCrypt($this->_post['currentPassword'], $userData['passHash']))
return Lang::account('wrongPass');
if (User::verifyCrypt($this->_post['newPassword'], $userData['passHash']))
return Lang::account('newPassDiff');
$token = Util::createHash();
// store new hash in updateValue field, exchange when confirmation mail gets confirmed
if (!DB::Aowow()->query('UPDATE ?_account SET `updateValue` = ?, `status` = ?d, `statusTimer` = UNIX_TIMESTAMP() + ?d, `token` = ? WHERE `id` = ?d',
User::hashCrypt($this->_post['newPassword']), ACC_STATUS_CHANGE_PASS, Cfg::get('ACC_RECOVERY_DECAY'), $token, User::$id))
return Lang::main('intError');
$email = DB::Aowow()->selectCell('SELECT `email` FROM ?_account WHERE `id` = ?d', User::$id);
if (!Util::sendMail($email, 'update-password', [$token, $email], Cfg::get('ACC_RECOVERY_DECAY')))
return Lang::main('intError2', ['send mail']);
// logout all other active sessions
if ($this->_post['globalLogout'])
DB::Aowow()->query('UPDATE ?_account_sessions SET `status` = ?d, `touched` = ?d WHERE `userId` = ?d AND `sessionId` <> ? AND `status` = ?d', SESSION_FORCED_LOGOUT, time(), User::$id, session_id(), SESSION_ACTIVE);
$this->success = true;
return Lang::account('updateMessage', 'personal', [User::$email]);
}
}
?>

View File

@@ -0,0 +1,61 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
/*
* accessed via account settings form submit
* write status to session and redirect to account settings
*/
class AccountUpdateusernameResponse extends TextResponse
{
protected ?string $redirectTo = '?account#personal';
protected bool $requiresLogin = true;
protected array $expectedPOST = array(
'newUsername' => ['filter' => FILTER_CALLBACK, 'options' => [Util::class, 'validateUsername']]
);
private bool $success = false;
public function __construct(string $pageParam)
{
if (Cfg::get('ACC_AUTH_MODE') != AUTH_MODE_SELF)
(new TemplateResponse())->generateError();
parent::__construct($pageParam);
}
protected function generate() : void
{
if (User::isBanned())
return;
if ($msg = $this->updateUsername())
$_SESSION['msg'] = ['username', $this->success, $msg];
}
private function updateUsername() : string
{
if (!$this->assertPOST('newUsername'))
return Lang::main('intError');
if (DB::Aowow()->selectCell('SELECT `renameCooldown` FROM ?_account WHERE `id` = ?d', User::$id) > time())
return Lang::main('intError'); // should have grabbed the error response..
// yes, including your current name. you don't want to change into your current name, right?
if (DB::Aowow()->selectCell('SELECT 1 FROM ?_account WHERE LOWER(`username`) = LOWER(?)', $this->_post['newUsername']))
return Lang::account('nameInUse');
DB::Aowow()->query('UPDATE ?_account SET `username` = ?, `renameCooldown` = ?d WHERE `id` = ?d', $this->_post['newUsername'], time() + Cfg::get('acc_rename_decay'), User::$id);
$this->success = true;
return Lang::account('updateMessage', 'username', [User::$username, $this->_post['newUsername']]);
}
}
?>

View File

@@ -13,7 +13,7 @@ define('CLI_HAS_E', CLI && // WIN10 and later u
(!OS_WIN || (function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(STDOUT)))); (!OS_WIN || (function_exists('sapi_windows_vt100_support') && sapi_windows_vt100_support(STDOUT))));
$reqExt = ['SimpleXML', 'gd', 'mysqli', 'mbstring', 'fileinfo'/*, 'gmp'*/]; $reqExt = ['SimpleXML', 'gd', 'mysqli', 'mbstring', 'fileinfo', 'intl'/*, 'gmp'*/];
$badExt = []; $badExt = [];
$error = ''; $error = '';
if ($ext = array_filter($reqExt, fn($x) => !extension_loaded($x))) if ($ext = array_filter($reqExt, fn($x) => !extension_loaded($x)))

View File

@@ -133,7 +133,7 @@ $lang = array(
'colon' => ': ', 'colon' => ': ',
'dateFmtShort' => "d.m.Y", 'dateFmtShort' => "d.m.Y",
'dateFmtLong' => "d.m.Y \u\m H:i", 'dateFmtLong' => "d.m.Y \u\m H:i",
'dateFmtUntil' => "j. F Y", 'dateFmtIntl' => "d. MMMM y",
'timeAgo' => 'vor %s', 'timeAgo' => 'vor %s',
'nfSeparators' => ['.', ','], 'nfSeparators' => ['.', ','],

View File

@@ -133,7 +133,7 @@ $lang = array(
'colon' => ': ', 'colon' => ': ',
'dateFmtShort' => "Y/m/d", 'dateFmtShort' => "Y/m/d",
'dateFmtLong' => "Y/m/d \a\\t g:i A", 'dateFmtLong' => "Y/m/d \a\\t g:i A",
'dateFmtUntil' => "F j, Y", 'dateFmtIntl' => "MMMM d, y",
'timeAgo' => "%s ago", 'timeAgo' => "%s ago",
'nfSeparators' => [',', '.'], 'nfSeparators' => [',', '.'],

View File

@@ -133,7 +133,7 @@ $lang = array(
'colon' => ': ', 'colon' => ': ',
'dateFmtShort' => "d/m/Y", 'dateFmtShort' => "d/m/Y",
'dateFmtLong' => "d/m/Y \a \l\a\s g:i A", 'dateFmtLong' => "d/m/Y \a \l\a\s g:i A",
'dateFmtUntil' => "j \d\\e F \d\\e Y", 'dateFmtIntl' => "d 'de' MMMM 'de' y",
'timeAgo' => 'hace %s', 'timeAgo' => 'hace %s',
'nfSeparators' => ['.', ','], 'nfSeparators' => ['.', ','],

View File

@@ -133,7 +133,7 @@ $lang = array(
'colon' => ' : ', 'colon' => ' : ',
'dateFmtShort' => "Y-m-d", 'dateFmtShort' => "Y-m-d",
'dateFmtLong' => "Y-m-d à g:i A", 'dateFmtLong' => "Y-m-d à g:i A",
'dateFmtUntil' => "j F Y", 'dateFmtIntl' => "d MMMM y",
'timeAgo' => 'il y a %s', 'timeAgo' => 'il y a %s',
'nfSeparators' => ['', ','], 'nfSeparators' => ['', ','],

View File

@@ -133,7 +133,7 @@ $lang = array(
'colon' => ": ", 'colon' => ": ",
'dateFmtShort' => "Y-m-d", 'dateFmtShort' => "Y-m-d",
'dateFmtLong' => "Y-m-d в g:i A", 'dateFmtLong' => "Y-m-d в g:i A",
'dateFmtUntil' => "j F Y г.", 'dateFmtIntl' => "d MMMM y г.",
'timeAgo' => '%s назад', 'timeAgo' => '%s назад',
'nfSeparators' => ['', ','], 'nfSeparators' => ['', ','],

View File

@@ -133,7 +133,7 @@ $lang = array(
'colon' => '', 'colon' => '',
'dateFmtShort' => "Y/m/d", 'dateFmtShort' => "Y/m/d",
'dateFmtLong' => "Y/m/d \a\\t g:i A", 'dateFmtLong' => "Y/m/d \a\\t g:i A",
'dateFmtUntil' => "Y年n月j", 'dateFmtIntl' => "y年M月d",
'timeAgo' => '%s之前', 'timeAgo' => '%s之前',
'nfSeparators' => [',', '.'], 'nfSeparators' => [',', '.'],

View File

@@ -0,0 +1,2 @@
ALTER TABLE `aowow_account`
ADD COLUMN `renameCooldown` int unsigned NOT NULL DEFAULT 0 COMMENT 'timestamp when rename is available again' AFTER `updateValue`;

View File

@@ -0,0 +1,2 @@
DELETE FROM `aowow_config` WHERE `key` = 'acc_rename_decay';
INSERT INTO `aowow_config` VALUES ('acc_rename_decay', 30 * 24 * 60 * 60, '30 * 24 * 60 * 60', 3, 129, 'delay between username changes');

View File

@@ -0,0 +1,11 @@
# Created: 2025
Email Change Confirm
Greetings,
We received a request to change your account's email address. If you made this request, please follow the link below to confirm the change.
HOST_URL?account=confirm-email-address&key=%1$s
If you didn't request this change please feel free to disregard this email. If the link did not work or you have any further concerns about this, please contact CONTACT_EMAIL. The link will become invalid %10$s after this email was sent.
The NAME_SHORT team

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
Demande de confirmation de changement d'adresse e-mail
Bonjour,
Nous avons reçu une demande de modification de l'adresse e-mail associée à votre compte. Si vous êtes à l'origine de cette demande, veuillez suivre le lien ci-dessous pour confirmer le changement.
HOST_URL?account=confirm-email-address&key=%1$s
Si vous n'avez pas demandé ce changement, vous pouvez ignorer cet e-mail. Si le lien ne fonctionne pas ou si vous avez d'autres préoccupations à ce sujet, veuillez contacter CONTACT_EMAIL. Ce lien deviendra invalide %10$s après l'envoi de cet e-mail.
L'équipe NAME_SHORT

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
Bestätigung der E-Mail-Änderung angefordert
Hallo,
Wir haben eine Anfrage zur Änderung Ihrer E-Mail-Adresse erhalten. Wenn Sie diese Anfrage gestellt haben, folgen Sie bitte dem untenstehenden Link, um die Änderung zu bestätigen.
HOST_URL?account=confirm-email-address&key=%1$s
Falls Sie diese Änderung nicht angefordert haben, können Sie diese E-Mail ignorieren. Falls der Link nicht funktioniert oder Sie weitere Fragen haben, wenden Sie sich bitte an CONTACT_EMAIL. Der Link wird %10$s nach Versand dieser E-Mail ungültig.
Das Team von NAME_SHORT

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
确认更改电子邮件地址
您好,
我们收到了一项更改您账户电子邮件地址的请求。如果是您本人操作,请点击下方链接以确认更改。
HOST_URL?account=confirm-email-address&key=%1$s
如果您未曾发起此更改,请忽略此邮件。如果链接无法使用或您对此有任何疑问,请联系 CONTACT_EMAIL。此链接将在本邮件发送后 %10$s 失效。
NAME_SHORT 团队敬上

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
Confirmación de cambio de correo electrónico
Saludos,
Hemos recibido una solicitud para cambiar la dirección de correo electrónico de su cuenta. Si usted realizó esta solicitud, siga el enlace de abajo para confirmar el cambio.
HOST_URL?account=confirm-email-address&key=%1$s
Si usted no solicitó este cambio, puede ignorar este correo. Si el enlace no funciona o tiene alguna inquietud, por favor contacte a CONTACT_EMAIL. El enlace se invalidará %10$s después de que este correo haya sido enviado.
El equipo de NAME_SHORT

View File

@@ -0,0 +1,12 @@
# GPTed from 2025 source
Подтверждение изменения адреса электронной почты
Здравствуйте,
Мы получили запрос на изменение адреса электронной почты, связанного с вашим аккаунтом. Если вы отправили этот запрос, пожалуйста, перейдите по ссылке ниже для подтверждения изменения.
HOST_URL?account=confirm-email-address&key=%1$s
Если вы не запрашивали это изменение, просто проигнорируйте это письмо. Если ссылка не работает или у вас есть дополнительные вопросы, пожалуйста, свяжитесь с CONTACT_EMAIL. Ссылка станет недействительной через %10$s после отправки этого письма.
Команда NAME_SHORT
Пожалуйста, перейдите по ссылке ниже, чтобы подтвердить ваш новый адрес электронной почты.

View File

@@ -0,0 +1,11 @@
# Created: 2025
Email Change Requested
Greetings,
We received a request to change your account's email address. If you made this request, please follow the instructions in the confirmation email sent to the address indicated. If you didn't make such a request, please click the link below to prevent the email from being changed.
HOST_URL?account=revert-email-address&key=%1$s
If the link did not work or you have any further concerns about this, please contact CONTACT_EMAIL. This link will automatically become invalid %10$s from now.
The NAME_SHORT team

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
Demande de modification d'adresse e-mail
Bonjour,
Nous avons reçu une demande de modification de l'adresse e-mail associée à votre compte. Si vous êtes à l'origine de cette demande, veuillez suivre les instructions contenues dans l'e-mail de confirmation envoyé à l'adresse indiquée. Si vous n'êtes pas à l'origine de cette demande, veuillez cliquer sur le lien ci-dessous pour empêcher la modification de l'adresse e-mail.
HOST_URL?account=revert-email-address&key=%1$s
Si le lien ne fonctionne pas ou si vous avez d'autres préoccupations à ce sujet, veuillez contacter CONTACT_EMAIL. Ce lien deviendra automatiquement invalide dans %10$s.
L'équipe NAME_SHORT

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
E-Mail-Änderung angefordert
Hallo,
Wir haben eine Anfrage zur Änderung Ihrer E-Mail-Adresse erhalten. Wenn Sie diese Anfrage gestellt haben, folgen Sie bitte den Anweisungen in der Bestätigungs-E-Mail, die an die angegebene Adresse gesendet wurde. Falls Sie diese Anfrage nicht gestellt haben, klicken Sie bitte auf den untenstehenden Link, um die Änderung der E-Mail-Adresse zu verhindern.
HOST_URL?account=revert-email-address&key=%1$s
Falls der Link nicht funktioniert oder Sie weitere Fragen haben, wenden Sie sich bitte an CONTACT_EMAIL. Dieser Link wird automatisch nach %%10$s ungültig.
Ihr NAME_SHORT-Team

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
请求更改电子邮件地址
您好,
我们收到了一项更改您账户电子邮件地址的请求。如果是您本人操作,请按照发送到指定地址的确认邮件中的说明进行操作。如果不是您本人操作,请点击下方链接以阻止电子邮件地址的更改。
HOST_URL?account=revert-email-address&key=%1$s
如果链接无法使用或您对此有任何疑问,请联系 CONTACT_EMAIL。此链接将在 %10$s 后自动失效。
NAME_SHORT 团队敬上

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
Solicitud de cambio de correo electrónico
Saludos,
Hemos recibido una solicitud para cambiar la dirección de correo electrónico de su cuenta. Si usted realizó esta solicitud, siga las instrucciones en el correo de confirmación enviado a la dirección indicada. Si no realizó esta solicitud, haga clic en el enlace de abajo para evitar el cambio de correo electrónico.
HOST_URL?account=revert-email-address&key=%1$s
Si el enlace no funciona o tiene alguna inquietud, por favor contacte a CONTACT_EMAIL. Este enlace se invalidará automáticamente en %10$s.
El equipo de NAME_SHORT

View File

@@ -0,0 +1,11 @@
# GPTed from 2025 source
Запрос на изменение адреса электронной почты
Здравствуйте,
Мы получили запрос на изменение адреса электронной почты, связанного с вашим аккаунтом. Если вы отправили этот запрос, пожалуйста, следуйте инструкциям в письме с подтверждением, отправленном на указанный адрес. Если вы не отправляли такой запрос, пожалуйста, перейдите по ссылке ниже, чтобы предотвратить изменение адреса электронной почты.
HOST_URL?account=revert-email-address&key=%1$s
Если ссылка не работает или у вас есть дополнительные вопросы, пожалуйста, свяжитесь с CONTACT_EMAIL. Эта ссылка автоматически станет недействительной через %10$s.
Команда NAME_SHORT

View File

@@ -0,0 +1,10 @@
# Created: May 2025
Password Confirmation
Hey!
Please click the link below to confirm your new password.
HOST_URL?account=confirm-password&key=%1$s
Let us know if you have any problems!
The NAME_SHORT team

View File

@@ -0,0 +1,10 @@
# Created: May 2025
Confirmation du mot de passe
Bonjour !
Veuillez cliquer sur le lien ci-dessous pour confirmer votre nouveau mot de passe.
HOST_URL?account=confirm-password&key=%1$s
Faites-nous savoir si vous rencontrez des problèmes !
L'équipe NAME_SHORT

View File

@@ -0,0 +1,10 @@
# Created: May 2025
Passwortbestätigung
Hallo!
Bitte klicke auf den untenstehenden Link, um dein neues Passwort zu bestätigen.
HOST_URL?account=confirm-password&key=%1$s
Lass uns wissen, falls du Probleme hast!
Das NAME_SHORT Team

View File

@@ -0,0 +1,10 @@
# Created by ChatGPT from May 2025 base; locale 0
密码确认
你好!
请点击下面的链接以确认你的新密码。
HOST_URL?account=confirm-password&key=%1$s
如果你有任何问题,请告诉我们!
NAME_SHORT 团队敬上

View File

@@ -0,0 +1,10 @@
# Created: May 2025
Confirmación de contraseña
¡Hola!
Por favor, haz clic en el siguiente enlace para confirmar tu nueva contraseña.
HOST_URL?account=confirm-password&key=%1$s
¡Avísanos si tienes algún problema!
El equipo de NAME_SHORT

View File

@@ -0,0 +1,10 @@
# Created: May 2025
Подтверждение пароля
Здравствуйте!
Пожалуйста, перейдите по ссылке ниже, чтобы подтвердить ваш новый пароль.
HOST_URL?account=confirm-password&key=%1$s
Сообщите нам, если у вас возникнут какие-либо проблемы!
Команда NAME_SHORT

View File

@@ -110,9 +110,9 @@ if ($this->bans):
<div class="box"><div class="msg-<?=($type ? 'success' : 'failure');?>"><?=$msg;?></div></div> <div class="box"><div class="msg-<?=($type ? 'success' : 'failure');?>"><?=$msg;?></div></div>
<?php endif; ?> <?php endif; ?>
<div><?=Lang::account('usernameNote');?></div> <div><?=Lang::account('usernameNote', [$this->renameCD]);?></div>
<?php if ($this->renameCD): ?> <?php if ($this->activeCD): ?>
<div class="msg-failure pad3"><br /><?=Lang::account('renameCD', [$this->renameCD]);?></div> <div class="msg-failure pad3"><br /><?=Lang::account('activeCD', [$this->activeCD]);?></div>
<?php endif; ?> <?php endif; ?>
<form action="?account=update-username" name="ce" method="post" id="change-username"> <form action="?account=update-username" name="ce" method="post" id="change-username">
<table cellspacing="5" cellpadding="0" border="0"> <table cellspacing="5" cellpadding="0" border="0">