GroupsTreeService.java
package sk.iway.iwcm.doc;
import org.springframework.stereotype.Service;
import sk.iway.iwcm.Constants;
import sk.iway.iwcm.DB;
import sk.iway.iwcm.Identity;
import sk.iway.iwcm.RequestBean;
import sk.iway.iwcm.SetCharacterEncodingFilter;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.admin.jstree.JsTreeItem;
import sk.iway.iwcm.admin.jstree.JsTreeItemState;
import sk.iway.iwcm.common.CloudToolsForCore;
import sk.iway.iwcm.editor.EditorDB;
import sk.iway.iwcm.editor.rest.WebPagesListener;
import sk.iway.iwcm.editor.service.GroupsService;
import sk.iway.iwcm.editor.service.WebpagesService;
import sk.iway.iwcm.i18n.Prop;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class GroupsTreeService {
/**
*
* @param user
* @param id - id priecinka
* @param showPages - ak je true, vratia sa v zozname aj web stranky
* @param click - typ zobrazenia/filtrovania
* @param requestedDomain - pozadovana domena pre filtrovanie alebo NULL pre filter podla aktualne nastavenej domeny
* @param request
* @return
*/
public List<JsTreeItem> getItems(Identity user, int id, boolean showPages, String click, String requestedDomain, HttpServletRequest request) {
GroupsDB groupsDB = GroupsDB.getInstance();
DocDB docDB = DocDB.getInstance();
int idOriginal = id;
String treeSearchValue = Tools.getStringValue(request.getParameter("treeSearchValue"), null);
if (isSystemRequested(click) && id<1) {
if (Constants.getBoolean("templatesUseRecursiveSystemFolder")==false) {
GroupDetails systemGroupDetails = GroupsService.getSystemGroupDetails();
if (systemGroupDetails != null) id = systemGroupDetails.getGroupId();
}
} else if (isTrashRequested(click) && id<1) {
GroupDetails trashGroupDetails = GroupsService.getTrashGroupDetails();
if (trashGroupDetails != null) id = trashGroupDetails.getGroupId();
}
List<JsTreeItem> items = new ArrayList<>();
GroupDetails group = groupsDB.getGroup(id);
if (group == null && id != 0) return items;
final int groupDefaultDocId = (group != null) ? group.getDefaultDocId() : -1;
boolean parentEditable = GroupsDB.isGroupEditable(user, id);
List<GroupDetails> groups = getGroups(id, treeSearchValue, request);
//Special case -> if we want tree items for STAT section AND user have cmp_stat_seeallgroups right, we do not filter by perms but RETURN ALL ITEMS
String referer = request.getHeader("referer");
String uri = request.getRequestURI();
final boolean checkGroupsPerms;
//Referer -> that we call from stat section
//Uri -> that it's called from GroupTreeRestController NOT from WebPagesListener
if(referer!=null && referer.contains("/apps/stat/admin/") && uri != null && uri.contains("/admin/rest/groups/tree/tree") && user.isEnabledItem("cmp_stat_seeallgroups")) {
checkGroupsPerms = false;
} else {
checkGroupsPerms = true;
groups = filterByPerms(groups, user); //Filter groups by perms
}
if (click.contains("alldomains")==false || requestedDomain!=null) groups = filterByDomain(groups, requestedDomain);
GroupDetails domainRootGroup = null;
String domainFilesPrefix = "";
if (Constants.getBoolean("enableStaticFilesExternalDir"))
{
int domainId = CloudToolsForCore.getDomainId();
domainRootGroup = GroupsDB.getInstance().getGroup(domainId);
domainFilesPrefix = "/" + domainRootGroup.getGroupName();
}
if (click.contains("filter-system-trash")) {
groups = filterSystem(groups);
//filter Full text Index folder /files
groups = filterFullPath(groups, "/files");
if (Constants.getBoolean("enableStaticFilesExternalDir") && domainRootGroup!=null)
{
///files adresar vytvarame v domenovom foldri
groups = filterFullPath(groups, domainFilesPrefix + "/files");
}
}
List<JsTreeItem> filteredFilesGroupsTree = new ArrayList<>();
if (isSystemRequested(click)) {
if (group!=null && (group.getFullPath().startsWith("/files") || group.getFullPath().startsWith(domainFilesPrefix+"/files"))) {
//if it's /files/something OR /Jet Portal 4/files/something then do not filter and show all groups by requested parent folder
} else {
if (Constants.getBoolean("templatesUseRecursiveSystemFolder")) {
groups = filterOnlyWithSystemChilds(groups, groupsDB);
} else {
//musis odfiltrovat Kos priecinok
groups = filterTrash(groups);
}
//add /files folder to root (it's shown in /System folder)
if (idOriginal==0) {
List<GroupDetails> filesGroups = filterFullPath(groupsDB.getGroups(0), "!/files");
if (domainRootGroup != null && Tools.isNotEmpty(domainFilesPrefix)) {
filesGroups.addAll( filterFullPath(groupsDB.getGroups(domainRootGroup.getGroupId()), "!"+domainFilesPrefix+"/files") );
}
if(Tools.isNotEmpty(treeSearchValue) == true) {
filteredFilesGroupsTree = getFilteredGroupsTree(filesGroups, treeSearchValue, user, showPages, request);
// This file groups is fully prepared tree with parents -> add at end
} else {
groups.addAll(filesGroups);
}
}
}
}
items.addAll( sortTreeBasedOnUserSettings(user, groups, showPages, checkGroupsPerms) );
if(Tools.isEmpty(treeSearchValue)) {
//standardne zobrazenie v stromovej strukture, rovno zobraz aj pod adresare
int groupId = WebPagesListener.getLastGroupId(request, 0);
if ((click.equals("dt-tree-group-filter-system-trash") || click.equals("dt-tree-filter-system-trash")) && id==0 && groupId<1 && request.getParameter("docid")==null) {
List<JsTreeItem> rootItems = new ArrayList<>();
rootItems.addAll(items);
//oznac prvy adresar ako selectnuty
if (rootItems.isEmpty()==false) {
rootItems.get(0).getState().setSelected(true);
}
//pridaj child elementy
if (rootItems.size() <= Constants.getInt("webpagesTreeAutoOpenLimit", 2)) {
//ak je tam menej ako 3 grupy, tak nacitaj rovno aj subgrupy
for (JsTreeItem item : rootItems) {
if (item.getState()==null) item.setState(new JsTreeItemState());
item.getState().setOpened(true);
//toto je totalna haluz, pri rozbalenie a refreshi toto nemoze byt nastavene, inak to padne
if (request.getParameter("click")!=null) item.setChildren(null);
item.setParent("#");
List<JsTreeItem> subGroups = this.getItems(user, Tools.getIntValue(item.getId(), -1), showPages, click, requestedDomain, request);
for (JsTreeItem sub : subGroups) {
sub.setParent(item.getId());
}
items.addAll(subGroups);
}
}
}
} else {
//
List<JsTreeItem> filteredListWithParents = addParents( items, user, showPages, checkGroupsPerms, id );
// Add prepared file groups
filteredListWithParents.addAll(filteredFilesGroupsTree);
items.clear();
items.addAll(filteredListWithParents);
}
if (parentEditable && showPages && id>0)
{
List<DocDetails> childDocs = docDB.getBasicDocDetailsByGroup(id, DocDB.ORDER_PRIORITY);
if (childDocs != null && childDocs.size() > 0) {
items.addAll(childDocs.stream().map(doc -> {
DocumentsJsTreeItem jstree = new DocumentsJsTreeItem(doc, groupDefaultDocId);
if (click.contains("alldomains")) {
jstree.setVirtualPath(addDomainPrefixToFullPath(doc, groupsDB));
}
return jstree;
}).collect(Collectors.toList()));
}
}
if (id == Constants.getInt("systemPagesNotApprovedDocs")) {
List<DocDetails> notApproved = docDB.getNotApprovedDocs(user.getUserId());
if (notApproved != null && notApproved.size() > 0) {
items.addAll(notApproved.stream().map(doc -> new DocumentsJsTreeItem(doc, groupDefaultDocId)).collect(Collectors.toList()));
}
}
if (id == Constants.getInt("systemPagesMyPages")) {
List<DocDetails> myPages = DocDB.getMyPages(user);
if (myPages.size() > 0) {
items.addAll(myPages.stream().map(doc -> new DocumentsJsTreeItem(doc, groupDefaultDocId)).collect(Collectors.toList()));
}
}
if (id == Constants.getInt("systemPagesDocsToApprove")) {
List<DocDetails> approve = docDB.getDocsForApprove(user.getUserId());
if (approve != null && approve.size() > 0) {
items.addAll(approve.stream().map(doc -> new DocumentsJsTreeItem(doc, groupDefaultDocId)).collect(Collectors.toList()));
}
}
return items;
}
private List<JsTreeItem> sortTreeBasedOnUserSettings(Identity user, List<GroupDetails> groups, boolean showPages, boolean checkGroupsPerms) {
String sortType = WebpagesService.getTreeSortType(user);
boolean orderAsc = WebpagesService.isTreeSortOrderAsc(user);
Comparator<GroupDetails> comparator;
if("title".equals(sortType)) {
// ignore case, sort that way is better
comparator = Comparator.comparing(GroupDetails::getGroupName, String.CASE_INSENSITIVE_ORDER);
} else if("createDate".equals(sortType)) {
comparator = Comparator.comparing(GroupDetails::getGroupId);
} else {
//DEFAULT OPTION -> sort by "priority"
comparator = Comparator.comparing(GroupDetails::getSortPriority);
}
if(!orderAsc) {
comparator = comparator.reversed();
}
return groups.stream()
.sorted(comparator)
.map(g -> new GroupsJsTreeItem(g, user, showPages, checkGroupsPerms))
.collect(Collectors.toList());
}
public void fixSortPriority(HttpServletRequest request, int docId, GroupDetails parent, int position) {
DocDB docDB = DocDB.getInstance();
//we need to subtrack groups from position, because we have groups in the list
List<GroupDetails> groups = GroupsDB.getInstance().getGroups(parent.getGroupId());
position -= groups.size();
DocDetails doc = docDB.getDoc(docId);
List<DocDetails> docsByGroup = docDB.getDocByGroup(parent.getGroupId(), DocDB.ORDER_PRIORITY, true, -1, -1, false, false);
List<DocDetails> collect = docsByGroup.stream().filter(d -> d.getDocId() != doc.getDocId()).sorted(Comparator.comparing(DocDetails::getSortPriority)).collect(Collectors.toList());
int sortPriority = collect.size() > 0 ? collect.get(0).getSortPriority() : parent.getSortPriority() * 10;
collect.add(position, doc);
int sortPriorityIncrementDoc = Constants.getInt("sortPriorityIncrementDoc");
for (DocDetails document : collect) {
document.setSortPriority(sortPriority);
DocDB.saveDoc(document);
sortPriority += sortPriorityIncrementDoc;
}
EditorDB.cleanSessionData(request);
}
private static List<GroupDetails> filterByPerms(List<GroupDetails> groups, Identity user) {
List<GroupDetails> filtered = groups.stream().filter(g->{
boolean editable = GroupsDB.isGroupEditable(user, g.getGroupId());
boolean viewable = GroupsDB.isGroupViewable(user, g.getGroupId());
if (g.isHiddenInAdmin() && user.isDisabledItem("editor_show_hidden_folders")) {
return false;
}
return viewable || editable;
}).collect(Collectors.toList());
return filtered;
}
private static List<GroupDetails> filterByDomain(List<GroupDetails> groups, String requestedDomain) {
if (Constants.getBoolean("multiDomainEnabled")==false) return groups;
String currentDomain = requestedDomain!=null ? requestedDomain : CloudToolsForCore.getDomainName();
List<GroupDetails> filtered = groups.stream().filter(g->{
if ("System".equals(g.getGroupName()) && Tools.isEmpty(g.getDomainName()) && Constants.getBoolean("templatesUseDomainLocalSystemFolder")) {
//odignorujme globalny system adresar, ak ma existovat lokalny
return false;
}
if (Tools.isEmpty(g.getDomainName()) || g.getDomainName().equals(currentDomain)) return true;
return false;
}).collect(Collectors.toList());
return filtered;
}
/**
* Z adresarov odfiltruje tie obsahujuce /System v ceste
* @param groups
* @return
*/
private static List<GroupDetails> filterSystem(List<GroupDetails> groups) {
List<GroupDetails> filteredByPath = filterFullPath(groups, "/System");
RequestBean rb = SetCharacterEncodingFilter.getCurrentRequestBean();
if (rb == null)
{
return filteredByPath;
}
//v starych WJ mame Syste priecinok ako podpriecinok hlavneho priecinku, musime odfiltrovat takto
String domain = rb.getDomain();
if (Tools.isEmpty(domain)) return filteredByPath;
int domainId = GroupsDB.getDomainId(domain);
List<GroupDetails> filtered = filteredByPath.stream().filter(g->{
if ("System".equals(g.getGroupName()) && g.getParentGroupId()==domainId) return false;
return true;
}).collect(Collectors.toList());
return filtered;
}
/**
* odfiltruje priecinok /System/Kos so zoznamu
* @param groups
* @return
*/
public static List<GroupDetails> filterTrash(List<GroupDetails> groups) {
String trashDirPath = getTrashDirPath();
return filterFullPath(groups, "*"+trashDirPath);
}
public static String getTrashDirPath() {
Prop propSystem = Prop.getInstance(Constants.getString("defaultLanguage"));
String trashDirPath = propSystem.getText("config.trash_dir");
return trashDirPath;
}
/**
* Odfiltruje adresare zacinajuce na zadanu cestu
* @param groups
* @param filterFullPath
* @return
*/
private static List<GroupDetails> filterFullPath(List<GroupDetails> groups, String filterFullPath) {
List<GroupDetails> filtered = groups.stream().filter(g->{
if (filterFullPath.startsWith("!") && g.getFullPath().startsWith(filterFullPath.substring(1))==false) return false;
if ( g.getFullPath().startsWith(filterFullPath) ) return false;
if (filterFullPath.startsWith("*") && filterFullPath.length()>2 && g.getFullPath().contains(filterFullPath.substring(1)) ) return false;
return true;
}).collect(Collectors.toList());
return filtered;
}
/**
* Z adresarov PONECHA len tie, ktore ako child maju System adresar
* @param groups
* @return
*/
private static List<GroupDetails> filterOnlyWithSystemChilds(List<GroupDetails> groups, GroupsDB groupsDB) {
List<GroupDetails> allSystemFolders = groupsDB.getAllSystemFolders(false);
List<GroupDetails> filtered = groups.stream().filter(g->{
String fullPath = g.getFullPath();
for (GroupDetails system : allSystemFolders) {
//Logger.debug(GroupsTreeService.class, "Comparing system "+system.getFullPath()+" vs group "+fullPath);
//smerom dole
if (system.getFullPath().startsWith(fullPath)) return true;
//smerom na childov
if (fullPath.startsWith(system.getFullPath())) return true;
}
return false;
}).collect(Collectors.toList());
return filtered;
}
/**
* Vrati true, ak je pozadovane zobrazenie system priecinka
* @param click
* @return
*/
private static boolean isSystemRequested(String click) {
return ("dt-tree-group-system".equals(click) || "dt-tree-system".equals(click));
}
/**
* Vrati true, ak je pozadovane zobrazenie kosa
* @param click
* @return
*/
private static boolean isTrashRequested(String click) {
return ("dt-tree-group-trash".equals(click) || "dt-tree-trash".equals(click));
}
/**
* Returns group.fullPath for DocDetails with domain prefix in multi domain enviroment
* @param tmp - DocDetails object
* @param groupsDB
* @return
*/
public static String addDomainPrefixToFullPath(DocDetails tmp, GroupsDB groupsDB) {
GroupDetails grp = groupsDB.getGroup(tmp.getGroupId());
StringBuilder path = new StringBuilder();
if (grp != null) {
if (Tools.isNotEmpty(grp.getDomainName()) && Constants.getBoolean("multiDomainEnabled")) path.append(grp.getDomainName()).append(":");
path.append(grp.getFullPath());
}
path.append("/");
path.append(tmp.getTitle());
return path.toString();
}
/**
* Returns GrooupDetails object for groupId, BUT verify permissions for user.
* It it's not accessible, return first accessible group for user.
* @param groupId
* @param user
* @return
*/
public static GroupDetails gerDefaultGroupTreeOptionForUser(int groupId, Identity user) {
GroupsDB groupsDB = GroupsDB.getInstance();
//User can edit all groups -> so return group (no check needed)
//OR user have right cmp_stat_seeallgroups (in stat section ONLY)
RequestBean rb = SetCharacterEncodingFilter.getCurrentRequestBean();
String referer = null;
if (rb != null) {
referer = rb.getReferrer();
}
if( Tools.isEmpty(user.getEditableGroups(true)) || (referer != null && referer.contains("/apps/stat/admin/") && user.isEnabledItem("cmp_stat_seeallgroups"))) {
if(groupId > 0) return groupsDB.findGroup(groupId);
GroupDetails rootGroup = new GroupDetails();
rootGroup.setGroupId(-1);
return rootGroup;
}
//Can handle default group ?
boolean parentEditable = GroupsDB.isGroupEditable(user, groupId);
boolean parentViewable = GroupsDB.isGroupViewable(user, groupId);
//Check if user have right for this group
//It cant be -1 (root group), because there is group restriction for you part of tree
if( (parentEditable || parentViewable) && groupId != -1) {
//User have right for this group)
return groupsDB.findGroup(groupId);
} else {
//Problem, user missing rights for this group ... return first permitted group
int[] permittedGroups = Tools.getTokensInt(user.getEditableGroups(true), ",");
//Use first groupId
return groupsDB.findGroup( permittedGroups[0] );
}
}
/**
* Filter list of groups by searchText value
* @param groups
* @param searchText
* @param request
* @return
*/
private List<GroupDetails> filterGroups(List<GroupDetails> groups, String searchText, HttpServletRequest request) {
String treeSearchType = Tools.getStringValue(request.getParameter("treeSearchType"), "");
final String wantedValueLC = DB.internationalToEnglish(searchText).toLowerCase();
//Filter by serach value and search type
if("contains".equals(treeSearchType)) {
return groups.stream()
.filter(group -> DB.internationalToEnglish(group.getGroupName()).toLowerCase().contains(wantedValueLC))
.collect(Collectors.toList());
} else if("startwith".equals(treeSearchType)) {
return groups.stream()
.filter(group -> DB.internationalToEnglish(group.getGroupName()).toLowerCase().startsWith(wantedValueLC))
.collect(Collectors.toList());
} else if("endwith".equals(treeSearchType)) {
return groups.stream()
.filter(group -> DB.internationalToEnglish(group.getGroupName()).toLowerCase().endsWith(wantedValueLC))
.collect(Collectors.toList());
} else if("equals".equals(treeSearchType)) {
return groups.stream()
.filter(group -> DB.internationalToEnglish(group.getGroupName()).equalsIgnoreCase(wantedValueLC))
.collect(Collectors.toList());
} else return new ArrayList<>();
}
/**
* Get groups list by root ID and filter by searchText
* @param rootGroupId
* @param searchText
* @param request
* @return
*/
private List<GroupDetails> getGroups(int rootGroupId, String searchText, HttpServletRequest request) {
GroupsDB groupsDB = GroupsDB.getInstance();
if (Tools.isEmpty(searchText)) return groupsDB.getGroups(rootGroupId);
//Get all suitable
List<GroupDetails> result;
if(rootGroupId < 1) {
result = groupsDB.getGroupsAll();
} else {
result = new ArrayList<>();
groupsDB.getGroupsTree(rootGroupId, result);
}
return filterGroups(result, searchText, request);
}
/**
* Filter file groups by searchValue
* @param fileGroups
* @param searchValue
* @param user
* @param showPages
* @param request
* @return
*/
private List<JsTreeItem> getFilteredGroupsTree(List<GroupDetails> fileGroups, String searchValue, Identity user, boolean showPages, HttpServletRequest request) {
GroupsDB groupsDB = GroupsDB.getInstance();
// From fileGroups get all groups expanded -> direction down (children)
Map<Integer, GroupDetails> allGroupsExpanded = new HashMap<>();
fileGroups.forEach(g -> allGroupsExpanded.put(g.getGroupId(), g));
for(GroupDetails fileGroup : fileGroups) {
List<GroupDetails> childs = new ArrayList<>();
groupsDB.getGroupsTree(fileGroup.getGroupId(), childs);
for(GroupDetails child : childs) {
allGroupsExpanded.putIfAbsent(child.getGroupId(), child);
}
}
// Filter all groups by wanted value
List<GroupDetails> filtered = filterGroups(new ArrayList<>(allGroupsExpanded.values()), searchValue, request);
//Transform to JsTreeItem
List<JsTreeItem> items = new ArrayList<>();
items.addAll(
filtered.stream()
.map(g -> new GroupsJsTreeItem(g, user, showPages))
.collect(Collectors.toList())
);
// Add to filtered groups their parents -> but only to height of original groups
Map<Integer, JsTreeItem> filteredWithParents = new HashMap<>();
for(JsTreeItem item : items) {
int idToAdd = Integer.parseInt(item.getId());
while(true) {
GroupDetails g = allGroupsExpanded.get(idToAdd);
if(g != null && filteredWithParents.containsKey(g.getGroupId()) == false) {
filteredWithParents.putIfAbsent(g.getGroupId(), new GroupsJsTreeItem(g, user, showPages));
idToAdd = g.getParentGroupId();
} else {
break;
}
}
}
// Set statuses
for (Map.Entry<Integer, JsTreeItem> entry : filteredWithParents.entrySet()) {
entry.getValue().setState(getLoadedState());
Integer parentId = allGroupsExpanded.get( entry.getKey() ).getParentGroupId();
entry.getValue().setParent( filteredWithParents.containsKey(parentId) ? parentId.toString() : "#" );
}
// Return filtered groups with their parents
return new ArrayList<>(filteredWithParents.values());
}
/**
* Add parent groups for foundGroups to correctly build tree structure
* @param filteredGroups
* @param user
* @param showPages
* @param checkGroupsPerms
* @param id
* @return
*/
private List<JsTreeItem> addParents(List<JsTreeItem> filteredGroups, Identity user, boolean showPages, boolean checkGroupsPerms, int id) {
GroupsDB groupsDB = GroupsDB.getInstance();
Map<Integer, GroupDetails> kk = new HashMap<>();
//Get whole trees
for(JsTreeItem group : filteredGroups) {
List<GroupDetails> parents = groupsDB.getParentGroups(Integer.parseInt(group.getId()), false);
for(GroupDetails parent : parents) {
kk.putIfAbsent(parent.getGroupId(), parent);
}
}
// Remove groups that are higher level that we want (including parent group because its System or trash and we dont want to show them)
if(id > 0) {
List<GroupDetails> groupsToRemove = groupsDB.getParentGroups(id);
for(GroupDetails group : groupsToRemove) {
kk.remove(group.getGroupId());
}
}
//Transform to JsTreeItem
List<JsTreeItem> items = new ArrayList<>();
items.addAll(
kk.values().stream()
.map(g -> new GroupsJsTreeItem(g, user, showPages, checkGroupsPerms))
.collect(Collectors.toList())
);
//Set them states
for(JsTreeItem item : items) {
item.setState(getLoadedState());
int parentId = kk.get( Integer.parseInt(item.getId()) ).getParentGroupId();
item.setParent( (parentId == 0 || parentId == id) ? "#" : String.valueOf(parentId) );
}
return items;
}
/**
* Returns JsTreeItemState with loaded state
* @return
*/
private JsTreeItemState getLoadedState() {
JsTreeItemState state = new JsTreeItemState();
state.setOpened(false);
state.setLoaded(true);
return state;
}
}