From 9a293dc1c57ce4d8b899de8a362d9363ccd9b92d Mon Sep 17 00:00:00 2001 From: Zacharie Date: Fri, 15 May 2020 12:37:01 +0200 Subject: [PATCH] Issue #1167 : Add pwg.groups.duplicate to API methods, add the merge action in group manager (and small code fixes) --- admin/themes/default/js/group_list.js | 207 ++++++++++++++----- admin/themes/default/template/group_list.tpl | 5 +- admin/themes/default/theme.css | 6 +- include/ws_functions/pwg.groups.php | 91 ++++++++ ws.php | 15 +- 5 files changed, 267 insertions(+), 57 deletions(-) diff --git a/admin/themes/default/js/group_list.js b/admin/themes/default/js/group_list.js index c1ed58a40..d455c42c4 100644 --- a/admin/themes/default/js/group_list.js +++ b/admin/themes/default/js/group_list.js @@ -1,7 +1,7 @@ const DELAY_FEEDBACK = 3000; /*------- -Group Popup +Group Popin -------*/ $(".group_details_popup_trigger").click(function () { @@ -84,31 +84,8 @@ jQuery(document).ready(function () { data = jQuery.parseJSON(raw_data); if (data.stat === "ok") { $(".addGroupFormLabelAndInput input").val(''); - id = data.result.groups[0].id; - //Setup the group - newgroup = $("#group-template").clone().attr("id", "group-" + id); - newgroup.css("order", -id); - newgroup.attr("data-id", id); - newgroup.find("#group_name").html(name); - newgroup.find(".group_name-editable").val(name); - newgroup.find(".Group-checkbox label").attr("for", "Group-Checkbox-selection-" + id); - newgroup.find(".Group-checkbox input").attr("id", "Group-Checkbox-selection-" + id); - newgroup.find(".input-edit-group-name").attr("placeholder", name); - newgroup.find(".group_number_users").html("0 " + str_member_default); - newgroup.find(".group_name-editable").html(name); - hideAddGroupForm(); - //Setup the icon color - var colors = [["#ffa744", "#ffe9cf"],["#896af3", "#e0daf4"], ["#6ece5e","#d6ffcf"],["#2883c3","#cfebff"]]; - var colorId = Number(id)%4; - newgroup.find(".icon-users-1").attr("style", "color:"+colors[colorId][0]+"; background-color:"+colors[colorId][1]); - - setupGroupBox(newgroup); - - //Place group in first Place - newgroup.appendTo(".groups"); - newgroup.find(".groupMessage").html(str_group_created); - newgroup.find(".groupMessage").fadeIn(); - newgroup.find(".groupMessage").delay(DELAY_FEEDBACK).fadeOut(); + group = data.result.groups[0]; + createGroup(group) } else { $("#addGroupForm .groupError").html(str_name_taken); $("#addGroupForm .groupError").fadeIn(); @@ -122,6 +99,33 @@ jQuery(document).ready(function () { }); }); +var createGroup = function(group) { + //Setup the group + newgroup = $("#group-template").clone().attr("id", "group-" + group.id); + newgroup.css("order", -group.id); + newgroup.attr("data-id", group.id); + newgroup.find("#group_name").html(group.name); + newgroup.find(".group_name-editable").val(group.name); + newgroup.find(".Group-checkbox label").attr("for", "Group-Checkbox-selection-" + group.id); + newgroup.find(".Group-checkbox input").attr("id", "Group-Checkbox-selection-" + group.id); + newgroup.find(".input-edit-group-name").attr("placeholder", group.name); + newgroup.find(".group_number_users").html(group.nb_users+" " + ((group.nb_users > 1)? str_members_default:str_member_default)); + newgroup.find(".group_name-editable").html(group.name); + hideAddGroupForm(); + //Setup the icon color + var colors = [["#ffa744", "#ffe9cf"],["#896af3", "#e0daf4"], ["#6ece5e","#d6ffcf"],["#2883c3","#cfebff"]]; + var colorId = Number(group.id)%4; + newgroup.find(".icon-users-1").attr("style", "color:"+colors[colorId][0]+"; background-color:"+colors[colorId][1]); + + setupGroupBox(newgroup); + + //Place group in first Place + newgroup.appendTo(".groups"); + newgroup.find(".groupMessage").html(str_group_created); + newgroup.find(".groupMessage").fadeIn(); + newgroup.find(".groupMessage").delay(DELAY_FEEDBACK).fadeOut(); +} + /*------- SETUP JS ON GROUP BOX -------*/ @@ -215,8 +219,15 @@ var setupGroupBox = function (groupBox) { /* Hide group options and rename field on click on the screen */ $(document).mouseup(function (e) { - if ($(e.target).closest("#group-"+id+" #GroupOptions").length === 0) { - groupBox.find(".group-dropdown-options #GroupOptions").hide(); + e.stopPropagation(); + let option_is_clicked = false + $("#GroupOptions div").each(function () { + if (!($(this).has(e.target).length === 0)) { + option_is_clicked = true; + } + }) + if (!option_is_clicked) { + groupBox.find("#GroupOptions").hide(); } }); @@ -229,6 +240,7 @@ var setupGroupBox = function (groupBox) { groupBox.find(".manage-users").on("click", function(){openUserManager(id)}); + groupBox.find("#GroupDuplicate").on("click", function(){duplicateAction(id)}) }; @@ -289,6 +301,7 @@ var deleteGroup = function (id) { if (data.stat === "ok") { $("#group-" + id).remove(); $(".DeleteGroupList div[data-id="+id+"]").remove() + $("#MergeOptionsChoices option[value="+ id +"]").remove() resolve(); } }, @@ -393,6 +406,47 @@ var setupDefaultActions = function(id, is_default) { } } +var duplicateAction = function(id) { + let loadState = new TemporaryState(); + loadState.changeHTML($("#group-" + id + " #GroupDuplicate"), " ") + loadState.removeClass($("#group-" + id + " #GroupDuplicate"), "icon-docs"); + loadState.changeAttribute($("#group-" + id + " #GroupDuplicate"), "style", "pointer-events: none; text-align: center;") + copy_name = $("#group-" + id + " #group_name").html() + str_copy; + + let name_exist = function(name) { + exist = false; + $(".Group-name-container p").each(function () { + if ($(this).html() === name) + exist = true + }) + return exist; + } + + let i = 1; + while (name_exist(copy_name)) + { + copy_name = $("#group-" + id + " #group_name").html() + str_other_copy.replace("%s", i++) + } + + jQuery.ajax({ + url: "ws.php?format=json&method=pwg.groups.duplicate", + type: "POST", + data: "group_id=" + id + "&pwg_token=" + pwg_token + "©_name=" + copy_name, + success: function (raw_data) { + data = jQuery.parseJSON(raw_data); + console.log(data); + loadState.reverse(); + if (data.stat === "ok") { + group = data.result.groups[0]; + createGroup(group); + } + }, + error: function (err) { + console.log(err); + }, + }); +} + /*------- Selection mode toggle actions, @@ -512,12 +566,18 @@ $('.ConfirmMergeButton').on("click", function() { loadState.removeClass($('.ConfirmMergeButton'), "icon-ok"); merge_group = []; str_merge_group = ""; + name_merge = []; + name_dest = []; dest_grp = $("#MergeOptionsChoices").val(); $(".DeleteGroupList div").each(function () { - if (dest_grp != $(this).attr("data-id")) { + if (dest_grp != $(this).attr("data-id")) + { str_merge_group += "&merge_group_id[]="+$(this).attr("data-id"); merge_group.push($(this).attr("data-id")); + name_merge.push($(this).find("p").html()) + } else { + name_dest = $(this).find("p").html(); } }) @@ -540,6 +600,23 @@ $('.ConfirmMergeButton').on("click", function() { $(".DeleteGroupList").html(""); $("#MergeOptionsChoices").html(""); + $.alert({ + title: str_merged_into + .replace("%s1",name_merge.toString()) + .replace("%s2",name_dest), + icon: 'icon-ok', + titleClass: "groupDeleteAlert", + theme:"modern", + closeIcon: true, + content: "", + animation: "zoom", + boxWidth: '20%', + useBootstrap: false, + backgroundDismiss: true, + animateFromElement: false, + typeAnimated: false, + }); + $("#group-"+dest_grp + " .group_number_users").html(" "); jQuery.ajax({ url: "ws.php?format=json&method=pwg.users.getList", @@ -564,11 +641,11 @@ $('.ConfirmMergeButton').on("click", function() { $('.ConfirmDeleteButton').on("click", function() { let names = []; - let promises = []; + let ids = []; $('.DeleteGroupList div').each(function () { let id = $(this).data('id'); names.push($("#group-"+id+" #group_name").html()); - promises.push(deleteGroup(id)); + ids.push(id); }); let loadState = new TemporaryState; @@ -576,25 +653,47 @@ $('.ConfirmDeleteButton').on("click", function() { loadState.changeHTML($('.ConfirmDeleteButton'), " "); loadState.removeClass($('.ConfirmDeleteButton'),"icon-ok"); - Promise.all(promises).then(() => { - loadState.reverse(); - updateSelectionPanel("NoSelection"); - $.alert({ - title: str_groups_deleted.replace("%s",names.toString()), - titleClass: "groupDeleteAlert", - theme: "modern", - icon: 'icon-ok', - closeIcon: true, - content: "", - animation: "zoom", - boxWidth: '20%', - useBootstrap: false, - backgroundDismiss: true, - animateFromElement: false, - typeAnimated: false, - backgroundDismiss: true, - }); + str_id = "" + ids.forEach(function(id) { + str_id += "group_id[]=" + id + "&" }) + + jQuery.ajax({ + url: "ws.php?format=json&method=pwg.groups.delete", + type: "POST", + data: str_id + "pwg_token=" + pwg_token, + success: function (raw_data) { + data = jQuery.parseJSON(raw_data); + if (data.stat === "ok") { + $(".DeleteGroupList div").each(function() { + $(this).remove(); + $("#group-" + $(this).attr("data-id")).remove(); + $("#MergeOptionsChoices option[value="+ $(this).attr("data-id") +"]").remove() + }) + + loadState.reverse(); + updateSelectionPanel("NoSelection"); + $.alert({ + title: str_groups_deleted.replace("%s",names.toString()), + titleClass: "groupDeleteAlert", + theme: "modern", + icon: 'icon-ok', + closeIcon: true, + content: "", + animation: "zoom", + boxWidth: '20%', + useBootstrap: false, + backgroundDismiss: true, + animateFromElement: false, + typeAnimated: false, + backgroundDismiss: true, + }); + } + }, + error: function (err) { + console.log(err); + }, + }); }); /*------- @@ -671,6 +770,7 @@ var openUserManager = function(grp_id) { loadState.reverse(); data = jQuery.parseJSON(raw_data); if (data.stat === "ok") { + //Set the popin name $(".group-name-block p").html( $("#group-" + grp_id + " #group_name").html() + " / " + str_user_list ) @@ -684,12 +784,12 @@ var openUserManager = function(grp_id) { // Sort in alphabetic order usersInGroup.sort(function( a, b ) { if ( a.username.toLowerCase() < b.username.toLowerCase() ){ - return 1; - } else return -1 + return -1; + } else return 1 }); let i = 0; while ($(".UsersInGroupList").outerHeight() <= maxOffsetUserCont && usersInGroup[i] != undefined){ - getUserDisplay(usersInGroup[i].username, usersInGroup[i].id, grp_id).prependTo(".UsersInGroupList"); + getUserDisplay(usersInGroup[i].username, usersInGroup[i].id, grp_id).appendTo(".UsersInGroupList"); i++; }; while ($(".UsersInGroupList").height() > maxOffsetUserCont) { @@ -806,7 +906,6 @@ $(".AddUserBlock button").on("click", function () { associateUserInfo.insertAfter(userBlock); associateUserInfo.find("p").html(str_user_associated); associateUserInfo.fadeIn() - associateUserInfo.delay(DELAY_FEEDBACK).fadeOut() updateUserSearch(); @@ -846,7 +945,7 @@ $(".input-user-name").on("input", function() { let i = 0; while ($(".UsersInGroupList").outerHeight() <= maxOffsetUserCont && usersInGroup[i] != undefined){ getUserDisplay(usersInGroup[i].username, usersInGroup[i].id, grp_id) - .prependTo(".UsersInGroupList"); + .appendTo(".UsersInGroupList"); i++; } } diff --git a/admin/themes/default/template/group_list.tpl b/admin/themes/default/template/group_list.tpl index 7e69cfc18..197ac8334 100644 --- a/admin/themes/default/template/group_list.tpl +++ b/admin/themes/default/template/group_list.tpl @@ -13,8 +13,11 @@ var str_delete = '{'Delete group "%s"?'|@translate}' var str_yes_delete_confirmation = "{'Yes, delete'|@translate}" var str_no_delete_confirmation = "{"No, I have changed my mind"|@translate}" var str_user_associated = "{"User associated"|@translate}" -var str_user_dissociated = '{'User "%s" Dissociated from this group'|@translate}' +var str_user_dissociated = '{'User "%s" dissociated from this group'|@translate}' var str_user_list = "{"User List"|@translate}" +var str_merged_into = '{'Group(s) \{%s1\} succesfully merged into "%s2"'|@translate}' +var str_copy = '{' (copy)'|@translate}' +var str_other_copy = '{' (copy %s)'|@translate}' var serverKey = '{$CACHE_KEYS.users}' var serverId = '{$CACHE_KEYS._hash}' diff --git a/admin/themes/default/theme.css b/admin/themes/default/theme.css index 48d269f3f..e014321d7 100644 --- a/admin/themes/default/theme.css +++ b/admin/themes/default/theme.css @@ -2050,6 +2050,10 @@ input:checked + .slider:before { line-height: 28px !important; } +.groupDeleteAlert { + margin-bottom: -2px !important; +} + .groupDeleteConfirm ~ .jconfirm-content-pane, .groupDeleteAlert ~ .jconfirm-content-pane { height: 0px !important; margin: 0px !important; @@ -2070,7 +2074,7 @@ input:checked + .slider:before { } .groupDeleteAlert .jconfirm-icon-c { - margin-bottom: 20px !important; + margin-bottom: 25px !important; } /*Group checkbox*/ diff --git a/include/ws_functions/pwg.groups.php b/include/ws_functions/pwg.groups.php index 5d6c7fabb..668385acc 100644 --- a/include/ws_functions/pwg.groups.php +++ b/include/ws_functions/pwg.groups.php @@ -316,6 +316,97 @@ SELECT user_id ); } +/** + * API method + * Create a copy of a group + * @param mixed[] $params + * @option int group_id + * @option string copy_name + */ +function ws_groups_duplicate($params, &$service) { + + if (get_pwg_token() != $params['pwg_token']) + { + return new PwgError(403, 'Invalid security token'); + } + + $query = ' +SELECT COUNT(*) + FROM `'.GROUPS_TABLE.'` + WHERE name = \''.$params['copy_name'].'\' +;'; + list($count) = pwg_db_fetch_row(pwg_query($query)); + if ($count != 0) + { + return new PwgError(WS_ERR_INVALID_PARAM, 'This name is already used by another group.'); + } + + $query = ' +SELECT COUNT(*) + FROM `'. GROUPS_TABLE .'` + WHERE id = '.$params["group_id"].' +;'; + list($count) = pwg_db_fetch_row(pwg_query($query)); + if ($count == 0) + { + return new PwgError(WS_ERR_INVALID_PARAM, 'This group does not exist.'); + } + + $query = ' +SELECT is_default + FROM `'. GROUPS_TABLE .'` + WHERE id = '.$params['group_id'].' +;'; + + $is_default = pwg_db_fetch_row(pwg_query($query))[0]; + + // creating the group + single_insert( + GROUPS_TABLE, + array( + 'name' => $params['copy_name'], + 'is_default' => boolean_to_string($is_default), + ) + ); + $inserted_id = pwg_db_insert_id(); + + pwg_activity('group', $inserted_id, 'add'); + + $query = ' + SELECT user_id + FROM `'. USER_GROUP_TABLE .'` + WHERE group_id = '.$params['group_id'].' + ;'; + + $users = query2array($query, null, 'user_id'); + + $inserts = array(); + foreach ($users as $user) + { + $inserts[] = array( + 'group_id' => $inserted_id, + 'user_id' => $user, + ); + } + + mass_inserts( + USER_GROUP_TABLE, + array('group_id', 'user_id'), + $inserts, + array('ignore'=>true) + ); + + include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); + invalidate_user_cache(); + + foreach ($users as $user_id) + { + pwg_activity('user', $user_id, 'edit', array("associated" => $params['group_id'])); + } + + return $service->invoke('pwg.groups.getList', array('group_id' => $inserted_id)); +} + /** * API method * Removes user(s) from a group diff --git a/ws.php b/ws.php index f85469fcc..c3c9dddd7 100644 --- a/ws.php +++ b/ws.php @@ -915,7 +915,7 @@ function ws_addDefaultMethods( $arr ) 'ws_groups_merge', array( 'destination_group_id' => array('type'=>WS_TYPE_ID, - 'info'=>'Destination group (is not necessarily part of groups to merge)'), + 'info'=>'Is not necessarily part of groups to merge'), 'merge_group_id' => array('flags'=>WS_PARAM_FORCE_ARRAY, 'type'=>WS_TYPE_ID), 'pwg_token' => array(), @@ -925,6 +925,19 @@ function ws_addDefaultMethods( $arr ) array('admin_only'=>true, 'post_only'=>true) ); + $service->addMethod( + 'pwg.groups.duplicate', + 'ws_groups_duplicate', + array( + 'group_id' => array('type'=>WS_TYPE_ID), + 'copy_name' => array(), + 'pwg_token' => array(), + ), + 'Create a copy of a group', + $ws_functions_root . 'pwg.groups.php', + array('admin_only'=>true, 'post_only'=>true) + ); + $service->addMethod( 'pwg.users.getList', 'ws_users_getList',