Merged account and avatar settings pages.

This commit is contained in:
flash 2018-08-11 20:56:54 +02:00
parent bd83bc15a0
commit 294380d7bc
9 changed files with 524 additions and 569 deletions

View file

@ -33,7 +33,7 @@ switch ($_GET['v'] ?? null) {
$getManageUsers = Database::prepare('
SELECT
u.`user_id`, u.`username`,
COALESCE(u.`user_colour`, r.`role_colour`) as `colour`
COALESCE(u.`user_colour`, r.`role_colour`) as `user_colour`
FROM `msz_users` as u
LEFT JOIN `msz_roles` as r
ON u.`display_role` = r.`role_id`

View file

@ -10,54 +10,23 @@ $queryOffset = (int)($_GET['o'] ?? 0);
$queryTake = 15;
$userPerms = perms_get_user(MSZ_PERMS_USER, $app->getUserId());
$settingsModes = [
'account' => [
'title' => 'Account',
'allow' => perms_check($userPerms, MSZ_USER_PERM_EDIT_PROFILE),
],
'images' => [
'title' => 'Avatar',
'allow' => perms_check($userPerms, MSZ_USER_PERM_CHANGE_AVATAR),
],
'sessions' => [
'title' => 'Sessions',
'allow' => true,
],
'login-history' => [
'title' => 'Login History',
'allow' => true,
],
'log' => [
'title' => 'Account Log',
'allow' => true,
],
$perms = [
'edit_profile' => perms_check($userPerms, MSZ_USER_PERM_EDIT_PROFILE),
'edit_avatar' => perms_check($userPerms, MSZ_USER_PERM_CHANGE_AVATAR),
];
$settingsMode = $_GET['m'] ?? null;
if ($settingsMode === 'avatar') {
header('Location: ?m=images');
return;
}
$settingsNavigation = [];
foreach ($settingsModes as $key => $value) {
if ($value['allow']) {
$settingsNavigation[$value['title']] = $key;
if ($settingsMode === null) {
$settingsMode = $key;
}
}
}
if (!$app->hasActiveSession() || !$settingsModes[$settingsMode]['allow']) {
if (!$app->hasActiveSession()) {
echo render_error(403);
return;
}
$tpl->var('settings_navigation', $settingsNavigation);
$settingsModes = [
'account' => 'Account',
'sessions' => 'Sessions',
'login-history' => 'Login History',
'log' => 'Account Log',
];
$settingsMode = $_GET['m'] ?? key($settingsModes);
$csrfErrorString = "Couldn't verify you, please refresh the page and retry.";
@ -86,6 +55,7 @@ $avatarErrorStrings = [
];
$tpl->vars([
'settings_perms' => $perms,
'settings_mode' => $settingsMode,
'settings_modes' => $settingsModes,
]);
@ -106,228 +76,201 @@ $avatarHeightMax = $app->getConfig()->get('Avatar', 'max_height', 'int', 4000);
$avatarFileSizeMax = $app->getConfig()->get('Avatar', 'max_filesize', 'int', 1000000);
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
switch ($settingsMode) {
case 'account':
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
$settingsErrors[] = $csrfErrorString;
break;
}
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
$settingsErrors[] = $csrfErrorString;
} else {
if (!empty($_POST['profile']) && is_array($_POST['profile'])) {
$setUserFieldErrors = user_profile_fields_set($app->getUserId(), $_POST['profile']);
if (isset($_POST['profile']) && is_array($_POST['profile'])) {
$setUserFieldErrors = user_profile_fields_set($app->getUserId(), $_POST['profile']);
if (count($setUserFieldErrors) > 0) {
foreach ($setUserFieldErrors as $name => $error) {
switch ($error) {
case MSZ_USER_PROFILE_INVALID_FIELD:
$settingsErrors[] = sprintf("Field '%s' does not exist!", $name);
break;
case MSZ_USER_PROFILE_FILTER_FAILED:
$settingsErrors[] = sprintf(
'%s field was invalid!',
user_profile_field_get_display_name($name)
);
break;
case MSZ_USER_PROFILE_UPDATE_FAILED:
$settingsErrors[] = 'Failed to update values, contact an administator.';
break;
default:
$settingsErrors[] = 'An unexpected error occurred, contact an administator.';
break;
}
}
}
}
if (!$disableAccountOptions) {
if (!empty($_POST['current_password'])
|| (
(isset($_POST['password']) || isset($_POST['email']))
&& (!empty($_POST['password']['new']) || !empty($_POST['email']['new']))
)
) {
$updateAccountFields = [];
$fetchPassword = Database::prepare('
SELECT `password`
FROM `msz_users`
WHERE `user_id` = :user_id
');
$fetchPassword->bindValue('user_id', $app->getUserId());
$currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : null;
if (empty($currentPassword)) {
$settingsErrors[] = 'Something went horribly wrong.';
break;
}
if (!password_verify($_POST['current_password'], $currentPassword)) {
$settingsErrors[] = 'Your current password was incorrect.';
break;
}
if (!empty($_POST['email']['new'])) {
if (empty($_POST['email']['confirm'])
|| $_POST['email']['new'] !== $_POST['email']['confirm']) {
$settingsErrors[] = 'The given e-mail addresses did not match.';
if (count($setUserFieldErrors) > 0) {
foreach ($setUserFieldErrors as $name => $error) {
switch ($error) {
case MSZ_USER_PROFILE_INVALID_FIELD:
$settingsErrors[] = sprintf("Field '%s' does not exist!", $name);
break;
}
$email_validate = user_validate_email($_POST['email']['new'], true);
if ($email_validate !== '') {
switch ($email_validate) {
case 'dns':
$settingsErrors[] = 'No valid MX record exists for this domain.';
break;
case 'format':
$settingsErrors[] = 'The given e-mail address was incorrectly formatted.';
break;
case 'in-use':
$settingsErrors[] = 'This e-mail address is already in use.';
break;
default:
$settingsErrors[] = 'Unknown e-mail validation error.';
}
break;
}
$updateAccountFields['email'] = strtolower($_POST['email']['new']);
audit_log('PERSONAL_EMAIL_CHANGE', $app->getUserId(), [
$updateAccountFields['email'],
]);
}
if (!empty($_POST['password']['new'])) {
if (empty($_POST['password']['confirm'])
|| $_POST['password']['new'] !== $_POST['password']['confirm']) {
$settingsErrors[] = "The given passwords did not match.";
break;
}
$password_validate = user_validate_password($_POST['password']['new']);
if ($password_validate !== '') {
$settingsErrors[] = "The given passwords was too weak.";
break;
}
$updateAccountFields['password'] = user_password_hash($_POST['password']['new']);
audit_log('PERSONAL_PASSWORD_CHANGE', $app->getUserId());
}
if (count($updateAccountFields) > 0) {
$updateUser = Database::prepare('
UPDATE `msz_users`
SET ' . pdo_prepare_array_update($updateAccountFields, true) . '
WHERE `user_id` = :user_id
');
$updateAccountFields['user_id'] = $app->getUserId();
$updateUser->execute($updateAccountFields);
}
}
}
break;
case 'images':
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
$settingsErrors[] = $csrfErrorString;
break;
}
if (!empty($_POST['avatar']) && is_array($_POST['avatar']) && !empty($_POST['avatar']['mode'])) {
switch ($_POST['avatar']['mode']) {
case 'delete':
user_avatar_delete($app->getUserId());
break;
case 'upload':
if (empty($_FILES['avatar'])
|| !is_array($_FILES['avatar'])
|| empty($_FILES['avatar']['name']['file'])) {
break;
}
if ($_FILES['avatar']['error']['file'] !== UPLOAD_ERR_OK) {
case MSZ_USER_PROFILE_FILTER_FAILED:
$settingsErrors[] = sprintf(
$avatarErrorStrings['upload'][$_FILES['avatar']['error']['file']]
?? $avatarErrorStrings['upload']['default'],
$_FILES['avatar']['error']['file'],
byte_symbol($avatarFileSizeMax, true),
$avatarWidthMax,
$avatarHeightMax
'%s field was invalid!',
user_profile_field_get_display_name($name)
);
break;
}
$setAvatar = user_avatar_set_from_path(
$app->getUserId(),
$_FILES['avatar']['tmp_name']['file']
case MSZ_USER_PROFILE_UPDATE_FAILED:
$settingsErrors[] = 'Failed to update values, contact an administator.';
break;
default:
$settingsErrors[] = 'An unexpected error occurred, contact an administator.';
break;
}
}
}
}
if (!empty($_POST['avatar']) && is_array($_POST['avatar'])) {
switch ($_POST['avatar']['mode'] ?? '') {
case 'delete':
user_avatar_delete($app->getUserId());
break;
case 'upload':
if (empty($_FILES['avatar'])
|| !is_array($_FILES['avatar'])
|| empty($_FILES['avatar']['name']['file'])) {
break;
}
if ($_FILES['avatar']['error']['file'] !== UPLOAD_ERR_OK) {
$settingsErrors[] = sprintf(
$avatarErrorStrings['upload'][$_FILES['avatar']['error']['file']]
?? $avatarErrorStrings['upload']['default'],
$_FILES['avatar']['error']['file'],
byte_symbol($avatarFileSizeMax, true),
$avatarWidthMax,
$avatarHeightMax
);
if ($setAvatar !== MSZ_USER_AVATAR_NO_ERRORS) {
$settingsErrors[] = sprintf(
$avatarErrorStrings['set'][$setAvatar]
?? $avatarErrorStrings['set']['default'],
$setAvatar,
byte_symbol($avatarFileSizeMax, true),
$avatarWidthMax,
$avatarHeightMax
);
}
break;
}
}
break;
}
case 'sessions':
if (!tmp_csrf_verify($_POST['csrf'] ?? '')) {
$settingsErrors[] = $csrfErrorString;
break;
}
$setAvatar = user_avatar_set_from_path(
$app->getUserId(),
$_FILES['avatar']['tmp_name']['file']
);
if ($setAvatar !== MSZ_USER_AVATAR_NO_ERRORS) {
$settingsErrors[] = sprintf(
$avatarErrorStrings['set'][$setAvatar]
?? $avatarErrorStrings['set']['default'],
$setAvatar,
byte_symbol($avatarFileSizeMax, true),
$avatarWidthMax,
$avatarHeightMax
);
}
break;
}
}
if (!empty($_POST['session']) && is_numeric($_POST['session'])) {
$session_id = (int)($_POST['session'] ?? 0);
if ($session_id < 1) {
$settingsErrors[] = 'Invalid session.';
break;
}
} else {
$findSession = Database::prepare('
SELECT `session_id`, `user_id`
FROM `msz_sessions`
WHERE `session_id` = :session_id
');
$findSession->bindValue('session_id', $session_id);
$session = $findSession->execute() ? $findSession->fetch() : null;
$findSession = Database::prepare('
SELECT `session_id`, `user_id`
FROM `msz_sessions`
WHERE `session_id` = :session_id
if (!$session || (int)$session['user_id'] !== $app->getUserId()) {
$settingsErrors[] = 'You may only end your own sessions.';
} else {
if ((int)$session['session_id'] === $app->getSessionId()) {
header('Location: /auth.php?m=logout&s=' . tmp_csrf_token());
return;
}
user_session_delete($session['session_id']);
audit_log('PERSONAL_SESSION_DESTROY', $app->getUserId(), [
$session['session_id'],
]);
}
}
}
if (!$disableAccountOptions) {
if (!empty($_POST['current_password'])
|| (
(isset($_POST['password']) || isset($_POST['email']))
&& (!empty($_POST['password']['new']) || !empty($_POST['email']['new']))
)
) {
$updateAccountFields = [];
$fetchPassword = Database::prepare('
SELECT `password`
FROM `msz_users`
WHERE `user_id` = :user_id
');
$findSession->bindValue('session_id', $session_id);
$session = $findSession->execute() ? $findSession->fetch() : null;
$fetchPassword->bindValue('user_id', $app->getUserId());
$currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : null;
if (!$session || (int)$session['user_id'] !== $app->getUserId()) {
$settingsErrors[] = 'You may only end your own sessions.';
break;
if (empty($currentPassword)) {
$settingsErrors[] = 'Something went horribly wrong.';
} else {
if (!password_verify($_POST['current_password'], $currentPassword)) {
$settingsErrors[] = 'Your current password was incorrect.';
} else {
if (!empty($_POST['email']['new'])) {
if (empty($_POST['email']['confirm'])
|| $_POST['email']['new'] !== $_POST['email']['confirm']) {
$settingsErrors[] = 'The given e-mail addresses did not match.';
} else {
$email_validate = user_validate_email($_POST['email']['new'], true);
if ($email_validate !== '') {
switch ($email_validate) {
case 'dns':
$settingsErrors[] = 'No valid MX record exists for this domain.';
break;
case 'format':
$settingsErrors[] = 'The given e-mail address was incorrectly formatted.';
break;
case 'in-use':
$settingsErrors[] = 'This e-mail address is already in use.';
break;
default:
$settingsErrors[] = 'Unknown e-mail validation error.';
}
} else {
$updateAccountFields['email'] = strtolower($_POST['email']['new']);
audit_log('PERSONAL_EMAIL_CHANGE', $app->getUserId(), [
$updateAccountFields['email'],
]);
}
}
}
if (!empty($_POST['password']['new'])) {
if (empty($_POST['password']['confirm'])
|| $_POST['password']['new'] !== $_POST['password']['confirm']) {
$settingsErrors[] = "The given passwords did not match.";
} else {
$password_validate = user_validate_password($_POST['password']['new']);
if ($password_validate !== '') {
$settingsErrors[] = "The given passwords was too weak.";
} else {
$updateAccountFields['password'] = user_password_hash($_POST['password']['new']);
audit_log('PERSONAL_PASSWORD_CHANGE', $app->getUserId());
}
}
}
if (count($updateAccountFields) > 0) {
$updateUser = Database::prepare('
UPDATE `msz_users`
SET ' . pdo_prepare_array_update($updateAccountFields, true) . '
WHERE `user_id` = :user_id
');
$updateAccountFields['user_id'] = $app->getUserId();
$updateUser->execute($updateAccountFields);
}
}
}
}
if ((int)$session['session_id'] === $app->getSessionId()) {
header('Location: /auth.php?m=logout&s=' . tmp_csrf_token());
return;
}
user_session_delete($session['session_id']);
audit_log('PERSONAL_SESSION_DESTROY', $app->getUserId(), [
$session['session_id'],
]);
break;
}
}
}
$tpl->var('settings_title', $settingsModes[$settingsMode]['title']);
$tpl->var('settings_title', $settingsModes[$settingsMode]);
$tpl->var('settings_errors', $settingsErrors);
switch ($settingsMode) {
@ -348,23 +291,18 @@ switch ($settingsMode) {
');
$getMail->bindValue('user_id', $app->getUserId());
$currentEmail = $getMail->execute() ? $getMail->fetchColumn() : 'Failed to fetch e-mail address.';
$tpl->vars([
'settings_profile_fields' => $profileFields,
'settings_profile_values' => $userFields,
'settings_disable_account_options' => $disableAccountOptions,
'settings_email' => $currentEmail,
]);
break;
case 'images':
$userHasAvatar = File::exists($app->getStore('avatars/original')->filename($avatarFileName));
$tpl->vars([
'avatar_user_id' => $app->getUserId(),
'avatar_max_width' => $avatarWidthMax,
'avatar_max_height' => $avatarHeightMax,
'avatar_max_filesize' => $avatarFileSizeMax,
'user_has_avatar' => $userHasAvatar,
'settings_profile_fields' => $profileFields,
'settings_profile_values' => $userFields,
'settings_disable_account_options' => $disableAccountOptions,
'settings_email' => $currentEmail,
]);
break;

View file

@ -286,6 +286,7 @@ class Application extends ApplicationBase
$this->templatingInstance->addFilter('html_colour');
$this->templatingInstance->addFilter('url_construct');
$this->templatingInstance->addFilter('country_name', 'get_country_name');
$this->templatingInstance->addFilter('flip', 'array_flip');
$this->templatingInstance->addFilter('first_paragraph');
$this->templatingInstance->addFilter('colour_get_css');
$this->templatingInstance->addFilter('colour_get_css_contrast');

View file

@ -1,114 +1,195 @@
{% extends '@mio/settings/master.twig' %}
{% block settings_content %}
<form method="post" action="?m=account" class="settings__account">
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">Profile</div>
<div class="container">
<div class="container__title">Account</div>
<form action="" method="post" class="settings__account">
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
{% for name, props in settings_profile_fields %}
<label class="settings__account__input settings__account__input--{{ name }}">
<div class="settings__account__input__name">
{{ props.name }}
</div>
<div class="settings__account__input__value">
<input type="{{ props.type|default('text') }}" name="profile[{{ name }}]" value="{{ settings_profile_values['user_' ~ name] }}" class="input__text settings__account__input__value__text">
</div>
</label>
{% endfor %}
</div>
<div class="settings__account__row">
{% if settings_perms.edit_profile %}
<div class="settings__account__column">
<div class="settings__account__title">Profile</div>
{% if settings_disable_account_options %}
<div class="settings__account__column settings__account__column--no-margin settings__account__column--disabled">
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">E-mail and Password changing</div>
<div class="settings__account__disabled">
<a class="input__button" href="https://flashii.net/settings.php?m=account">visit main site</a>
{% for name, props in settings_profile_fields %}
<label class="settings__account__input settings__account__input--{{ name }}">
<div class="settings__account__input__name">
{{ props.name }}
</div>
<div class="settings__account__input__value">
<input type="{{ props.type|default('text') }}" name="profile[{{ name }}]" value="{{ settings_profile_values['user_' ~ name] }}" class="input__text settings__account__input__value__text">
</div>
</label>
{% endfor %}
</div>
{% endif %}
{% if settings_disable_account_options %}
<div class="settings__account__column settings__account__column--no-margin settings__account__column--disabled">
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">E-mail and Password changing</div>
<div class="settings__account__disabled">
<a class="input__button" href="https://flashii.net/settings.php?m=account">visit main site</a>
</div>
</div>
</div>
</div>
</div>
{% else %}
<div class="settings__account__column settings__account__column--no-margin">
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">E-mail</div>
{% else %}
<div class="settings__account__column settings__account__column--no-margin">
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">E-mail</div>
<label class="settings__account__input">
<div class="settings__account__input__name">
Current e-mail address
</div>
<div class="settings__account__input__value">
<input type="text" class="input__text input__text--readonly settings__account__input__value__text" readonly value="{{ settings_email }}">
</div>
</label>
<label class="settings__account__input">
<div class="settings__account__input__name">
Current e-mail address
</div>
<div class="settings__account__input__value">
<input type="text" class="input__text input__text--readonly settings__account__input__value__text" readonly value="{{ settings_email }}">
</div>
</label>
<label class="settings__account__input">
<div class="settings__account__input__name">
New e-mail Address
</div>
<div class="settings__account__input__value">
<input type="text" name="email[new]" class="input__text settings__account__input__value__text">
</div>
</label>
<label class="settings__account__input">
<div class="settings__account__input__name">
New e-mail Address
</div>
<div class="settings__account__input__value">
<input type="text" name="email[new]" class="input__text settings__account__input__value__text">
</div>
</label>
<label class="settings__account__input">
<div class="settings__account__input__name">
Confirmation
</div>
<div class="settings__account__input__value">
<input type="text" name="email[confirm]" class="input__text settings__account__input__value__text">
</div>
</label>
<label class="settings__account__input">
<div class="settings__account__input__name">
Confirmation
</div>
<div class="settings__account__input__value">
<input type="text" name="email[confirm]" class="input__text settings__account__input__value__text">
</div>
</label>
</div>
</div>
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">Password</div>
<label class="settings__account__input">
<div class="settings__account__input__name">
New Password
</div>
<div class="settings__account__input__value">
<input type="password" name="password[new]" class="input__text settings__account__input__value__text">
</div>
</label>
<label class="settings__account__input">
<div class="settings__account__input__name">
Confirmation
</div>
<div class="settings__account__input__value">
<input type="password" name="password[confirm]" class="input__text settings__account__input__value__text">
</div>
</label>
</div>
</div>
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">Confirmation</div>
<label class="settings__account__input">
<div class="settings__account__input__name">
Current Password
</div>
<div class="settings__account__input__value">
<input type="password" name="current_password" placeholder="only needed for e-mail and password updating" class="input__text settings__account__input__value__text">
</div>
</label>
</div>
</div>
</div>
{% endif %}
</div>
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">Password</div>
<label class="settings__account__input">
<div class="settings__account__input__name">
New Password
</div>
<div class="settings__account__input__value">
<input type="password" name="password[new]" class="input__text settings__account__input__value__text">
</div>
</label>
<label class="settings__account__input">
<div class="settings__account__input__name">
Confirmation
</div>
<div class="settings__account__input__value">
<input type="password" name="password[confirm]" class="input__text settings__account__input__value__text">
</div>
</label>
</div>
</div>
<div class="settings__account__row">
<div class="settings__account__column">
<div class="settings__account__title">Confirmation</div>
<label class="settings__account__input">
<div class="settings__account__input__name">
Current Password
</div>
<div class="settings__account__input__value">
<input type="password" name="current_password" placeholder="only needed for e-mail and password updating" class="input__text settings__account__input__value__text">
</div>
</label>
</div>
</div>
{% if settings_perms.edit_profile or not settings_disable_account_options %}
<div class="settings__account__row settings__account__row--buttons">
<button class="input__button">Update</button>
<button class="input__button" type="reset">Reset</button>
</div>
{% endif %}
</form>
</div>
{% if settings_perms.edit_avatar %}
<div class="container">
<div class="container__title">Avatar</div>
<form action="" method="post" class="settings__images" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="{{ avatar_max_filesize }}">
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
<div class="settings__images__sections">
<div class="settings__images__requirements">
<ul class="settings__images__requirements__list">
<li class="settings__images__requirement settings__images__requirement--header">Guidelines</li>
<li class="settings__images__requirement">Keep things sane and suitable for all ages.</li>
<li class="settings__images__requirement">Image may not exceed the <strong>{{ avatar_max_filesize|byte_symbol(true) }}</strong> filesize limit.</li>
<li class="settings__images__requirement settings__images__requirement--header">Avatar</li>
<li class="settings__images__requirement">May not be larger than <strong>{{ avatar_max_width }}x{{ avatar_max_height }}</strong>.</li>
<li class="settings__images__requirement">Will be centre cropped to be <strong>200x200</strong>.</li>
<li class="settings__images__requirement">Animated gif images are allowed.</li>
</ul>
</div>
</div>
<div class="settings__avatar">
<label class="settings__avatar__label">
<div
class="avatar settings__avatar__preview"
id="avatar-preview"
style="background-image:url('/profile.php?u={{ avatar_user_id }}&amp;m=avatar')"></div>
<input
class="settings__avatar__input"
accept="image/png,image/jpeg,image/gif"
type="file"
name="avatar[file]"
id="avatar-selection">
<div class="settings__avatar__name" id="avatar-name">
Click to select a file!
</div>
</label>
<div class="settings__avatar__buttons">
<button
class="settings__avatar__button"
name="avatar[mode]"
value="upload">
Upload
</button>
<button
class="settings__avatar__button settings__avatar__button--delete{{ user_has_avatar ? '' : ' settings__avatar__button--disabled' }}"
{{ user_has_avatar ? '' : 'disabled' }}
name="avatar[mode]"
value="delete">
Delete
</button>
</div>
</div>
</form>
</div>
<div class="settings__account__row settings__account__row--buttons">
<button class="input__button" name="csrf" value="{{ csrf_token() }}">Update</button>
<button class="input__button" type="reset">Reset</button>
</div>
</form>
<script>
function updateAvatarPreview(name, url, previewEl, nameEl) {
url = url || "/profile.php?u={{ avatar_user_id }}&m=avatar";
previewEl = previewEl || document.getElementById('avatar-preview');
nameEl = nameEl || document.getElementById('avatar-name');
previewEl.style.backgroundImage = 'url(\'{0}\')'.replace('{0}', url);
nameEl.textContent = name;
}
document.getElementById('avatar-selection').addEventListener('change', function (ev) {
updateAvatarPreview(ev.target.files[0].name, URL.createObjectURL(ev.target.files[0]));
});
</script>
{% endif %}
{% endblock %}

View file

@ -1,77 +0,0 @@
{% extends '@mio/settings/master.twig' %}
{% block settings_content %}
<form
class="settings__images"
method="post"
action="?m=images"
enctype="multipart/form-data">
<input type="hidden"
name="MAX_FILE_SIZE"
value="{{ avatar_max_filesize }}">
<input type="hidden"
name="csrf"
value="{{ csrf_token() }}">
<div class="settings__images__sections">
<div class="settings__images__requirements">
<ul class="settings__images__requirements__list">
<li class="settings__images__requirement settings__images__requirement--header">Guidelines</li>
<li class="settings__images__requirement">Keep things sane and suitable for all ages.</li>
<li class="settings__images__requirement">Image may not exceed the <strong>{{ avatar_max_filesize|byte_symbol(true) }}</strong> filesize limit.</li>
<li class="settings__images__requirement settings__images__requirement--header">Avatar</li>
<li class="settings__images__requirement">May not be larger than <strong>{{ avatar_max_width }}x{{ avatar_max_height }}</strong>.</li>
<li class="settings__images__requirement">Will be centre cropped to be <strong>200x200</strong>.</li>
<li class="settings__images__requirement">Animated gif images are allowed.</li>
</ul>
</div>
</div>
<div class="settings__avatar">
<label class="settings__avatar__label">
<div
class="avatar settings__avatar__preview"
id="avatar-preview"
style="background-image:url('/profile.php?u={{ avatar_user_id }}&amp;m=avatar')"></div>
<input
class="settings__avatar__input"
accept="image/png,image/jpeg,image/gif"
type="file"
name="avatar[file]"
id="avatar-selection">
<div class="settings__avatar__name" id="avatar-name">
Click to select a file!
</div>
</label>
<div class="settings__avatar__buttons">
<button
class="settings__avatar__button"
name="avatar[mode]"
value="upload">
Upload
</button>
<button
class="settings__avatar__button settings__avatar__button--delete{{ user_has_avatar ? '' : ' settings__avatar__button--disabled' }}"
{{ user_has_avatar ? '' : 'disabled' }}
name="avatar[mode]"
value="delete">
Delete
</button>
</div>
</div>
</form>
<script>
function updateAvatarPreview(name, url, previewEl, nameEl) {
url = url || "/profile.php?u={{ avatar_user_id }}&m=avatar";
previewEl = previewEl || document.getElementById('avatar-preview');
nameEl = nameEl || document.getElementById('avatar-name');
previewEl.style.backgroundImage = 'url(\'' + url + '\')';
nameEl.textContent = name;
}
document.getElementById('avatar-selection').addEventListener('change', function (ev) {
updateAvatarPreview(ev.target.files[0].name, URL.createObjectURL(ev.target.files[0]));
});
</script>
{% endblock %}

View file

@ -4,51 +4,55 @@
{% set alpagination = pagination(audit_log_count, audit_log_take, audit_log_offset, '?m=log', 'settings__') %}
{% block settings_content %}
<div class="settings__description">
<p>This is a log of all "important" actions that have been done using your account for your review. If you notice anything strange, please alert the staff.</p>
</div>
<div class="container">
<div class="container__title">Account Log</div>
<div class="settings__log">
{{ alpagination }}
{% for log in audit_logs %}
<div class="settings__log__entry" id="log-{{ log.log_id }}">
<div class="settings__log__column settings__login-history__column--ip">
<div class="settings__log__column__name">
IP
</div>
<div class="settings__log__column__value">
{{ log.log_ip }}
{% if log.log_country|default('XX') != 'XX' %}
<img class="settings__log__country" src="https://static.flash.moe/flags/fff/{{ log.log_country|lower }}.png" alt="{{ log.log_country }}" title="{{ log.log_country|country_name }}">
{% endif %}
</div>
</div>
<div class="settings__log__column settings__log__column--date" title="{{ log.log_created|date('r') }}">
<div class="settings__log__column__name">
Date
</div>
<time class="settings__log__column__value" datetime="{{ log.log_created|date('c') }}">
{{ log.log_created|time_diff }}
</time>
</div>
<div class="settings__log__column settings__log__column--action">
<div class="settings__log__column__name">
Action
</div>
<div class="settings__log__column__value">
{% if log.log_action in log_strings|keys %}
{{ log_strings[log.log_action]|vsprintf(log.log_params|json_decode) }}
{% else %}
{{ log.log_action }}({{ log.log_params }})
{% endif %}
</div>
</div>
<div class="settings__log">
<div class="settings__description">
<p>This is a log of all "important" actions that have been done using your account for your review. If you notice anything strange, please alert the staff.</p>
</div>
{% endfor %}
{{ alpagination }}
{{ alpagination }}
{% for log in audit_logs %}
<div class="settings__log__entry" id="log-{{ log.log_id }}">
<div class="settings__log__column settings__login-history__column--ip">
<div class="settings__log__column__name">
IP
</div>
<div class="settings__log__column__value">
{{ log.log_ip }}
{% if log.log_country|default('XX') != 'XX' %}
<img class="settings__log__country" src="https://static.flash.moe/flags/fff/{{ log.log_country|lower }}.png" alt="{{ log.log_country }}" title="{{ log.log_country|country_name }}">
{% endif %}
</div>
</div>
<div class="settings__log__column settings__log__column--date" title="{{ log.log_created|date('r') }}">
<div class="settings__log__column__name">
Date
</div>
<time class="settings__log__column__value" datetime="{{ log.log_created|date('c') }}">
{{ log.log_created|time_diff }}
</time>
</div>
<div class="settings__log__column settings__log__column--action">
<div class="settings__log__column__name">
Action
</div>
<div class="settings__log__column__value">
{% if log.log_action in log_strings|keys %}
{{ log_strings[log.log_action]|vsprintf(log.log_params|json_decode) }}
{% else %}
{{ log.log_action }}({{ log.log_params }})
{% endif %}
</div>
</div>
</div>
{% endfor %}
{{ alpagination }}
</div>
</div>
{% endblock %}

View file

@ -4,58 +4,62 @@
{% set lhpagination = pagination(login_attempts_count, login_attempts_take, login_attempts_offset, '?m=login-history', 'settings__') %}
{% block settings_content %}
<div class="settings__description">
<p>These are all the login attempts to your account. If any attempt that you don't recognise is marked as successful your account may be compromised, ask a staff member for advice in this case.</p>
</div>
<div class="container">
<div class="container__title">Login History</div>
<div class="settings__login-history">
{{ lhpagination }}
<div class="settings__login-history">
<div class="settings__description">
<p>These are all the login attempts to your account. If any attempt that you don't recognise is marked as successful your account may be compromised, ask a staff member for advice in this case.</p>
</div>
{% for attempt in user_login_attempts %}
<div class="settings__login-history__entry" id="attempt-{{ attempt.attempt_id }}">
<div class="settings__login-history__column settings__login-history__column--ip">
<div class="settings__login-history__column__name">
IP
</div>
<div class="settings__login-history__column__value">
{{ attempt.attempt_ip_decoded }}
{% if attempt.attempt_country != 'XX' %}
<img class="settings__login-history__country" src="https://static.flash.moe/flags/fff/{{ attempt.attempt_country|lower }}.png" alt="{{ attempt.attempt_country }}" title="{{ attempt.attempt_country|country_name }}">
{% endif %}
</div>
</div>
{{ lhpagination }}
<div class="settings__login-history__column settings__login-history__column--success">
<div class="settings__login-history__column__name">
Was Successful?
</div>
<div class="settings__login-history__column__value settings__login-history__column__value--{{ attempt.was_successful ? 'successful' : 'failed' }}">
{{ attempt.was_successful ? 'Yes' : 'No' }}
</div>
</div>
<div class="settings__login-history__column settings__login-history__column--created" title="{{ attempt.created_at|date('r') }}">
<div class="settings__login-history__column__name">
Attempted
</div>
<time class="settings__login-history__column__value" datetime="{{ attempt.created_at|date('c') }}">
{{ attempt.created_at|time_diff }}
</time>
</div>
{% if attempt.user_agent|length > 0 %}
<div class="settings__login-history__column settings__login-history__column--user_agent">
{% for attempt in user_login_attempts %}
<div class="settings__login-history__entry" id="attempt-{{ attempt.attempt_id }}">
<div class="settings__login-history__column settings__login-history__column--ip">
<div class="settings__login-history__column__name">
User Agent
IP
</div>
<div class="settings__login-history__column__value">
{{ attempt.user_agent }}
{{ attempt.attempt_ip_decoded }}
{% if attempt.attempt_country != 'XX' %}
<img class="settings__login-history__country" src="https://static.flash.moe/flags/fff/{{ attempt.attempt_country|lower }}.png" alt="{{ attempt.attempt_country }}" title="{{ attempt.attempt_country|country_name }}">
{% endif %}
</div>
</div>
{% endif %}
</div>
{% endfor %}
{{ lhpagination }}
<div class="settings__login-history__column settings__login-history__column--success">
<div class="settings__login-history__column__name">
Was Successful?
</div>
<div class="settings__login-history__column__value settings__login-history__column__value--{{ attempt.was_successful ? 'successful' : 'failed' }}">
{{ attempt.was_successful ? 'Yes' : 'No' }}
</div>
</div>
<div class="settings__login-history__column settings__login-history__column--created" title="{{ attempt.created_at|date('r') }}">
<div class="settings__login-history__column__name">
Attempted
</div>
<time class="settings__login-history__column__value" datetime="{{ attempt.created_at|date('c') }}">
{{ attempt.created_at|time_diff }}
</time>
</div>
{% if attempt.user_agent|length > 0 %}
<div class="settings__login-history__column settings__login-history__column--user_agent">
<div class="settings__login-history__column__name">
User Agent
</div>
<div class="settings__login-history__column__value">
{{ attempt.user_agent }}
</div>
</div>
{% endif %}
</div>
{% endfor %}
{{ lhpagination }}
</div>
</div>
{% endblock %}

View file

@ -4,7 +4,7 @@
{% set title = 'Settings » ' ~ settings_title %}
{% block content %}
{{ navigation(settings_navigation, settings_mode, true, '?m=%s') }}
{{ navigation(settings_modes|flip, settings_mode, true, '?m=%s') }}
{% block settings_container %}
{% if settings_errors is defined and settings_errors|length > 0 %}
@ -20,14 +20,14 @@
</div>
{% endif %}
<div class="container settings settings--{{ settings_mode }}">
<div class="container__title settings__title settings__title--{{ settings_mode }}">{{ title }}</div>
{% block settings_content %}
<div class="container__content">
This is a blank settings page.
<div class="container">
<div class="container__title">{{ title }}</div>
<div class="container__content">
This is a blank settings page.
</div>
</div>
{% endblock %}
</div>
{% endblock %}
{{ navigation(mio_navigation) }}

View file

@ -4,66 +4,70 @@
{% set spagination = pagination(sessions_count, sessions_take, sessions_offset, '?m=sessions', 'settings__') %}
{% block settings_content %}
<div class="settings__description">
<p>These are the active logins to your account, clicking the Kill button will force a logout on that session. Your current login is highlighted with a darker purple so you don't accidentally force yourself to logout.</p>
</div>
<div class="container">
<div class="container__title">Login History</div>
<div class="settings__sessions">
{{ spagination }}
<div class="settings__sessions">
<div class="settings__description">
<p>These are the active logins to your account, clicking the Kill button will force a logout on that session. Your current login is highlighted with a darker purple so you don't accidentally force yourself to logout.</p>
</div>
{% for session in user_sessions %}
<div class="settings__sessions__entry{% if session.session_id == active_session_id %} settings__sessions__entry--current{% endif %}" id="session-{{ session.session_id }}">
<div class="settings__sessions__column settings__sessions__column--ip">
<div class="settings__sessions__column__name">
IP
</div>
<div class="settings__sessions__column__value">
{{ session.session_ip_decoded }}
{% if session.session_country != 'XX' %}
<img class="settings__sessions__country" src="https://static.flash.moe/flags/fff/{{ session.session_country|lower }}.png" alt="{{ session.session_country }}" title="{{ session.session_country|country_name }}">
{% endif %}
</div>
</div>
{{ spagination }}
<div class="settings__sessions__column settings__sessions__column--created" title="{{ session.created_at|date('r') }}">
<div class="settings__sessions__column__name">
Created
</div>
<time class="settings__sessions__column__value" datetime="{{ session.created_at|date('c') }}">
{{ session.created_at|time_diff }}
</time>
</div>
<div class="settings__sessions__column settings__sessions__column--expires" title="{{ session.expires_on|date('r') }}">
<div class="settings__sessions__column__name">
Expires
</div>
<time class="settings__sessions__column__value" datetime="{{ session.expires_on|date('c') }}">
{{ session.expires_on|time_diff }}
</time>
</div>
{% if session.user_agent|length > 0 %}
<div class="settings__sessions__column settings__sessions__column--user_agent">
{% for session in user_sessions %}
<div class="settings__sessions__entry{% if session.session_id == active_session_id %} settings__sessions__entry--current{% endif %}" id="session-{{ session.session_id }}">
<div class="settings__sessions__column settings__sessions__column--ip">
<div class="settings__sessions__column__name">
User Agent
IP
</div>
<div class="settings__sessions__column__value">
{{ session.user_agent }}
{{ session.session_ip_decoded }}
{% if session.session_country != 'XX' %}
<img class="settings__sessions__country" src="https://static.flash.moe/flags/fff/{{ session.session_country|lower }}.png" alt="{{ session.session_country }}" title="{{ session.session_country|country_name }}">
{% endif %}
</div>
</div>
{% endif %}
<form class="settings__sessions__column settings__sessions__column--options" method="post" action="?m=sessions">
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
<input type="hidden" name="session" value="{{ session.session_id }}">
<button class="input__button settings__sessions__button">
{{ session.session_id == active_session_id ? 'Logout' : 'Kill' }}
</button>
</form>
</div>
{% endfor %}
<div class="settings__sessions__column settings__sessions__column--created" title="{{ session.created_at|date('r') }}">
<div class="settings__sessions__column__name">
Created
</div>
<time class="settings__sessions__column__value" datetime="{{ session.created_at|date('c') }}">
{{ session.created_at|time_diff }}
</time>
</div>
{{ spagination }}
<div class="settings__sessions__column settings__sessions__column--expires" title="{{ session.expires_on|date('r') }}">
<div class="settings__sessions__column__name">
Expires
</div>
<time class="settings__sessions__column__value" datetime="{{ session.expires_on|date('c') }}">
{{ session.expires_on|time_diff }}
</time>
</div>
{% if session.user_agent|length > 0 %}
<div class="settings__sessions__column settings__sessions__column--user_agent">
<div class="settings__sessions__column__name">
User Agent
</div>
<div class="settings__sessions__column__value">
{{ session.user_agent }}
</div>
</div>
{% endif %}
<form class="settings__sessions__column settings__sessions__column--options" method="post" action="?m=sessions">
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
<input type="hidden" name="session" value="{{ session.session_id }}">
<button class="input__button settings__sessions__button">
{{ session.session_id == active_session_id ? 'Logout' : 'Kill' }}
</button>
</form>
</div>
{% endfor %}
{{ spagination }}
</div>
</div>
{% endblock %}