cool profile editor v2

This commit is contained in:
flash 2018-09-23 21:12:40 +02:00
parent 774527c616
commit 0ef2dc898b
13 changed files with 231 additions and 94 deletions

View file

@ -55,4 +55,8 @@
&--destroy {
--accent-colour: #c00;
}
&--save {
--accent-colour: #080;
}
}

View file

@ -9,4 +9,16 @@
&:focus {
border-color: var(--accent-colour);
}
&--new {
font-size: 1.2em;
margin: 0;
padding: 5px 10px;
color: #fff;
border-radius: 2px;
background: #222;
box-shadow: inset 0 0 4px #111;
border-color: #222;
transition: border-color .2s;
}
}

View file

@ -10,4 +10,15 @@
&:focus {
border-color: var(--accent-colour);
}
&--new {
font-size: 1.2em;
padding: 5px 10px;
color: #fff;
border-radius: 2px;
background: #222;
box-shadow: inset 0 0 4px #111;
border-color: #222;
transition: border-color .2s;
}
}

View file

@ -3,5 +3,22 @@
&__content {
max-height: 600px;
overflow: auto;
padding: 2px 5px;
&--edit {
padding: 5px;
}
}
&__editor {
width: 100%;
height: 500px;
max-width: 100%;
min-width: 100%;
}
&__select {
margin-bottom: 5px;
width: 100%;
}
}

View file

@ -30,6 +30,10 @@
text-decoration: none;
}
&__input {
width: 100%;
}
&__link {
color: inherit;
text-decoration: underline dotted;

View file

@ -48,6 +48,7 @@
}
&__options {
min-height: 62px;
background-color: rgba(0, 0, 0, .9);
padding: 0 20px;
display: flex;
@ -59,6 +60,10 @@
align-items: center;
}
&__action {
margin-right: 5px;
}
&__stats {
display: flex;
}

View file

@ -4,13 +4,13 @@ use Misuzu\IO\File;
require_once __DIR__ . '/../misuzu.php';
$user_id = (int)($_GET['u'] ?? 0);
$userId = (int)($_GET['u'] ?? 0);
$mode = (string)($_GET['m'] ?? null);
switch ($mode) {
case 'avatar':
$avatar_filename = $app->getDefaultAvatar();
$user_avatar = "{$user_id}.msz";
$user_avatar = "{$userId}.msz";
$cropped_avatar = build_path(
create_directory(build_path($app->getStoragePath(), 'avatars/200x200')),
$user_avatar
@ -42,7 +42,7 @@ switch ($mode) {
case 'background':
$user_background = build_path(
create_directory(build_path($app->getStoragePath(), 'backgrounds/original')),
"{$user_id}.msz"
"{$userId}.msz"
);
if (!is_file($user_background)) {
@ -85,7 +85,7 @@ switch ($mode) {
ON r.`role_id` = u.`display_role`
WHERE `user_id` = :user_id
');
$getProfile->bindValue('user_id', $user_id);
$getProfile->bindValue('user_id', $userId);
$profile = $getProfile->execute() ? $getProfile->fetch() : [];
if (!$profile) {
@ -94,7 +94,19 @@ switch ($mode) {
break;
}
$userPerms = perms_get_user(MSZ_PERMS_USER, $app->getUserId());
$perms = [
'edit_profile' => perms_check($userPerms, MSZ_PERM_USER_EDIT_PROFILE),
'edit_avatar' => perms_check($userPerms, MSZ_PERM_USER_CHANGE_AVATAR),
'edit_background' => perms_check($userPerms, MSZ_PERM_USER_CHANGE_BACKGROUND),
'edit_about' => perms_check($userPerms, MSZ_PERM_USER_EDIT_ABOUT),
];
if ($app->hasActiveSession()) {
$canEdit = $app->getUserId() === $profile['user_id']
|| perms_check($userPerms, MSZ_PERM_USER_MANAGE_USERS);
$isEditing = $canEdit && $mode === 'edit';
$getFriendInfo = Database::prepare('
SELECT
:visitor as `visitor`, :profile as `profile`,
@ -121,12 +133,17 @@ switch ($mode) {
$getFriendInfo->bindValue('profile', $profile['user_id']);
$friendInfo = $getFriendInfo->execute() ? $getFriendInfo->fetch(PDO::FETCH_ASSOC) : [];
tpl_var('friend_info', $friendInfo);
tpl_vars([
'friend_info' => $friendInfo,
]);
}
tpl_vars([
'profile' => $profile,
'profile_fields' => $app->hasActiveSession() ? user_profile_fields_display($profile) : [],
'can_edit' => $canEdit ?? false,
'is_editing' => $isEditing ?? false,
'perms' => $perms,
'profile_fields' => $app->hasActiveSession() ? user_profile_fields_display($profile, !$isEditing) : [],
'has_background' => is_file(build_path($app->getStoragePath(), 'backgrounds/original', "{$profile['user_id']}.msz")),
]);
echo tpl_render('user.profile');

View file

@ -20,6 +20,15 @@ if (!$app->hasActiveSession()) {
return;
}
$settingsUserId = !empty($_REQUEST['user']) && perms_check($userPerms, MSZ_PERM_USER_MANAGE_USERS)
? (int)$_REQUEST['user']
: $app->getUserId();
if ($settingsUserId !== $app->getUserId() && !user_exists($settingsUserId)) {
echo render_error(400);
return;
}
$settingsModes = [
'account' => 'Account',
'sessions' => 'Sessions',
@ -54,6 +63,7 @@ $avatarErrorStrings = [
];
tpl_vars([
'settings_user_id' => $settingsUserId,
'settings_perms' => $perms,
'settings_mode' => $settingsMode,
'settings_modes' => $settingsModes,
@ -69,7 +79,7 @@ if (!array_key_exists($settingsMode, $settingsModes)) {
$settingsErrors = [];
$disableAccountOptions = !MSZ_DEBUG && $app->disableRegistration();
$avatarFileName = "{$app->getUserId()}.msz";
$avatarFileName = "{$settingsUserId}.msz";
$avatarProps = $app->getAvatarProps();
$backgroundProps = $app->getBackgroundProps();
@ -81,7 +91,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!$perms['edit_profile']) {
$settingsErrors[] = "You're not allowed to edit your profile.";
} else {
$setUserFieldErrors = user_profile_fields_set($app->getUserId(), $_POST['profile']);
$setUserFieldErrors = user_profile_fields_set($settingsUserId, $_POST['profile']);
if (count($setUserFieldErrors) > 0) {
foreach ($setUserFieldErrors as $name => $error) {
@ -136,7 +146,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
`user_about_parser` = :parser
WHERE `user_id` = :user
');
$setAbout->bindValue('user', $app->getUserId());
$setAbout->bindValue('user', $settingsUserId);
$setAbout->bindValue('content', strlen($aboutText) < 1 ? null : $aboutText);
$setAbout->bindValue('parser', $aboutParser);
$setAbout->execute();
@ -148,7 +158,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['avatar']) && is_array($_POST['avatar'])) {
switch ($_POST['avatar']['mode'] ?? '') {
case 'delete':
user_avatar_delete($app->getUserId());
user_avatar_delete($settingsUserId);
break;
case 'upload':
@ -176,7 +186,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$setAvatar = user_avatar_set_from_path(
$app->getUserId(),
$settingsUserId,
$_FILES['avatar']['tmp_name']['file'],
$avatarProps
);
@ -198,7 +208,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!empty($_POST['background']) && is_array($_POST['background'])) {
switch ($_POST['background']['mode'] ?? '') {
case 'delete':
user_background_delete($app->getUserId());
user_background_delete($settingsUserId);
break;
case 'upload':
@ -226,7 +236,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
$setBackground = user_background_set_from_path(
$app->getUserId(),
$settingsUserId,
$_FILES['background']['tmp_name']['file'],
$backgroundProps
);
@ -252,9 +262,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
DELETE FROM `msz_sessions`
WHERE `user_id` = :user_id
')->execute([
'user_id' => $app->getUserId(),
'user_id' => $settingsUserId,
]);
audit_log('PERSONAL_SESSION_DESTROY_ALL', $app->getUserId());
audit_log('PERSONAL_SESSION_DESTROY_ALL', $settingsUserId);
header('Location: /');
return;
}
@ -274,7 +284,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$findSession->bindValue('session_id', $session_id);
$session = $findSession->execute() ? $findSession->fetch() : null;
if (!$session || (int)$session['user_id'] !== $app->getUserId()) {
if (!$session || (int)$session['user_id'] !== $settingsUserId) {
$settingsErrors[] = 'You may only end your own sessions.';
} else {
if ((int)$session['session_id'] === $app->getSessionId()) {
@ -283,7 +293,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
user_session_delete($session['session_id']);
audit_log('PERSONAL_SESSION_DESTROY', $app->getUserId(), [
audit_log('PERSONAL_SESSION_DESTROY', $settingsUserId, [
$session['session_id'],
]);
}
@ -304,7 +314,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
FROM `msz_users`
WHERE `user_id` = :user_id
');
$fetchPassword->bindValue('user_id', $app->getUserId());
$fetchPassword->bindValue('user_id', $settingsUserId);
$currentPassword = $fetchPassword->execute() ? $fetchPassword->fetchColumn() : null;
if (empty($currentPassword)) {
@ -339,7 +349,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
} else {
$updateAccountFields['email'] = mb_strtolower($_POST['email']['new']);
audit_log('PERSONAL_EMAIL_CHANGE', $app->getUserId(), [
audit_log('PERSONAL_EMAIL_CHANGE', $settingsUserId, [
$updateAccountFields['email'],
]);
}
@ -357,7 +367,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$settingsErrors[] = "The given passwords was too weak.";
} else {
$updateAccountFields['password'] = user_password_hash($_POST['password']['new']);
audit_log('PERSONAL_PASSWORD_CHANGE', $app->getUserId());
audit_log('PERSONAL_PASSWORD_CHANGE', $settingsUserId);
}
}
}
@ -368,7 +378,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
SET ' . pdo_prepare_array_update($updateAccountFields, true) . '
WHERE `user_id` = :user_id
');
$updateAccountFields['user_id'] = $app->getUserId();
$updateAccountFields['user_id'] = $settingsUserId;
$updateUser->execute($updateAccountFields);
}
}
@ -376,6 +386,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
}
}
if (!empty($_POST['user']) && !empty($_SERVER['HTTP_REFERER'])) {
header('Location: /profile.php?u=' . ((int)($_POST['user'] ?? 0)));
return;
}
}
tpl_vars([
@ -384,7 +399,7 @@ tpl_vars([
]);
switch ($settingsMode) {
case 'account': // TODO: FIX THIS GARBAGE HOLY HELL
case 'account':
$profileFields = user_profile_fields_get();
$getAccountInfo = Database::prepare(sprintf(
@ -395,7 +410,7 @@ switch ($settingsMode) {
',
pdo_prepare_array($profileFields, true, '`user_%s`')
));
$getAccountInfo->bindValue('user_id', $app->getUserId());
$getAccountInfo->bindValue('user_id', $settingsUserId);
$accountInfo = $getAccountInfo->execute() ? $getAccountInfo->fetch(PDO::FETCH_ASSOC) : [];
$userHasAvatar = is_file(build_path($app->getStoragePath(), 'avatars/original', $avatarFileName));
@ -418,7 +433,7 @@ switch ($settingsMode) {
FROM `msz_sessions`
WHERE `user_id` = :user_id
');
$getSessionCount->bindValue('user_id', $app->getUserId());
$getSessionCount->bindValue('user_id', $settingsUserId);
$sessionCount = $getSessionCount->execute() ? $getSessionCount->fetchColumn() : 0;
$getSessions = Database::prepare('
@ -432,7 +447,7 @@ switch ($settingsMode) {
');
$getSessions->bindValue('offset', $queryOffset);
$getSessions->bindValue('take', $queryTake);
$getSessions->bindValue('user_id', $app->getUserId());
$getSessions->bindValue('user_id', $settingsUserId);
$sessions = $getSessions->execute() ? $getSessions->fetchAll() : [];
tpl_vars([
@ -453,7 +468,7 @@ switch ($settingsMode) {
FROM `msz_login_attempts`
WHERE `user_id` = :user_id
');
$getLoginAttemptsCount->bindValue('user_id', $app->getUserId());
$getLoginAttemptsCount->bindValue('user_id', $settingsUserId);
$loginAttemptsCount = $getLoginAttemptsCount->execute() ? $getLoginAttemptsCount->fetchColumn() : 0;
$getLoginAttempts = Database::prepare('
@ -467,14 +482,14 @@ switch ($settingsMode) {
');
$getLoginAttempts->bindValue('offset', $loginAttemptsOffset);
$getLoginAttempts->bindValue('take', min(20, max(5, $queryTake)));
$getLoginAttempts->bindValue('user_id', $app->getUserId());
$getLoginAttempts->bindValue('user_id', $settingsUserId);
$loginAttempts = $getLoginAttempts->execute() ? $getLoginAttempts->fetchAll() : [];
$auditLogCount = audit_log_count($app->getUserId());
$auditLogCount = audit_log_count($settingsUserId);
$auditLog = audit_log_list(
$auditLogOffset,
min(20, max(5, $queryTake)),
$app->getUserId()
$settingsUserId
);
tpl_vars([

View file

@ -163,19 +163,20 @@ function user_profile_fields_set(int $userId, array $fields): array
return $errors;
}
function user_profile_fields_display(array $user): array
function user_profile_fields_display(array $user, bool $hideEmpty = true): array
{
$output = [];
foreach (MSZ_USER_PROFILE_FIELDS as $name => $field) {
$dbn = user_profile_field_get_database_name($name);
if (!array_key_exists($dbn, $user) || empty($user[$dbn])) {
if ($hideEmpty && (!array_key_exists($dbn, $user) || empty($user[$dbn]))) {
continue;
}
$value = $user[$dbn] ?? '';
$output[$name] = $field;
$output[$name]['value'] = htmlentities($user[$dbn]);
$output[$name]['value'] = htmlentities($value);
foreach (['link', 'format'] as $multipath) {
if (empty($output[$name][$multipath]) || !is_array($output[$name][$multipath])) {
@ -183,7 +184,7 @@ function user_profile_fields_display(array $user): array
}
foreach (array_reverse($output[$name][$multipath], true) as $regex => $string) {
if ($regex === '_' || !preg_match("#{$regex}#", $user[$dbn])) {
if ($regex === '_' || !preg_match("#{$regex}#", $value)) {
continue;
}

View file

@ -51,6 +51,18 @@ function user_password_hash(string $password): string
return password_hash($password, MSZ_USERS_PASSWORD_HASH_ALGO);
}
// function of the century, only use this if it doesn't make sense to grab data otherwise
function user_exists(int $userId): bool
{
$check = Database::prepare('
SELECT COUNT(`user_id`) > 0
FROM `msz_users`
WHERE `user_id` = :user_id
');
$check->bindValue('user_id', $userId);
return $check->execute() ? (bool)$check->fetchColumn() : false;
}
function user_id_from_username(string $username): int
{
$getId = Database::prepare('SELECT `user_id` FROM `msz_users` WHERE LOWER(`username`) = LOWER(:username)');

View file

@ -1,10 +1,16 @@
{% extends 'settings/master.twig' %}
{% if user_has_background %}
{% set site_background_url = '/profile.php?m=background&u=' ~ current_user.user_id %}
{% set site_background_url = '/profile.php?m=background&u=' ~ settings_user_id %}
{% endif %}
{% block settings_content %}
{#<div class="warning">
<div class="warning__content">
A few of the elements on this page have been moved to the on-profile editor. To find them, go to your profile and hit the "Edit Profile" button below your avatar.
</div>
</div>#}
<div class="container container--translucent">
<div class="container__title">Account</div>
<form action="" method="post" class="settings__account">
@ -150,7 +156,7 @@
<div
class="avatar settings__avatar__preview"
id="avatar-preview"
style="background-image:url('/profile.php?u={{ current_user.user_id }}&amp;m=avatar')"></div>
style="background-image:url('/profile.php?u={{ settings_user_id }}&amp;m=avatar')"></div>
<input
class="settings__avatar__input"
accept="image/png,image/jpeg,image/gif"
@ -183,7 +189,7 @@
<script>
function updateAvatarPreview(name, url, previewEl, nameEl) {
url = url || "/profile.php?u={{ current_user.user_id }}&m=avatar";
url = url || "/profile.php?u={{ settings_user_id }}&m=avatar";
previewEl = previewEl || document.getElementById('avatar-preview');
nameEl = nameEl || document.getElementById('avatar-name');
previewEl.style.backgroundImage = 'url(\'{0}\')'.replace('{0}', url);
@ -249,7 +255,7 @@
<script>
function updateBackgroundPreview(name, url, previewEl, nameEl) {
url = url || "/profile.php?u={{ current_user.user_id }}&m=background";
url = url || "/profile.php?u={{ settings_user_id }}&m=background";
previewEl = previewEl || document.body;
nameEl = nameEl || document.getElementById('background-name');
previewEl.style.setProperty('--site-background-image', 'url(\'{0}\')'.replace('{0}', url));
@ -271,7 +277,7 @@
<form method="post" action="" enctype="multipart/form-data" class="settings__about">
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
<textarea name="about[text]" class="input__textarea settings__about__text" id="about-textarea">{{ account_info.user_about_content }}</textarea>
<textarea name="about[text]" class="input__textarea settings__about__text" id="about-textarea">{{ account_info.user_about_content|escape }}</textarea>
<div class="settings__about__options">
<select name="about[parser]" class="input__select">

View file

@ -36,13 +36,19 @@
<div class="profile__header__options">
<div class="profile__header__actions">
{% if current_user.user_id|default(0) == profile.user_id %}
<a href="/settings.php" class="input__button input__button--new">Edit Profile</a>
{% elseif current_user is defined %}
{% if is_editing %}
<button class="input__button input__button--new input__button--save profile__header__action">Save</button>
<a href="/profile.php?u={{ profile.user_id }}" class="input__button input__button--new input__button--destroy profile__header__action">Discard</a>
<a href="/settings.php" class="input__button input__button--new profile__header__action">Settings</a>
{% elseif can_edit %}
<a href="/profile.php?u={{ profile.user_id }}&amp;m=edit" class="input__button input__button--new profile__header__action">Edit Profile</a>
{% endif %}
{% if current_user is defined and current_user.user_id != profile.user_id and not is_editing %}
{% if friend_info.visitor_relation == constant('MSZ_USER_RELATION_FOLLOW') %}
<a href="/relations.php?u={{ profile.user_id }}&amp;m=remove" class="input__button input__button--new input__button--destroy">{{ friend_info.profile_relation == constant('MSZ_USER_RELATION_FOLLOW') ? 'Unfriend' : 'Unfollow' }}</a>
<a href="/relations.php?u={{ profile.user_id }}&amp;m=remove" class="input__button input__button--new input__button--destroy profile__header__action">{{ friend_info.profile_relation == constant('MSZ_USER_RELATION_FOLLOW') ? 'Unfriend' : 'Unfollow' }}</a>
{% else %}
<a href="/relations.php?u={{ profile.user_id }}&amp;m=add&amp;t=follow" class="input__button input__button--new">{{ friend_info.profile_relation == constant('MSZ_USER_RELATION_FOLLOW') ? 'Add as Friend' : 'Follow' }}</a>
<a href="/relations.php?u={{ profile.user_id }}&amp;m=add&amp;t=follow" class="input__button input__button--new profile__header__action">{{ friend_info.profile_relation == constant('MSZ_USER_RELATION_FOLLOW') ? 'Add as Friend' : 'Follow' }}</a>
{% endif %}
{% endif %}
</div>

View file

@ -40,7 +40,16 @@
] %}
{% block content %}
<div class="profile" id="profile">
{% if is_editing %}
<form class="profile" method="post" action="/settings.php">
{% else %}
<div class="profile">
{% endif %}
{% if is_editing %}
<input type="hidden" name="csrf" value="{{ csrf_token() }}">
<input type="hidden" name="user" value="{{ profile.user_id }}">
{% endif %}
{% include 'user/_layout/header.twig' %}
<div class="warning">
@ -57,17 +66,21 @@
You must <a href="/auth.php?m=login" class="profile__accounts__link">log in</a> to view full profiles!
</div>
</div>
{% elseif profile_fields|default([])|length > 0 %}
{% elseif is_editing ? perms.edit_profile : profile_fields|default([])|length > 0 %}
<div class="container container--new">
<div class="container__title">
Elsewhere
</div>
<div class="profile__accounts">
{% for name, data in profile_fields %}
<div class="profile__accounts__item">
<label class="profile__accounts__item">
<div class="profile__accounts__title">
{{ data.name }}
</div>
{% if is_editing %}
<input type="{{ data.type|default('text') }}" name="profile[{{ name }}]" value="{{ profile['user_' ~ name] }}" class="input__text input__text--new profile__accounts__input">
{% else %}
<div class="profile__accounts__value"
{% if data.tooltip is defined %}title="{{ data.tooltip|format(data.value)|raw }}"{% endif %}>
{% set profile_field_value = (data.format is defined ? data.format : '%s')|format(data.value) %}
@ -77,7 +90,8 @@
{{ profile_field_value }}
{% endif %}
</div>
</div>
{% endif %}
</label>
{% endfor %}
</div>
</div>
@ -86,18 +100,31 @@
<div class="profile__container__main">
{% if profile.user_about_content|length > 0 %}
{% if is_editing ? perms.edit_about : profile.user_about_content|length > 0 %}
<div class="container container--new profile__about" id="about">
<div class="container__title profile__about__title">
About {{ profile.username }}
</div>
<div class="container__content profile__about__content">
<div class="profile__about__content{% if is_editing %} profile__about__content--edit{% endif %}">
{% if is_editing %}
<select name="about[parser]" class="input__select input__select--new profile__about__select">
{% for parser, name in constant('MSZ_PARSERS_NAMES') %}
<option value="{{ parser }}"{% if parser == profile.user_about_parser %} selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
<textarea name="about[text]" class="input__textarea input__textarea--new profile__about__editor" id="about-textarea">{{ profile.user_about_content|escape }}</textarea>
{% else %}
{{ profile.user_about_content|escape|parse_text(profile.user_about_parser)|raw }}
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
{% if is_editing %}
</form>
{% else %}
</div>
{% endif %}
{% endblock %}