diff --git a/admin/themes/default/js/user_list.js b/admin/themes/default/js/user_list.js
index fd71097c11..ce8f8932df 100644
--- a/admin/themes/default/js/user_list.js
+++ b/admin/themes/default/js/user_list.js
@@ -63,6 +63,7 @@ function open_user_list() {
function close_user_list() {
hide_temporary_messages();
+ $('#result_send_mail_copy_input').val('');
$("#UserList").fadeOut();
}
@@ -81,6 +82,15 @@ function isSelectionMode() {
return $("#toggleSelectionMode").is(":checked")
}
+function reset_password_modals() {
+ $('.user-property-password-change').hide();
+ $('.user-property-password-change-inputs').hide();
+ $('#edit_password_success_change').hide();
+ $('#edit_password_result_mail').hide();
+ $('#edit_password_result_mail_copy').hide();
+ $('.user-property-password-choice').show();
+}
+
$( document ).ready(function() {
$(".user-property-register").tipTip({
@@ -96,9 +106,10 @@ $( document ).ready(function() {
fadeOut: 200
});
$(".advanced-filter-level select option").eq(1).remove();
+
+ /* Edit Password */
$('.button-edit-password-icon').click(function () {
- $('.user-property-password-change-inputs, .edit-password-success').hide()
- $('.user-property-password-choice').show();
+ reset_password_modals();
$('.user-property-password-change').fadeIn();
})
@@ -107,6 +118,24 @@ $( document ).ready(function() {
reset_input_password();
})
+ $('.icon-show-password').on('click', function() {
+ const icon = $(this);
+ const closestInput = icon.siblings();
+ if (closestInput.attr('type') === 'password') {
+ closestInput.attr('type', 'text');
+ icon.removeClass('icon-eye').addClass('icon-eye-off');
+ } else {
+ closestInput.attr('type', 'password');
+ icon.removeClass('icon-eye-off').addClass('icon-eye');
+ }
+ });
+
+ // Password input onkeyup clear error
+ $('#edit_user_password, #edit_user_conf_password').on('keyup', function() {
+ hide_error_edit_user();
+ });
+
+ /* Edit Username */
$('.edit-username').click(function () {
$('.user-property-username-change').show();
})
@@ -382,25 +411,6 @@ $( document ).ready(function() {
});
});
- // Show / Hide password input
- $('.icon-show-password').on('click', function() {
- const icon = $(this);
- const closestInput = icon.siblings();
- if (closestInput.attr('type') === 'password') {
- closestInput.attr('type', 'text');
- icon.removeClass('icon-eye').addClass('icon-eye-off');
- } else {
- closestInput.attr('type', 'password');
- icon.removeClass('icon-eye-off').addClass('icon-eye');
- }
- });
-
- // Password input onchange clear error
- $('#edit_user_password, #edit_user_conf_password').on('keyup', function() {
- hide_error_edit_user();
- });
-
-
/* tabsheet pop in guest */
$('.guest-edit-user-tabsheet').off('click').on('click', function() {
const tabName = $(this).attr('id').split('_');
@@ -1026,9 +1036,9 @@ function editTabsBind () {
}
function check_tabs (title_tab_name_id) {
-
- if (plugins_load.length > 1) {
- const countMoresPlugins = plugins_load.length - 1;
+ if (plugins_load.length > 2) {
+ $('.edit-user-tab-title').css({gap : '0px', justifyContent: 'space-between'});
+ const countMoresPlugins = plugins_load.length - 2;
if (!document.getElementById('mores_plugins_expand')) {
$('.edit-user-tab-title').append(
' ' +
@@ -1044,7 +1054,7 @@ function check_tabs (title_tab_name_id) {
tabsheetTitle.css({'max-width': '100%'});
tabsheetTitle.appendTo(dropdown);
- if (1 == countMoresPlugins) {
+ if (1 === countMoresPlugins) {
tabsheetTitle.css('border-radius', '10px');
} else {
dropdown.children().css('border-radius', '0');
@@ -1124,13 +1134,12 @@ function plugin_add_tab_in_user_modal(tab_name, content_id, users_table=null, se
// DOM modification
const content = $('#' + content_id);
- // TODO Add a super div with the real width and height (for better css custom)
$('.edit-user-tab-title').append(
'
'+ tab_name +'
'
);
$('.edit-user-slides').append(
- '
'
+ '
'
);
content.appendTo('#tab_' + name);
@@ -1143,7 +1152,7 @@ function plugin_add_tab_in_user_modal(tab_name, content_id, users_table=null, se
plugins_load.push('name_tab_' + name);
check_tabs('name_tab_' + name);
- if (plugins_load.length <= 1) {
+ if (plugins_load.length <= 2) {
$('#name_tab_' + name).tipTip({
delay: 0,
fadeIn: 200,
@@ -1249,6 +1258,9 @@ function generate_user_list() {
$(".user-container").click(user_container_click);
}
+function copyToClipboard(toCopy) {
+ navigator.clipboard.writeText(toCopy);
+}
/*---------------------
Fill the pop-in values
---------------------*/
@@ -1315,6 +1327,7 @@ function fill_user_edit_summary(user_to_edit, pop_in, isGuest) {
if(!window.isSecureContext) {
$('#copy_password').hide();
$('.update-password-success').css('margin', '40px 0');
+ $('#result_send_mail_copy').hide();
}
}
@@ -1369,12 +1382,49 @@ function fill_user_edit_update(user_to_edit, pop_in) {
$('.user-property-password-choice').hide();
$('.user-property-password-change-inputs').fadeIn();
});
+ if (user_to_edit.email) {
+ pop_in.find('#send_password_link')
+ .removeClass('unavailable tiptip')
+ .attr('title', '')
+ .off('click')
+ .on('click', function () {
+ send_link_password(user_to_edit.email, user_to_edit.username, user_to_edit.id, true);
+ });
+ pop_in.find('#send_password_link').off('mouseenter mouseleave focus blur');
+ } else {
+ pop_in.find('#send_password_link')
+ .addClass('unavailable tiptip')
+ .off('click')
+ .attr('title', cannotSendMail)
+ .tipTip({
+ delay: 0,
+ fadeIn: 200,
+ fadeOut: 200,
+ edgeOffset: 3
+ });
+ }
+ pop_in.find('#copy_password_link').off('click').on('click', function() {
+ const inputValue = $('#result_send_mail_copy_input').val();
+ if (inputValue === '') {
+ send_link_password(user_to_edit.email, user_to_edit.username, user_to_edit.id, false);
+ } else {
+ if (window.isSecureContext && navigator.clipboard) {
+ copyToClipboard(inputValue);
+ };
+ }
+ $('.user-property-password-choice').hide();
+ $('#edit_password_result_mail_copy').fadeIn();
+ $('#close_password_mail_send_close').off('click').on('click', function() {
+ reset_password_modals();
+ });
+ $('#result_send_mail_copy_input').trigger('focus');
+ });
pop_in.find('.edit-password-validate').unbind("click").click(function() {
const errDiv = $('#UserList .EditUserErrors');
const inputPassword = $('#edit_user_password').val();
const inputConfirmPassword = $('#edit_user_conf_password').val();
- if (inputPassword == '' || inputConfirmPassword == '') {
+ if (inputPassword === '' || inputConfirmPassword === '') {
errDiv.html(missingField);
show_error_edit_user();
} else if (inputPassword !== inputConfirmPassword) {
@@ -1663,18 +1713,18 @@ function update_user_password() {
data = jQuery.parseJSON(raw_data);
if (data.stat == 'ok') {
$('.user-property-password-change-inputs').hide();
- $('.edit-password-success').fadeIn();
+ $('#edit_password_success_change').fadeIn();
if (window.isSecureContext && navigator.clipboard) {
$('#copy_password').on('click', async function() {
- navigator.clipboard.writeText(ajax_data['password']);
+ copyToClipboard(ajax_data['password'])
$('#password_msg_success').html(passwordCopied);
});
};
$('#close_password_success').on('click', function() {
$('.user-property-password-change').hide();
- $('.edit-password-success').hide();
+ $('#edit_password_success_change').hide();
$('.user-property-password-change-inputs').show();
reset_input_password();
});
@@ -1989,3 +2039,44 @@ function show_filter_infos(nb_filters) {
$(".filter-counter").css('display', 'none').html(0);
}
}
+
+function send_link_password(email, username, user_id, send_by_mail) {
+ $.ajax({
+ url: "ws.php?format=json",
+ dataType: "json",
+ data: {
+ method: 'pwg.users.generateResetPasswordLink',
+ user_id: user_id,
+ send_by_mail: send_by_mail,
+ pwg_token: pwg_token
+ },
+ success: function(response) {
+ if('ok' === response.stat) {
+ $('#result_send_mail_copy_input').val(response.result.generated_link);
+ if(send_by_mail) {
+ if(response.result.send_by_mail) {
+ $('#result_send_mail').removeClass('update-password-fail icon-red').addClass('update-password-success icon-green');
+ $('#icon_password_msg_result_mail').removeClass('icon-cancel').addClass('icon-ok');
+ $('#password_msg_result_mail').html(sprintf(mailSentAt, username, email));
+ } else {
+ $('#result_send_mail').removeClass('update-password-success icon-green').addClass('update-password-fail icon-red');
+ $('#icon_password_msg_result_mail').removeClass('icon-ok').addClass('icon-cancel');
+ $('#password_msg_result_mail').html(errorMailSent);
+ }
+ $('.user-property-password-choice').hide();
+ $('#edit_password_result_mail').fadeIn();
+ $('#close_password_mail_close').off('click').on('click', function() {
+ reset_password_modals();
+ });
+ } else {
+ if (window.isSecureContext && navigator.clipboard) {
+ copyToClipboard(response.result.generated_link);
+ };
+ }
+ }
+ },
+ error: function(err) {
+ console.log(err);
+ },
+ });
+}
diff --git a/admin/themes/default/template/user_list.tpl b/admin/themes/default/template/user_list.tpl
index 49008e6279..fc28115a7e 100644
--- a/admin/themes/default/template/user_list.tpl
+++ b/admin/themes/default/template/user_list.tpl
@@ -31,6 +31,9 @@ const missingField = "{'Please complete all fields'|@translate|escape:javascript
const passwordUpdated = "{'Password updated'|@translate|escape:javascript}";
const passwordCopied = "{'Password copied'|@translate|escape:javascript}";
const copyPassword = "{'Copy password'|@translate|escape:javascript}";
+const mailSentAt = "{'Mail sent to %s [%s].'|@translate|escape:javascript}";
+const errorMailSent = "{'Error sending email'|@translate|escape:javascript}";
+const cannotSendMail = "{'Cannot send an email to this user because he doesn\'t have an email address'|@translate|escape:javascript}"
const registered_str = '{"Registered"|@translate|escape:javascript}';
const last_visit_str = '{"Last visit"|@translate|escape:javascript}';
@@ -671,7 +674,7 @@ $(document).ready(function() {
{'Properties'|@translate}
{'Preferences'|@translate}
-
{'Notifications'|@translate}
+ {*
{'Notifications'|@translate}
*}
@@ -720,7 +723,7 @@ $(document).ready(function() {
placeholder="{'Select groups or type them'|translate}" name="group_id[]" multiple
style="box-sizing:border-box;">
- {'Some of these groups give access to notifications. To find out more, go to the Notifications tab.'|@translate}
+ {* {'Some of these groups give access to notifications. To find out more, go to the Notifications tab.'|@translate} *}
@@ -787,9 +790,9 @@ $(document).ready(function() {
- *}
@@ -834,10 +837,12 @@ $(document).ready(function() {
-
{'Resend password link'|@translate}
-
{'or'|@translate}
+
+
{'Copy the password link'|@translate}
+
{'Resend password link'|@translate}
{'Change password'|@translate}
-
{'Cancel'|@translate}
+
+
{'Cancel'|@translate}
@@ -864,13 +869,33 @@ $(document).ready(function() {
-
+
{'Password updated'|@translate}
{'Copy password'|@translate}
{'Ok'|@translate}
+
+
+
+
+ text
+
+
{'Ok'|@translate}
+
+
+
+
+
+
+
+
+
+ {'Copied link'|@translate}
+
+
{'Ok'|@translate}
+
@@ -1538,7 +1563,8 @@ $(document).ready(function() {
.edit-user-tab-title {
display: flex;
- justify-content: space-between;
+ /* justify-content: space-between; */
+ gap: 30px;
font-weight: bold;
font-size: 14px;
}
@@ -1802,6 +1828,9 @@ $(document).ready(function() {
font-weight:bold;
margin-bottom:45px;
height:30px;
+ display: flex;
+ gap: 5px;
+ max-width: 250px;
}
.user-property-username-change {
@@ -1879,17 +1908,15 @@ $(document).ready(function() {
}
.user-property-password-choice .head-button-2 {
- gap: 5px;
- justify-content: center;
-}
-
-.user-property-password-choice .or {
+ display: block;
text-align: center;
- font-weight: 700;
+ margin-right: 0;
+ margin-bottom: 20px;
}
.user-property-password-choice .edit-password-cancel {
text-align: center;
+ margin: 0;
}
.edit-password-success,
@@ -1902,12 +1929,52 @@ $(document).ready(function() {
padding-top: 30px;
}
-.update-password-success {
+.edit-password-success .edit-password-success-input {
+ background-color: transparent;
+ font-size: 15px;
+ border: none;
+ flex: auto;
+ padding: 5px;
+}
+
+.edit-password-success .edit-password-success-reset-link {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ margin: 10px 0;
+ background-color: #F3F3F3;
+}
+
+.edit-password-success .edit-password-success-reset-link span {
+ padding-right: 5px;
+}
+
+#result_send_mail_copy {
+ margin-bottom: 0px;
+}
+
+#edit_password_result_mail_copy {
+ padding-top: 0px;
+}
+
+.update-password-success,
+.update-password-fail {
padding: 10px;
font-weight: bold;
margin-bottom: 20px;
}
+#result_send_mail {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ margin-bottom: 10px;
+}
+
+#edit_password_result_mail {
+ padding-top: 20px;
+}
+
.update-username-success {
display: flex;
padding: 10px;
@@ -1987,6 +2054,9 @@ $(document).ready(function() {
.edit-username-title {
font-size:1.4em;
+ width: auto;
+ overflow-x: hidden;
+ text-overflow: ellipsis;
}
.edit-username-specifier {
@@ -2157,6 +2227,20 @@ $(document).ready(function() {
font-weight:normal;
}
+/* plugins tabsheet */
+.edit-user-tabsheet-plugins {
+ width: 435px !important;
+ height: 480px !important;
+ margin-right: 15px !important;
+ margin-top: 29px !important;
+}
+/* for firefox */
+@-moz-document url-prefix() {
+ .edit-user-tabsheet-plugins {
+ margin-top: 25px !important;
+ }
+}
+
/* update */
.update-user-button {
diff --git a/include/functions_mail.inc.php b/include/functions_mail.inc.php
index fe5859ebb2..edd7bda957 100644
--- a/include/functions_mail.inc.php
+++ b/include/functions_mail.inc.php
@@ -1004,6 +1004,43 @@ function pwg_send_mail_test($success, $mail, $args)
}
}
+/**
+ * Generate content mail for reset password
+ *
+ * Return the content mail to send
+ * @since 15
+ * @param string $username
+ * @param string $reset_password_link
+ * @param string $gallery_title
+ * @return string mail content
+ */
+function pwg_generate_reset_password_mail($username, $reset_password_link, $gallery_title)
+{
+ set_make_full_url();
+
+ $message = l10n('Someone requested that the password be reset for the following user account:') . "\r\n\r\n";
+ $message.= l10n(
+ 'Username "%s" on gallery %s',
+ $username,
+ get_gallery_home_url()
+ );
+ $message.= "\r\n\r\n";
+ $message.= l10n('To reset your password, visit the following address:') . "\r\n";
+ $message.= $reset_password_link;
+ $message.= "\r\n\r\n";
+ $message.= l10n('If this was a mistake, just ignore this email and nothing will happen.')."\r\n";
+
+ unset_make_full_url();
+
+ $message = trigger_change('render_lost_password_mail_content', $message);
+
+ return array(
+ 'subject' => '['.$gallery_title.'] '.l10n('Password Reset'),
+ 'content' => $message,
+ 'email_format' => 'text/plain',
+ );
+}
+
trigger_notify('functions_mail_included');
?>
diff --git a/include/functions_user.inc.php b/include/functions_user.inc.php
index 5fab17c57b..d4f1bf9eeb 100644
--- a/include/functions_user.inc.php
+++ b/include/functions_user.inc.php
@@ -1733,6 +1733,41 @@ function deactivate_password_reset_key($user_id)
);
}
+/**
+ * Generate reset password link
+ *
+ * @since 15
+ * @param int $user_id
+ * @param string $user_email
+ * @return array activation_key and reset password link
+ */
+function generate_reset_password_link($user_id)
+{
+ $activation_key = generate_key(20);
+
+ list($expire) = pwg_db_fetch_row(pwg_query('SELECT ADDDATE(NOW(), INTERVAL 1 HOUR)'));
+
+ single_update(
+ USER_INFOS_TABLE,
+ array(
+ 'activation_key' => pwg_password_hash($activation_key),
+ 'activation_key_expire' => $expire,
+ ),
+ array('user_id' => $user_id)
+ );
+
+ set_make_full_url();
+
+ $reset_password_link = get_root_url().'password.php?key='.$activation_key;
+
+ unset_make_full_url();
+
+ return array(
+ 'activation_key' => $activation_key,
+ 'reset_password_link' => $reset_password_link,
+ );
+}
+
/**
* Gets the last visit (datetime) of a user, based on history table
*
diff --git a/include/ws_functions/pwg.users.php b/include/ws_functions/pwg.users.php
index f1afdabf03..32fb098c21 100644
--- a/include/ws_functions/pwg.users.php
+++ b/include/ws_functions/pwg.users.php
@@ -961,4 +961,67 @@ function ws_users_favorites_getList($params, &$service)
);
}
+/**
+ * API method
+ * Returns the reset password link of the current user
+ * @since 15
+ * @param mixed[] $params
+ * @option int user_id
+ * @option string pwg_token
+ * @option boolean send_by_mail
+ */
+function ws_users_generate_reset_password_link($params, &$service)
+{
+ global $user, $conf;
+ include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
+ include_once(PHPWG_ROOT_PATH.'include/functions_mail.inc.php');
+
+ if (get_pwg_token() != $params['pwg_token'])
+ {
+ return new PwgError(403, 'Invalid security token');
+ }
+
+ // check if user exist
+ if (get_username($params['user_id']) === false)
+ {
+ return new PwgError(WS_ERR_INVALID_PARAM, 'This user does not exist.');
+ }
+
+ $user_lost = getuserdata($params['user_id']);
+
+ // Cannot perform this action for a guest or generic user
+ if (is_a_guest($user_lost['status']) or is_generic($user_lost['status']))
+ {
+ return new PwgError(403, 'Password reset is not allowed for this user');
+ }
+
+ // Only webmaster can perform this action for another webmaster
+ if ('admin' === $user['status'] && 'webmaster' === $user_lost['status'])
+ {
+ return new PwgError(403, 'You cannot perform this action');
+ }
+
+ $generate_link = generate_reset_password_link($params['user_id']);
+ $send_by_mail_response = null;
+
+ if ($params['send_by_mail'] and !empty($user_lost['email']))
+ {
+ $email_params = pwg_generate_reset_password_mail($user_lost['username'], $generate_link['reset_password_link'], $conf['gallery_title']);
+ // Here we remove the display of errors because they prevent the response from being parsed
+ if (@pwg_mail($user_lost['email'], $email_params))
+ {
+ $send_by_mail_response = 'Mail sent at : ' . $user_lost['email'];
+ }
+ else
+ {
+ $send_by_mail_response = false;
+ }
+ }
+
+
+ return array(
+ 'generated_link' => $generate_link['reset_password_link'],
+ 'send_by_mail' => $send_by_mail_response,
+ );
+}
?>
diff --git a/password.php b/password.php
index 286f0a8d7f..ee9262adc1 100644
--- a/password.php
+++ b/password.php
@@ -76,44 +76,11 @@ function process_password_request()
return false;
}
- $activation_key = generate_key(20);
-
- list($expire) = pwg_db_fetch_row(pwg_query('SELECT ADDDATE(NOW(), INTERVAL 1 HOUR)'));
-
- single_update(
- USER_INFOS_TABLE,
- array(
- 'activation_key' => pwg_password_hash($activation_key),
- 'activation_key_expire' => $expire,
- ),
- array('user_id' => $user_id)
- );
+ $generate_link = generate_reset_password_link($user_id);
- $userdata['activation_key'] = $activation_key;
-
- set_make_full_url();
-
- $message = l10n('Someone requested that the password be reset for the following user account:') . "\r\n\r\n";
- $message.= l10n(
- 'Username "%s" on gallery %s',
- $userdata['username'],
- get_gallery_home_url()
- );
- $message.= "\r\n\r\n";
- $message.= l10n('To reset your password, visit the following address:') . "\r\n";
- $message.= get_root_url().'password.php?key='.$activation_key.'-'.urlencode($userdata['email']);
- $message.= "\r\n\r\n";
- $message.= l10n('If this was a mistake, just ignore this email and nothing will happen.')."\r\n";
+ $userdata['activation_key'] = $generate_link['activation_key'];
- unset_make_full_url();
-
- $message = trigger_change('render_lost_password_mail_content', $message);
-
- $email_params = array(
- 'subject' => '['.$conf['gallery_title'].'] '.l10n('Password Reset'),
- 'content' => $message,
- 'email_format' => 'text/plain',
- );
+ $email_params = pwg_generate_reset_password_mail($userdata['username'], $generate_link['reset_password_link'], $conf['gallery_title']);
if (pwg_mail($userdata['email'], $email_params))
{
@@ -137,54 +104,27 @@ function check_password_reset_key($reset_key)
{
global $page, $conf;
- list($key, $email) = explode('-', $reset_key, 2);
-
+ $key = $reset_key;
if (!preg_match('/^[a-z0-9]{20}$/i', $key))
{
$page['errors'][] = l10n('Invalid key');
return false;
}
- $user_ids = array();
-
- $query = '
-SELECT
- '.$conf['user_fields']['id'].' AS id
- FROM '.USERS_TABLE.'
- WHERE '.$conf['user_fields']['email'].' = \''.pwg_db_real_escape_string($email).'\'
-;';
- $user_ids = query2array($query, null, 'id');
-
- if (count($user_ids) == 0)
- {
- $page['errors'][] = l10n('Invalid username or email');
- return false;
- }
-
- $user_id = null;
-
$query = '
SELECT
user_id,
status,
- activation_key,
- activation_key_expire,
- NOW() AS dbnow
+ activation_key
FROM '.USER_INFOS_TABLE.'
- WHERE user_id IN ('.implode(',', $user_ids).')
+ WHERE activation_key IS NOT NULL
+ AND activation_key_expire > NOW()
;';
$result = pwg_query($query);
while ($row = pwg_db_fetch_assoc($result))
{
if (pwg_password_verify($key, $row['activation_key']))
{
- if (strtotime($row['dbnow']) > strtotime($row['activation_key_expire']))
- {
- // key has expired
- $page['errors'][] = l10n('Invalid key');
- return false;
- }
-
if (is_a_guest($row['status']) or is_generic($row['status']))
{
$page['errors'][] = l10n('Password reset is not allowed for this user');
@@ -192,6 +132,7 @@ function check_password_reset_key($reset_key)
}
$user_id = $row['user_id'];
+ break;
}
}
diff --git a/ws.php b/ws.php
index 83d86f80bd..b8ab47c653 100644
--- a/ws.php
+++ b/ws.php
@@ -1453,6 +1453,26 @@ function ws_addDefaultMethods( $arr )
'',
$ws_functions_root . 'pwg.images.php'
);
+
+ $service->addMethod(
+ 'pwg.users.generateResetPasswordLink',
+ 'ws_users_generate_reset_password_link',
+ array(
+ 'user_id' => array(
+ 'type'=>WS_TYPE_ID
+ ),
+ 'pwg_token' => array(),
+ 'send_by_mail' => array(
+ 'flags' => WS_PARAM_OPTIONAL,
+ 'type' => WS_TYPE_BOOL,
+ 'default' => false,
+ ),
+ ),
+ 'Return the reset password link
+ (Only webmaster can perform this action for another webmaster)',
+ $ws_functions_root . 'pwg.users.php',
+ array('admin_only'=>true)
+ );
}
?>