fixes #2354 add profile standard page

This commit is contained in:
Linty
2025-05-05 21:40:59 +02:00
parent 7f850c2938
commit 9bcc2cfa02
10 changed files with 569 additions and 13 deletions

View File

@@ -187,7 +187,7 @@ class Template
// standard pages can't get the header to load the html header
if (
'default' != $theme
and in_array(script_basename(), array('identification', 'register', 'password'))
and in_array(script_basename(), array('identification', 'register', 'password', 'profile'))
and (($themeconf['use_standard_pages'] ?? false) or conf_get_param('use_standard_pages', false))
)
{

View File

@@ -471,3 +471,9 @@ $lang['Confirm my new password'] = 'Confirm my new password';
$lang['Confirm Password'] = 'Confirm Password';
$lang['Confirm new password'] = 'Confirm new password';
$lang['Set my password'] = 'Set my password';
$lang['Account'] = 'Account';
$lang['Manage your account'] = 'Manage your account';
$lang['Choose how you want to see your gallery'] = 'Choose how you want to see your gallery';
$lang['Change your password'] = 'Change your password';
$lang['Options'] = 'Options';
$lang['Your changes have been applied.'] = 'Your changes have been applied.';

View File

@@ -470,3 +470,9 @@ $lang['Confirm new password'] = 'Confirmer le nouveau mot de passe';
$lang['Set my password'] = 'Définir mon mot de passe';
$lang['Your password was successfully set'] = 'Votre mot de passe a été défini avec succès';
$lang['Your password was successfully reset'] = 'Votre mot de passe a été réinitialisé avec succès';
$lang['Account'] = 'Mon compte';
$lang['Manage your account'] = 'Gérer votre compte';
$lang['Choose how you want to see your gallery'] = 'Choisissez comment vous voulez voir votre galerie';
$lang['Change your password'] = 'Changez votre mot de passe';
$lang['Options'] = 'Options';
$lang['Your changes have been applied.'] = 'Vos changements ont été pris en compte.';

View File

@@ -30,22 +30,24 @@ if (!defined('PHPWG_ROOT_PATH'))
trigger_notify('loc_begin_profile');
// Reset to default (Guest) custom settings
if (isset($_POST['reset_to_default']))
{
$fields = array(
'nb_image_page', 'expand',
'show_nb_comments', 'show_nb_hits', 'recent_period', 'show_nb_hits'
);
$fields = array(
'nb_image_page', 'expand',
'show_nb_comments', 'show_nb_hits', 'recent_period', 'show_nb_hits'
);
// Get the Guest custom settings
$query = '
// Get the Guest custom settings
$query = '
SELECT '.implode(',', $fields).'
FROM '.USER_INFOS_TABLE.'
WHERE user_id = '.$conf['default_user_id'].'
;';
$result = pwg_query($query);
$default_user = pwg_db_fetch_assoc($result);
$result = pwg_query($query);
$default_user = pwg_db_fetch_assoc($result);
$template->assign('DEFAULT_USER_VALUES', $default_user);
// Reset to default (Guest) custom settings
if (isset($_POST['reset_to_default']))
{
$userdata = array_merge($userdata, $default_user);
}
@@ -68,10 +70,49 @@ SELECT '.implode(',', $fields).'
$themeconf = $template->get_template_vars('themeconf');
if (!isset($themeconf['hide_menu_on']) OR !in_array('theProfilePage', $themeconf['hide_menu_on']))
{
include( PHPWG_ROOT_PATH.'include/menubar.inc.php');
if ($themeconf['id'] !== 'standard_pages')
{
include( PHPWG_ROOT_PATH.'include/menubar.inc.php');
}
}
include(PHPWG_ROOT_PATH.'include/page_header.php');
//Load language if cookie is set from login/register/password pages
if (isset($_COOKIE['lang']) and $user['language'] != $_COOKIE['lang'])
{
if (!array_key_exists($_COOKIE['lang'], get_languages()))
{
fatal_error('[Hacking attempt] the input parameter "'.$_COOKIE['lang'].'" is not valid');
}
$user['language'] = $_COOKIE['lang'];
load_language('common.lang', '', array('language'=>$user['language']));
}
//Get list of languages
foreach (get_languages() as $language_code => $language_name)
{
$language_options[$language_code] = $language_name;
}
$template->assign(array(
'language_options' => $language_options,
'current_language' => $user['language']
));
//Get link to doc
if ('fr' == substr($user['language'], 0, 2))
{
$help_link = "https://doc-fr.piwigo.org/les-utilisateurs/se-connecter-a-piwigo";
}
else
{
$help_link = "https://doc.piwigo.org/managing-users/log-in-to-piwigo";
}
$template->assign('HELP_LINK', $help_link);
trigger_notify('loc_end_profile');
flush_page_messages();
$template->pparse('profile');

View File

@@ -84,6 +84,17 @@
</fieldset>
{/if}
{if isset($PLUGINS_PROFILE)}
{foreach from=$PLUGINS_PROFILE item=plugin_block}
<fieldset>
<legend>{$plugin_block.name}</legend>
<div class="plugins fields">
{include file=$plugin_block.template}
</div>
</fieldset>
{/foreach}
{/if}
<p class="bottomButtons">
<input type="hidden" name="pwg_token" value="{$PWG_TOKEN}">
<input class="submit" type="submit" name="validate" value="{'Submit'|@translate}">

View File

@@ -0,0 +1,156 @@
let PWG_TOKEN;
$(function() {
PWG_TOKEN = $('#pwg_token').val();
$('.profile-section .display-btn').on('click', function () {
const display = $(this).data('display');
const selector = $(`#${display}`);
const element = selector.get(0);
if (selector.hasClass('open')) {
// close
element.style.maxHeight = element.scrollHeight + 'px';
void element.offsetHeight;
element.style.maxHeight = '0px';
selector.removeClass('open');
$(this).addClass('close');
} else {
// open
selector.addClass('open');
element.style.maxHeight = element.scrollHeight + 'px';
$(this).removeClass('close');
if ('account-display' !== display) {
setTimeout(() => {
const el = $(`#${display.split('-')[0]}-section`).get(0);
el.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}, 200);
}
}
});
$('#account-section .display-btn').trigger('click');
$('#save_account').on('click', function() {
const mail = $('#email').val();
if (!mail || mail == '') {
$('#email_error').show();
return;
}
setInfos({ email: mail });
});
$('#save_preferences').on('click', function() {
const values = {
nb_image_page: $('#nb_image_page').val(),
theme: $('select[name="theme"]').val(),
language: $('select[name="language"]').val(),
recent_period: $('#recent_period').val(),
expand: $('#opt_album').is(':checked'),
show_nb_comments: $('#opt_comment').is(':checked'),
show_nb_hits: $('#opt_hits').is(':checked')
}
if (values.nb_image_page == '') {
$('#error_nb_image').show();
return;
}
if (values.recent_period == '') {
$('#error_period').show();
return;
}
setInfos({...values});
});
$('#save_password').on('click', function() {
const passwords = {
password: $('#password').val(),
new_password: $('#password_new').val(),
conf_new_password: $('#password_conf').val(),
}
if (passwords.password == '' || passwords.new_password == '' || passwords.conf_new_password == '') {
$('#password-section input').each((i, element) => {
const el = $(element);
if (el.val() == '') {
el.parent().siblings().show();
}
});
return;
}
setInfos({...passwords});
$('#password-section input').val('');
});
standardSaveSelector.forEach((selector, i) => {
// console.log(i, selector);
$(selector).on('click', function() {
const values = {};
$(`#${i}-section`).find('input, textarea, select').each((i, element) => {
const el = $(element);
const inputName = el.attr('name');
const inputValue = el.val();
values[inputName] = inputValue;
});
setInfos({...values});
});
});
const userDefaultValues = {
nb_image_page: $('input[name="nb_image_page"]').val(),
theme: $('select[name="theme"]').val(),
language: $('select[name="language"]').val(),
recent_period: $('input[name="recent_period"]').val(),
opt_album: $('#opt_album').is(':checked'),
opt_comment: $('#opt_comment').is(':checked'),
opt_hits: $('#opt_hits').is(':checked'),
}
$('#reset_preferences').on('click', function() {
$('input[name="nb_image_page"]').val(userDefaultValues.nb_image_page);
$('select[name="theme"]').val(userDefaultValues.theme);
$('select[name="language"]').val(userDefaultValues.language);
$('input[name="recent_period"]').val(userDefaultValues.recent_period);
$('#opt_album').prop('checked', userDefaultValues.opt_album);
$('#opt_comment').prop('checked', userDefaultValues.opt_comment);
$('#opt_hits').prop('checked', userDefaultValues.opt_hits);
});
$('#default_preferences').on('click', function() {
$('input[name="nb_image_page"]').val(preferencesDefaultValues.nb_image_page);
$('input[name="recent_period"]').val(preferencesDefaultValues.recent_period);
$('#opt_album').prop('checked', preferencesDefaultValues.opt_album);
$('#opt_comment').prop('checked', preferencesDefaultValues.opt_comment);
$('#opt_hits').prop('checked', preferencesDefaultValues.opt_hits);
});
});
function setInfos(params, method='pwg.users.setMyInfo') {
// for debug
// console.log('setInfos', params);
const all_params = {
...params,
pwg_token: PWG_TOKEN
}
$.ajax({
url: `ws.php?format=json&method=${method}`,
type: "POST",
dataType: "json",
data: all_params,
success: (data) => {
if (data.stat == 'ok') {
pwgToaster({ text: data.result, icon: 'success' });
} else if (data.stat == 'fail') {
pwgToaster({ text: data.message, icon: 'error' });
} else {
pwgToaster({ text: 'Error try later...', icon: 'error' });
}
},
error: function (e) {
pwgToaster({ text: e.responseJSON?.message ?? 'Server Internal Error try later...', icon: 'error' });
},
});
}

View File

@@ -0,0 +1,32 @@
function pwgToaster(info) {
if (!info.text || !info.icon) {
console.log('set info.text or info.icon');
return;
}
if (typeof info.text !== 'string') {
console.log('info.text is not a string');
return;
}
if (info.icon !== 'success' && info.icon !== 'error') {
console.log('info.icon must be success or error');
return;
}
const template = $('#toast_template').clone();
template.find('.toast_text').html(info.text);
template.find('.toast_icon').addClass(info.icon === 'success' ? 'icon-ok' : 'icon-cancel');
template.addClass(info.icon === 'success' ? info.icon : 'error');
template.removeClass('template');
template.appendTo('#pwg_toaster');
const time = info.time ?? 3600;
setInterval(() => {
template.fadeOut(() => {
template.remove();
})
}, time);
}

View File

@@ -0,0 +1,255 @@
{combine_css id='standard_pages_css' path="themes/standard_pages/css/standard_pages.css" order=100}
{combine_css path="themes/default/vendor/fontello/css/gallery-icon.css" order=-10}
{combine_css path="admin/themes/default/fontello/css/fontello.css" order=-11}
<script>
var selected_language = `{$language_options[$current_language]}`;
var url_logo_light = `{$ROOT_URL}themes/standard_pages/images/piwigo_logo.svg`;
var url_logo_dark = `{$ROOT_URL}themes/standard_pages/images/piwigo_logo_dark.svg`;
</script>
{combine_script id='standard_pages_js' load='async' require='jquery' path='themes/standard_pages/js/standard_pages.js'}
{combine_script id='standard_profile_js' load='async' require='jquery' path='themes/standard_pages/js/profile.js'}
{footer_script}
const standardSaveSelector = [];
const preferencesDefaultValues = {
nb_image_page: {$DEFAULT_USER_VALUES['nb_image_page']},
recent_period: {$DEFAULT_USER_VALUES['recent_period']},
opt_album: {$DEFAULT_USER_VALUES['expand']},
opt_comment: {$DEFAULT_USER_VALUES['show_nb_comments']},
opt_hits: {$DEFAULT_USER_VALUES['show_nb_hits']},
};
{/footer_script}
<container id="mode" class="light">
<section id="header-options">
<div>
<i class="gallery-icon-moon toggle-mode" id="toggle_mode_light" onclick="toggle_mode('dark')"></i>
<i class="gallery-icon-sun toggle-mode" id="toggle_mode_dark" onclick="toggle_mode('light')"></i>
</div>
<div>
<a href="{$HELP_LINK}" target="_blank">{'Help'|translate}</a>
{include file='toaster.tpl'}
</div>
</section>
<section id="logo-section">
<img id="piwigo-logo" src="{$ROOT_URL}themes/standard_pages/images/piwigo_logo.svg">
</section>
<a href="{$U_HOME}" id="return-to-gallery"><i class="gallery-icon-left"></i> {'Return to the gallery'|translate}</a>
{* ACCOUNT *}
<section id="account-section" class="profile-section">
<div class="title">
<div class="column-flex">
<h1>{'Account'|translate}</h1>
<p>{'Manage your account'|translate}</p>
</div>
<i class="gallery-icon-up-open display-btn close" data-display="account-display"></i>
</div>
<div class="form" id="account-display">
<div class="column-flex first">
<label for="username">{'Username'|translate}</label>
<div class="row-flex input-container username">
<i class="gallery-icon-user"></i>
<p>{$USERNAME}</p>
<input id="pwg_token" type="hidden" value="{$PWG_TOKEN}" />
</div>
</div>
<div class="column-flex">
<label for="mail_address">{'Email address'|translate}</label>
<div class="row-flex input-container">
<i class="gallery-icon-user"></i>
<input type="email" name="mail_address" id="email" value="{$EMAIL}" />
</div>
<p id="email_error" class="error-message"><i class="gallery-icon-attention-circled"></i>
{'must not be empty'|translate}</p>
</div>
<div class="save">
<button class="btn btn-main" id="save_account">{'Submit'|translate}</button>
</div>
</div>
</section>
{* PREFERENCES *}
{if $ALLOW_USER_CUSTOMIZATION}
<section id="preferences-section" class="profile-section">
<div class="title">
<div class="column-flex">
<h1>{'Preferences'|translate}</h1>
<p>{'Choose how you want to see your gallery'|translate}</p>
</div>
<i class="gallery-icon-up-open display-btn close" data-display="preferences-display"></i>
</div>
<div class="form" id="preferences-display">
<div class="column-flex first">
<label for="nb_image_page">{'Number of photos per page'|translate}</label>
<div class="row-flex input-container">
<i class="icon-picture"></i>
<input type="number" size="4" maxlength="3" name="nb_image_page" id="nb_image_page"
value="{$NB_IMAGE_PAGE}" />
</div>
<p id="error_nb_image" class="error-message"><i class="gallery-icon-attention-circled"></i>
{'must not be empty'|translate}</p>
</div>
<div class="column-flex">
<label for="theme">{'Theme'|translate}</label>
<div class="row-flex input-container">
<i class="icon-brush"></i>
{html_options name=theme options=$template_options selected=$template_selection}
</div>
<p class="error-message"><i class="gallery-icon-attention-circled"></i> {'must not be empty'|translate}</p>
</div>
<div class="column-flex">
<label for="language">{'Language'|translate}</label>
<div class="row-flex input-container">
<i class="icon-language"></i>
{html_options name=language options=$language_options selected=$language_selection}
</div>
<p class="error-message"><i class="gallery-icon-attention-circled"></i> {'must not be empty'|translate}</p>
</div>
<div class="column-flex">
<label for="recent_period">{'Recent period'|translate}</label>
<div class="row-flex input-container">
<i class="icon-calendar"></i>
<input type="number" size="3" maxlength="2" name="recent_period" id="recent_period"
value="{$RECENT_PERIOD}" />
</div>
<p id="error_period" class="error-message"><i class="gallery-icon-attention-circled"></i>
{'must not be empty'|translate}</p>
</div>
{* OPTIONS *}
<label class="options-title">{'Options'|translate}</label>
<div class="column-flex input-container preferences-options">
<div class="row-flex option">
<label class="switch">
<input type="checkbox" id="opt_album" {if "true" === $EXPAND}checked{/if}>
<span class="slider round"></span>
</label>
<p>{'Expand all albums'|@translate}</p>
</div>
{if $ACTIVATE_COMMENTS}
<div class="row-flex option">
<label class="switch">
<input type="checkbox" id="opt_comment" {if "true" === $NB_COMMENTS}checked{/if}>
<span class="slider round"></span>
</label>
<p>{'Show number of comments'|@translate}</p>
</div>
{/if}
<div class="row-flex option">
<label class="switch">
<input type="checkbox" id="opt_hits" {if "true" === $NB_HITS}checked{/if}>
<span class="slider round"></span>
</label>
<p>{'Show number of hits'|@translate}</p>
</div>
</div>
<div class="reset">
<button class="btn btn-main btn-secondary"
id="default_preferences">{'Reset to default values'|translate}</button>
<div class="save">
<button class="btn btn-main btn-secondary" id="reset_preferences">{'Reset'|translate}</button>
<button class="btn btn-main" id="save_preferences">{'Submit'|translate}</button>
</div>
</div>
</div>
</section>
{/if}
{* PASSWORD *}
{if not $SPECIAL_USER}
<section id="password-section" class="profile-section">
<div class="title">
<div class="column-flex">
<h1>{'Password'|translate}</h1>
<p>{'Change your password'|translate}</p>
</div>
<i class="gallery-icon-up-open display-btn close" data-display="password-display"></i>
</div>
<div class="form" id="password-display">
<div class="column-flex">
<label for="password">{'Password'|translate}</label>
<div class="row-flex input-container">
<i class="gallery-icon-lock"></i>
<input type="password" class="" name="password" id="password" size="25" />
<i class="gallery-icon-eye togglePassword"></i>
</div>
<p class="error-message"><i class="gallery-icon-attention-circled"></i> {'must not be empty'|translate}</p>
</div>
<div class="column-flex">
<label for="password_new">{'New password'|translate}</label>
<div class="row-flex input-container">
<i class="gallery-icon-lock"></i>
<input type="password" class="" name="new-password" id="password_new" size="25" />
<i class="gallery-icon-eye togglePassword"></i>
</div>
<p class="error-message"><i class="gallery-icon-attention-circled"></i> {'must not be empty'|translate}</p>
</div>
<div class="column-flex">
<label for="password_conf">{'Confirm my new password'|translate}</label>
<div class="row-flex input-container">
<i class="gallery-icon-lock"></i>
<input type="password" class="" name="password_conf" id="password_conf" size="25" />
<i class="gallery-icon-eye togglePassword"></i>
</div>
<p class="error-message"><i class="gallery-icon-attention-circled"></i> {'must not be empty'|translate}</p>
</div>
<div class="save">
<button class="btn btn-main" id="save_password">{'Submit'|translate}</button>
</div>
</div>
</section>
{/if}
{if isset($PLUGINS_PROFILE)}
{foreach from=$PLUGINS_PROFILE item=plugin_block key=k_block}
<section id="{$k_block}-section" class="profile-section">
<div class="title">
<div class="column-flex">
<h1>{$plugin_block.name}</h1>
<p>{$plugin_block.desc}</p>
</div>
<i class="gallery-icon-up-open display-btn close" data-display="{$k_block}-display"></i>
</div>
<div class="form plugins" id="{$k_block}-display">
{include file=$plugin_block.template}
{if $plugin_block.standard_show_save}
<div class="save">
<button class="btn btn-main" id="save_{$k_block}">{'Submit'|translate}</button>
</div>
{footer_script}
standardSaveSelector.push('#save_{$k_block}');
{/footer_script}
{/if}
</div>
</section>
{/foreach}
{/if}
{if count($language_options) > 1}
<section id="language-switch">
<div id="lang-select">
<span id="other-languages">
{foreach from=$language_options key=$code item=$lang}
<span id="lang={$code}" onclick="setCookie('lang','{$code}',30)">{$lang}</span>
{/foreach}
</span>
<div id="selected-language-container">
<i class="gallery-icon-left-chevron"></i><span
id="selected-language">{$language_options[$current_language]}</span>
</div>
</div>
</section>
{/if}
</container>

View File

@@ -0,0 +1,47 @@
{combine_script id='toaster_js' load='async' require='jquery' path='themes/standard_pages/js/toaster.js'}
{html_style}
.toast.template {
display: none;
}
.toaster {
position: absolute;
right: 15px;
max-width: 300px;
top: 40px;
display: flex;
flex-direction: column;
gap: 10px;
}
.toast {
display: flex;
gap: 5px;
padding: 10px;
border-radius: 5px;
align-items: center;
font-size: 15px;
width: fit-content;
align-self: flex-end;
}
.toast i:before {
font-size: 33px;
}
.toast.success {
background-color:#4CA530;
color:#D6FFCF;
}
.toast.error {
background-color:#BE4949;
color:#FFC8C8;
}
{/html_style}
<div class="toaster" id="pwg_toaster">
<div class="toast template" id="toast_template">
<i class="toast_icon"></i>
<p class="toast_text"></p>
</div>
</div>

View File

@@ -455,6 +455,8 @@ p.error-message{
.profile-section .username {
width: fit-content;
cursor: not-allowed;
background-color: transparent !important;
border: none !important;
}
.profile-section .input-container.radio {