From df46475e1b7b7320bd60d866ed186668b97d455f Mon Sep 17 00:00:00 2001 From: Zacharie Date: Wed, 13 May 2020 16:57:52 +0200 Subject: [PATCH] Issue #1167 : Implement group merge function * Implement new API function pwg.groups.merge * Generalize the delete group function in admin/include/functions.php * Implement merge action in Group Manager * Add loading state on grouped delete in group manager * Member manager's design fixes --- admin/include/functions.php | 44 +++ admin/themes/clear/theme.css | 2 +- admin/themes/default/js/group_list.js | 285 +++++++++++++------ admin/themes/default/template/group_list.tpl | 26 +- admin/themes/default/theme.css | 45 ++- admin/themes/roma/theme.css | 2 +- include/ws_functions/pwg.groups.php | 127 ++++++--- ws.php | 15 + 8 files changed, 402 insertions(+), 144 deletions(-) diff --git a/admin/include/functions.php b/admin/include/functions.php index 3521c619a..98946772e 100644 --- a/admin/include/functions.php +++ b/admin/include/functions.php @@ -2454,6 +2454,50 @@ SELECT name return $groupname; } +function delete_groups($group_ids) { + + $group_id_string = implode(',', $group_ids); + + // destruction of the access linked to the group + $query = ' +DELETE + FROM '. GROUP_ACCESS_TABLE .' + WHERE group_id IN('. $group_id_string .') +;'; + pwg_query($query); + + // destruction of the users links for this group + $query = ' +DELETE + FROM '. USER_GROUP_TABLE .' + WHERE group_id IN('. $group_id_string .') +;'; + pwg_query($query); + + $query = ' +SELECT id, name + FROM `'. GROUPS_TABLE .'` + WHERE id IN('. $group_id_string .') +;'; + + $group_list = query2array($query, 'id', 'name'); + $groupids = array_keys($group_list); + + // destruction of the group + $query = ' +DELETE + FROM `'. GROUPS_TABLE .'` + WHERE id IN('. $group_id_string .') +;'; + pwg_query($query); + + trigger_notify('delete_group', $groupids); + pwg_activity('group', $groupids, 'delete'); + + + return $group_list; +} + /** * Returns the username corresponding to the given user identifier if exists. * diff --git a/admin/themes/clear/theme.css b/admin/themes/clear/theme.css index c62e929a7..32451510d 100644 --- a/admin/themes/clear/theme.css +++ b/admin/themes/clear/theme.css @@ -602,7 +602,7 @@ input:focus + .slider { border:1px solid #3c3c3c; } -.ValidationUserAssociated{ +.ValidationUserAssociated, .ValidationUserDissociated{ color:#00aa00; background-color:#c2f5c2; } diff --git a/admin/themes/default/js/group_list.js b/admin/themes/default/js/group_list.js index 529e1c01a..f7f61d746 100644 --- a/admin/themes/default/js/group_list.js +++ b/admin/themes/default/js/group_list.js @@ -90,6 +90,7 @@ jQuery(document).ready(function () { 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); @@ -109,9 +110,9 @@ jQuery(document).ready(function () { newgroup.find(".groupMessage").fadeIn(); newgroup.find(".groupMessage").delay(DELAY_FEEDBACK).fadeOut(); } else { - $("#showAddGroup .groupError").html(str_name_taken); - $("#showAddGroup .groupError").fadeIn(); - $("#showAddGroup .groupError").delay(DELAY_FEEDBACK).fadeOut(); + $("#addGroupForm .groupError").html(str_name_taken); + $("#addGroupForm .groupError").fadeIn(); + $("#addGroupForm .groupError").delay(DELAY_FEEDBACK).fadeOut(); } }, error: function (err) { @@ -129,54 +130,14 @@ jQuery(document).ready(function () { setupGroupBox($(this)); }); }); - var setupGroupBox = function (groupBox) { var id = groupBox.data("id"); /* Change background color of group block if checked in selection mode */ groupBox.find(".Group-checkbox input[type='checkbox']").change(function () { - if ($(this).is(":checked")) { - groupBox.addClass("OrangeBackground"); - groupBox.find(".icon-users-1").addClass("OrangeIcon"); - groupBox.find(".group_number_users").addClass("OrangeFont"); - - //Display item selection on selection panel - item = $( - "
" + - "" + - "

" + - groupBox.find("#group_name").html() + - "

" - ); - item.appendTo(".DeleteGroupList"); - item.find("a").on("click", function () { - groupBox.find(".Group-checkbox input").attr("checked", false); - groupBox - .find(".Group-checkbox input[type='checkbox']") - .trigger("change"); - }); - updateSelectionPanel(); - option = $('') - option.appendTo("#MergeOptionsChoices"); - } else { - groupBox.removeClass("OrangeBackground"); - groupBox.find(".icon-users-1").removeClass("OrangeIcon"); - groupBox.find(".group_number_users").removeClass("OrangeFont"); - $(".DeleteGroupList div").each(function () { - if ($(this).data("id") == id) { - $(this).remove(); - } - }); - updateSelectionPanel(); - $("#MergeOptionsChoices option").each(function () { - if ($(this).attr("value") == id) { - $(this).remove(); - } - }); - } + toogleSelection(id, groupBox.find(".Group-checkbox input[type='checkbox']").is(":checked")); }); + groupBox.find(".Group-checkbox input[type='checkbox']").attr("checked", false) /* Display the option on the click on "..." */ groupBox.find(".group-dropdown-options").click(function GroupOptions() { @@ -186,18 +147,40 @@ var setupGroupBox = function (groupBox) { /* Set the delete action */ groupBox.find("#GroupDelete").on("click", function () { $.confirm({ - title: str_delete+' '+groupBox.find("#group_name").html(), - content: str_are_you_sure, + title: str_delete.replace("%s",groupBox.find("#group_name").html()), + draggable: false, + titleClass: "groupDeleteConfirm", + theme: "modern", + content: "", + animation: "zoom", boxWidth: '30%', useBootstrap: false, type: 'red', - typeAnimated: true, + animateFromElement: false, + backgroundDismiss: true, + typeAnimated: false, buttons: { confirm: { text: str_yes_delete_confirmation, btnClass: 'btn-red', action: function () { - deleteGroup(id); + let groupName = groupBox.find(".Group-name-container p").html(); + deleteGroup(id).then(() => { + $.alert({ + title: str_group_deleted.replace("%s",groupName), + icon: 'icon-ok', + titleClass: "groupDeleteAlert", + theme:"modern", + closeIcon: true, + content: "", + animation: "zoom", + boxWidth: '20%', + useBootstrap: false, + backgroundDismiss: true, + animateFromElement: false, + typeAnimated: false, + }); + }); } }, cancel: { @@ -249,28 +232,71 @@ var setupGroupBox = function (groupBox) { }; +var toogleSelection = function(group_id, toggle) { + groupBox = $("#group-"+group_id); + if (toggle) { + groupBox.find(".Group-checkbox input").attr("checked", true); + groupBox.addClass("OrangeBackground"); + groupBox.find(".icon-users-1").addClass("OrangeIcon"); + groupBox.find(".group_number_users").addClass("OrangeFont"); + + //Display item selection on selection panel + item = $( + "
" + + "" + + "

" + + groupBox.find("#group_name").html() + + "

" + ); + item.appendTo(".DeleteGroupList"); + item.find("a").on("click", function () { + groupBox.find(".Group-checkbox input").attr("checked", false); + toogleSelection(group_id); + }); + updateSelectionPanel(); + option = $('') + option.appendTo("#MergeOptionsChoices"); + } else { + groupBox.find(".Group-checkbox input").attr("checked", false); + groupBox.removeClass("OrangeBackground"); + groupBox.find(".icon-users-1").removeClass("OrangeIcon"); + groupBox.find(".group_number_users").removeClass("OrangeFont"); + $(".DeleteGroupList div").each(function () { + if ($(this).data("id") == group_id) { + $(this).remove(); + } + }); + updateSelectionPanel(); + $("#MergeOptionsChoices option").each(function () { + if ($(this).attr("value") == group_id) { + $(this).remove(); + } + }); + } +} + /* Group Ajax and Display Functions */ var deleteGroup = function (id) { - jQuery.ajax({ - url: "ws.php?format=json&method=pwg.groups.delete", - type: "POST", - data: "group_id=" + id + "&pwg_token=" + pwg_token, - success: function (raw_data) { - data = jQuery.parseJSON(raw_data); - if (data.stat === "ok") { - $.alert({ - title: str_group_deleted, - content: "", - boxWidth: '20%', - useBootstrap: false - }); - $("#group-" + id).remove(); - } - }, - error: function (err) { - console.log(err); - }, - }); + return new Promise((resolve, reject) => { + jQuery.ajax({ + url: "ws.php?format=json&method=pwg.groups.delete", + type: "POST", + data: "group_id=" + id + "&pwg_token=" + pwg_token, + success: function (raw_data) { + data = jQuery.parseJSON(raw_data); + if (data.stat === "ok") { + $("#group-" + id).remove(); + $(".DeleteGroupList div[data-id="+id+"]").remove() + resolve(); + } + }, + error: function (err) { + console.log(err); + }, + }); + }) }; var renameGroup = function(id, newName) { @@ -338,13 +364,9 @@ var setDefaultGroup = function (id, is_default) { if (data.stat === "ok") { if (is_default) { setupDefaultActions(id,true) - //$("#group-" + id).find(".groupMessage").html(str_set_default_state); } else { setupDefaultActions(id,false) - //$("#group-" + id).find(".groupMessage").html(str_unset_default_state); } - //$("#group-" + id).find(".groupMessage").fadeIn(); - //$("#group-" + id).find(".groupMessage").delay(DELAY_FEEDBACK).fadeOut(); } }, error: function (err) { @@ -379,6 +401,7 @@ var setupDefaultActions = function(id, is_default) { -------*/ $(function () { + $("#toggleSelectionMode").attr("checked", false) $("#toggleSelectionMode").click(function () { if ($(this).is(":checked")) { $(".in-selection-mode").show(); @@ -478,17 +501,98 @@ var buttonUnavailable = function(button) { button.removeAttr("OnClick"); } +/*------- + Merge function on button's pannel + -------*/ + +$('.ConfirmMergeButton').on("click", function() { + let loadState = new TemporaryState(); + loadState.changeAttribute($('.ConfirmMergeButton'), "style", "pointer-events: none"); + loadState.changeHTML($('.ConfirmMergeButton'), " "); + loadState.removeClass($('.ConfirmMergeButton'), "icon-ok"); + merge_group = []; + str_merge_group = ""; + dest_grp = $("#MergeOptionsChoices").val(); + + $(".DeleteGroupList div").each(function () { + if (dest_grp != $(this).attr("data-id")) { + str_merge_group += "&merge_group_id[]="+$(this).attr("data-id"); + merge_group.push($(this).attr("data-id")); + } + }) + + jQuery.ajax({ + url: "ws.php?format=json&method=pwg.groups.merge", + type: "POST", + data: "destination_group_id=" + dest_grp + str_merge_group + "&pwg_token=" + pwg_token, + success: function (raw_data) { + loadState.reverse(); + data = jQuery.parseJSON(raw_data); + if (data.stat === "ok") { + updateSelectionPanel('Selection'); + console.log(data); + merge_group.forEach(function(id) { + ($("#group-"+id).fadeOut(complete=function(){ + $(this).remove(); + })) + }) + toogleSelection(dest_grp, false) + + $("#group-"+dest_grp + " .group_number_users").html(" "); + jQuery.ajax({ + url: "ws.php?format=json&method=pwg.users.getList", + type: "POST", + data: "group_id=" + dest_grp, + success: function (raw_data) { + data = jQuery.parseJSON(raw_data); + let number = data.result.users.length; + $("#group-"+dest_grp + " .group_number_users").html( + number + " " + ((number > 1)? str_members_default:str_member_default) + ); + } + }) + }; + } + }) +}) + /*------- Delete function on button's pannel -------*/ $('.ConfirmDeleteButton').on("click", function() { + let names = []; + let promises = []; $('.DeleteGroupList div').each(function () { - deleteGroup($(this).data('id')); - $(this).remove(); + let id = $(this).data('id'); + names.push($("#group-"+id+" #group_name").html()); + promises.push(deleteGroup(id)); + }); + + let loadState = new TemporaryState; + loadState.changeAttribute($('.ConfirmDeleteButton'), "style", "pointer-events: none"); + 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, + }); }) -}) /*------- Manage User Part @@ -505,9 +609,13 @@ var usersInGroup = []; // Max offset of the user container (322 = 6 lines) var maxOffsetUserCont = 322; -var infoUsers = $("
" +var dissociateUserInfo = $("
" + "

" - + "
") + + "
").appendTo(".group-name-block").hide(); + +var associateUserInfo = $("
" ++ "

" ++ "
"); // Setup the user research bar $(function() { @@ -625,10 +733,10 @@ var getUserDisplay = function(username, user_id, grp_id) { success: function (raw_data) { data = jQuery.parseJSON(raw_data); if (data.stat === "ok") { - infoUsers.remove(); - infoUsers.insertAfter(userBlock).hide(); - infoUsers.find("p").html(str_user_dissociated); - infoUsers.fadeIn() + let str = str_user_dissociated.replace("%s", username) + dissociateUserInfo.find("p").html(str); + dissociateUserInfo.fadeIn() + dissociateUserInfo.delay(DELAY_FEEDBACK).fadeOut() userBlock.remove() @@ -652,7 +760,7 @@ var getUserDisplay = function(username, user_id, grp_id) { //Update member number function function updateMembernumber(number, grp_id) { $(".GroupContainer[data-id="+grp_id+"] .group_number_users") - .html(number + " " + str_member_default); + .html(number + " " + ((number > 1)? str_members_default:str_member_default)); $(".UserNumberBadge").html(number); $(".AmountOfUsersShown strong:nth-child(2)").html(number) $(".AmountOfUsersShown strong:nth-child(1)").html($(".UsernameBlock").length) @@ -691,10 +799,11 @@ $(".AddUserBlock button").on("click", function () { }) let userBlock = getUserDisplay(username, id, grp_id).prependTo(".UsersInGroupList"); - infoUsers.remove(); - infoUsers.insertAfter(userBlock).hide(); - infoUsers.find("p").html(str_user_associated); - infoUsers.fadeIn() + associateUserInfo.remove() + associateUserInfo.insertAfter(userBlock); + associateUserInfo.find("p").html(str_user_associated); + associateUserInfo.fadeIn() + associateUserInfo.delay(DELAY_FEEDBACK).fadeOut() updateUserSearch(); @@ -716,7 +825,6 @@ $(".input-user-name").on("input", function() { searchString = $(this).val().toLowerCase(); grp_id = $(".UserListPopIn").data("group_id"); if (searchString != "") { - infoUsers.remove(); $(".UsersInGroupListContainer").css("min-height", $(".UsersInGroupListContainer").height()) usersInGroup.forEach(function(u) { let isSearched = u.username.toLowerCase().includes(searchString) @@ -739,6 +847,7 @@ $(".input-user-name").on("input", function() { i++; } } + $(".AmountOfUsersShown strong:nth-child(1)").html($(".UsernameBlock").length) while ($(".UsersInGroupList").height() > maxOffsetUserCont) { $(".UsernameBlock").last().remove(); } diff --git a/admin/themes/default/template/group_list.tpl b/admin/themes/default/template/group_list.tpl index a2edff3f8..7e69cfc18 100644 --- a/admin/themes/default/template/group_list.tpl +++ b/admin/themes/default/template/group_list.tpl @@ -1,20 +1,19 @@ {footer_script} var pwg_token = "{$PWG_TOKEN}"; var str_member_default = "{'member'|@translate}" +var str_members_default = "{'members'|@translate}" var str_group_created = "{'Group added'|@translate}" var str_renaming_done = "{'Group renamed'|@translate}" var str_name_taken = "{'Name is already taken'|@translate}" -var str_group_deleted = "{'Group succesfully deleted'|@translate}" +var str_group_deleted = '{'Group "%s" succesfully deleted'|@translate}' +var str_groups_deleted = '{'Groups \{%s\} succesfully deleted'|@translate}' var str_set_default = "{'Set as group for new users'|@translate}" var str_unset_default = "{'Unset as group for new users'|@translate}" -var str_set_default_state = "{'Group for new users set'|@translate}" -var str_unset_default_state = "{'Group for new users unset'|@translate}" -var str_delete = "{'Delete'|@translate}" -var str_are_you_sure = "{'Are you sure ?'|@translate}" -var str_yes_delete_confirmation = "{'Yes, delete it'|@translate}" -var str_no_delete_confirmation = "{"No, I've changed my mind"|@translate}" -var str_user_associated = "{"User Associated"|@translate}" -var str_user_dissociated = "{"User Dissociated"|@translate}" +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_list = "{"User List"|@translate}" var serverKey = '{$CACHE_KEYS.users}' @@ -122,20 +121,19 @@ var rootUrl = '{$ROOT_URL}'

{'Choose which group to merge these groups into'|@translate}

{'The other groups will be removed'|@translate}

- -
- + Cancel

You are about to delete 0 groups, are you sure?

- - No, I have changed my mind + + {"No, I have changed my mind"|@translate}
diff --git a/admin/themes/default/theme.css b/admin/themes/default/theme.css index 59141ab82..48d269f3f 100644 --- a/admin/themes/default/theme.css +++ b/admin/themes/default/theme.css @@ -2044,6 +2044,35 @@ input:checked + .slider:before { font-size: 13px; } +.groupDeleteConfirm, .groupDeleteAlert { + padding-bottom: 0 !important; + color: #3c3c3c !important; + line-height: 28px !important; +} + +.groupDeleteConfirm ~ .jconfirm-content-pane, .groupDeleteAlert ~ .jconfirm-content-pane { + height: 0px !important; + margin: 0px !important; +} + +.groupDeleteConfirm ~ .jconfirm-buttons button { + text-transform: none !important; +} + +.groupDeleteAlert .jconfirm-icon-c i { + color: #0a0 !important; + background-color:#c2f5c2 !important; + border-radius: 20px; + padding: 5px 3px; + border-radius: 60px; + padding: 2px; + font-size: 45px; +} + +.groupDeleteAlert .jconfirm-icon-c { + margin-bottom: 20px !important; +} + /*Group checkbox*/ .Group-checkbox{ @@ -2421,7 +2450,7 @@ input:checked + .slider:before { display:inline-block; font-size:12px; text-align:left; - padding:2px; + padding: 4px 6px; border-radius:10px; margin-left:-5px; margin-right:10px; @@ -2431,6 +2460,20 @@ input:checked + .slider:before { margin:0; } +.ValidationUserDissociated{ + display:inline-block; + text-align:left; + padding: 6px; + border-radius: 30px; + margin-left: 8px; +} + +.ValidationUserDissociated p{ + margin:0 !important; + font-size:12px !important; + font-weight: 500 !important; +} + #UserList .FilterUserBlock .AmountOfUsersShown { display:inline-block; diff --git a/admin/themes/roma/theme.css b/admin/themes/roma/theme.css index c9f0dad05..3403babf5 100644 --- a/admin/themes/roma/theme.css +++ b/admin/themes/roma/theme.css @@ -632,7 +632,7 @@ input:focus + .slider { border:1px solid #f3f3f3; } -.ValidationUserAssociated{ +.ValidationUserAssociated, .ValidationUserDissociated{ background-color:#00aa00; color:#c2f5c2; } diff --git a/include/ws_functions/pwg.groups.php b/include/ws_functions/pwg.groups.php index cba84e441..765645862 100644 --- a/include/ws_functions/pwg.groups.php +++ b/include/ws_functions/pwg.groups.php @@ -104,46 +104,9 @@ function ws_groups_delete($params, &$service) return new PwgError(403, 'Invalid security token'); } - $group_id_string = implode(',', $params['group_id']); - - // destruction of the access linked to the group - $query = ' -DELETE - FROM '. GROUP_ACCESS_TABLE .' - WHERE group_id IN('. $group_id_string .') -;'; - pwg_query($query); - - // destruction of the users links for this group - $query = ' -DELETE - FROM '. USER_GROUP_TABLE .' - WHERE group_id IN('. $group_id_string .') -;'; - pwg_query($query); - - $query = ' -SELECT id, name - FROM `'. GROUPS_TABLE .'` - WHERE id IN('. $group_id_string .') -;'; - - $group_list = query2array($query, 'id', 'name'); - $groupnames = array_values($group_list); - $groupids = array_keys($group_list); - - // destruction of the group - $query = ' -DELETE - FROM `'. GROUPS_TABLE .'` - WHERE id IN('. $group_id_string .') -;'; - pwg_query($query); - - trigger_notify('delete_group', $groupids); - pwg_activity('group', $groupids, 'delete'); - include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); + $groupnames = array_values(delete_groups($params['group_id'])); + invalidate_user_cache(); return new PwgNamedArray($groupnames, 'group_deleted'); @@ -264,6 +227,92 @@ SELECT COUNT(*) return $service->invoke('pwg.groups.getList', array('group_id' => $params['group_id'])); } +/** + * API method + * Merge groups in one other group + * @param mixed[] $params + * @option int destination_group_id + * @option int[] merge_group_id + */ +function ws_groups_merge($params, &$service) { + + if (get_pwg_token() != $params['pwg_token']) + { + return new PwgError(403, 'Invalid security token'); + } + + $all_groups = $params['merge_group_id']; + array_push($all_groups, $params['destination_group_id']); + + $all_groups = array_unique($all_groups); + $merge_group = array_diff($params['merge_group_id'], array($params['destination_group_id'])); + + $query = ' +SELECT COUNT(*) + FROM `'. GROUPS_TABLE .'` + WHERE id in ('.implode(',', $all_groups) .') +;'; + list($count) = pwg_db_fetch_row(pwg_query($query)); + if ($count != count($all_groups)) + { + return new PwgError(WS_ERR_INVALID_PARAM, 'All groups does not exist.'); + } + + $user_in_merge_groups = array(); + $user_in_dest = array(); + $user_to_add = array(); + + $query = ' +SELECT DISTINCT(user_id) + FROM `'. USER_GROUP_TABLE .'` + WHERE + group_id in ('.implode(',', $merge_group) .') +;'; + $user_in_merge_groups = query2array($query, null, 'user_id'); + + $query = ' +SELECT user_id + FROM `'. USER_GROUP_TABLE .'` + WHERE group_id = '.$params['destination_group_id'].' +;'; + + $user_in_dest = query2array($query, null, 'user_id');; + + + $user_to_add = array_diff($user_in_merge_groups, $user_in_dest); + + $inserts = array(); + foreach ($user_to_add as $user) + { + $inserts[] = array( + 'group_id' => $params['destination_group_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(); + + pwg_activity('group', $params['destination_group_id'], 'edit'); + foreach ($user_to_add as $user_id) + { + pwg_activity('user', $user_id, 'edit'); + } + + include_once(PHPWG_ROOT_PATH.'admin/include/functions.php'); + + delete_groups($merge_group); + + return $service->invoke('pwg.groups.getList', array('group_id' => $params['destination_group_id'])); +} + /** * API method * Removes user(s) from a group diff --git a/ws.php b/ws.php index ef4c9a1ea..f85469fcc 100644 --- a/ws.php +++ b/ws.php @@ -910,6 +910,21 @@ function ws_addDefaultMethods( $arr ) array('admin_only'=>true, 'post_only'=>true) ); + $service->addMethod( + 'pwg.groups.merge', + 'ws_groups_merge', + array( + 'destination_group_id' => array('type'=>WS_TYPE_ID, + 'info'=>'Destination group (is not necessarily part of groups to merge)'), + 'merge_group_id' => array('flags'=>WS_PARAM_FORCE_ARRAY, + 'type'=>WS_TYPE_ID), + 'pwg_token' => array(), + ), + 'Merge groups in one other group', + $ws_functions_root . 'pwg.groups.php', + array('admin_only'=>true, 'post_only'=>true) + ); + $service->addMethod( 'pwg.users.getList', 'ws_users_getList',