WebpagesService.java

package sk.iway.iwcm.editor.service;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

import sk.iway.iwcm.Adminlog;
import sk.iway.iwcm.Constants;
import sk.iway.iwcm.DB;
import sk.iway.iwcm.DBPool;
import sk.iway.iwcm.Identity;
import sk.iway.iwcm.InitServlet;
import sk.iway.iwcm.LabelValueDetails;
import sk.iway.iwcm.Logger;
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.layout.LayoutService;
import sk.iway.iwcm.admin.settings.AdminSettingsService;
import sk.iway.iwcm.common.CloudToolsForCore;
import sk.iway.iwcm.common.UserTools;
import sk.iway.iwcm.database.SimpleQuery;
import sk.iway.iwcm.doc.DocBasic;
import sk.iway.iwcm.doc.DocDB;
import sk.iway.iwcm.doc.DocDetails;
import sk.iway.iwcm.doc.DocEditorFields;
import sk.iway.iwcm.doc.GroupDetails;
import sk.iway.iwcm.doc.GroupEditorField;
import sk.iway.iwcm.doc.GroupsDB;
import sk.iway.iwcm.doc.GroupsTreeService;
import sk.iway.iwcm.doc.HistoryDB;
import sk.iway.iwcm.doc.MultigroupMappingDB;
import sk.iway.iwcm.doc.PerexGroupBean;
import sk.iway.iwcm.doc.TemplateDetails;
import sk.iway.iwcm.doc.TemplatesDB;
import sk.iway.iwcm.doc.attributes.jpa.DocAtrDefEntity;
import sk.iway.iwcm.doc.attributes.jpa.DocAtrDefRepository;
import sk.iway.iwcm.editor.EditorDB;
import sk.iway.iwcm.editor.EditorForm;
import sk.iway.iwcm.editor.facade.EditorFacade;
import sk.iway.iwcm.editor.rest.GetAllItemsDocOptions;
import sk.iway.iwcm.i18n.Prop;
import sk.iway.iwcm.system.context.ContextFilter;
import sk.iway.iwcm.search.SearchService;
import sk.iway.iwcm.system.datatable.DatatablePageImpl;
import sk.iway.iwcm.system.datatable.DatatableRestControllerV2;
import sk.iway.iwcm.system.datatable.NotifyBean;
import sk.iway.iwcm.system.datatable.NotifyButton;
import sk.iway.iwcm.system.datatable.ProcessItemAction;
import sk.iway.iwcm.system.datatable.SpecSearch;
import sk.iway.iwcm.system.datatable.json.LabelValue;
import sk.iway.iwcm.system.jpa.JpaTools;
import sk.iway.iwcm.users.UserDetails;
import sk.iway.iwcm.users.UserGroupDetails;
import sk.iway.iwcm.users.UserGroupsDB;
import sk.iway.iwcm.users.UsersDB;

/**
 * Priprava zoznamu web stranok a pridruzenych ciselnikov pre DT.
 * Servis je potrebne konstruovat so zadanym group_id, podla neho sa nasledne vracaju data.
 */
public class WebpagesService {

    private int groupId;
    private Prop prop;
	private GroupDetails localSystemGroup;

	public static final String DATA_NOT_LOADED = "data not loaded";
	private static final String ADMIN_SETTINGS_KEY = "jstreeSettings_web-pages-list";

	public WebpagesService() {}

    public WebpagesService(int groupId, Identity user, Prop prop, HttpServletRequest request) {
        this.groupId = groupId;
		if (groupId<1) {
			//musime vydedukovat korenovy adresar
			//zoznam web stranok v korenovom/prvom adresari v zozname
			int rootGroupId = Constants.getInt("rootGroupId");
			//ziskaj zoznam root adresarov
			GroupsTreeService groupsTreeService = new GroupsTreeService();
			List<JsTreeItem> rootGroups = groupsTreeService.getItems(user, 0, false, "dt-tree-group-filter-system-trash", null, request);
			if (rootGroups.isEmpty()==false) {
				rootGroupId = Tools.getIntValue(rootGroups.get(0).getId(), rootGroupId);
			}
			this.groupId = rootGroupId;
		}
		this.prop = prop;
    }

	/**
	 * Ponecha v zozname len adresare z aktualne nastavenej domeny
	 * @param groups
	 * @return
	 */
	public static List<GroupDetails> filterGroupsByCurrentDomain(List<GroupDetails> groups) {
		List<GroupDetails> filtered = new ArrayList<>();
		RequestBean rb = SetCharacterEncodingFilter.getCurrentRequestBean();
		String currentDomain = null;
		if (rb != null) currentDomain = rb.getDomain();
		if (currentDomain == null) currentDomain = "";

		boolean hasLocalSystem = false;
		GroupDetails globalSystem = null;

		for (GroupDetails group : groups) {
			if (Tools.isEmpty(group.getDomainName()) || currentDomain.equals(group.getDomainName())) {

				if ("System".equals(group.getGroupName()) && group.getParentGroupId()<1) {
					//globalny system adresar ma typicky prazdne domenove meno, ak existuje lokalny system nechceme ten globalny mat v zozname
					if (Tools.isEmpty(group.getDomainName())) {
						globalSystem = group;
						continue;
					}
					else hasLocalSystem = true;
				}

				filtered.add(group);
			}
		}

		if (hasLocalSystem==false && globalSystem!=null) filtered.add(globalSystem);

		return filtered;
	}

	/**
	 * Vrati zoznam dostupnych sablon
	 * @param recursive - ak je true vratia sa aj sablony z podadresarov
	 * @return
	 */
    public List<TemplateDetails> getTemplates(boolean recursive) {

		List<TemplateDetails> allTemplates = new ArrayList<>();
		allTemplates.addAll(getTemplates(-1, recursive));
		List<TemplateDetails> templates = TemplatesDB.filterDeviceTemplates(allTemplates);

        return templates;
	}

	/**
	 * Vrati fiktivny korenovy adresar, je potrebny pre zobrazenie v stromovej strukture
	 * v editore ked je mozne vybrat aj korenovy adresar
	 * @return
	 */
	public static GroupDetails getRootGroup() {
		GroupEditorField groupEditorField = new GroupEditorField();

		//Set root parent group details into group editor field
		groupEditorField.setParentGroupDetails(null);

		// Create default root group and set editorFileds
		GroupDetails groupDetails = new GroupDetails();
		groupDetails.setGroupId(0);
		Prop prop = Prop.getInstance();
		groupDetails.setGroupName(prop.getText("stat_settings.group_id"));
		groupDetails.setFullPath("/");
		groupDetails.setEditorFields(groupEditorField);

		return groupDetails;
	}

	/**
	 * Vrati GroupDetails objekt podla zadaneho groupId
	 * @param groupId
	 * @return
	 */
	public static GroupDetails getGroup(int groupId) {
		if (groupId == 0) {
			return getRootGroup();
		} else {
			return GroupsDB.getInstance().getGroup(groupId);
		}
	}

	/**
	 * Vrati DocDetails z cache (BasicDoc), je mozne zadat aj -1 pre vratenie cisteho dokumentu
	 * @param docId
	 * @return
	 */
	public static DocDetails getBasicDoc(int docId) {
		DocDetails doc;
		if (docId < 1) {
			doc = new DocDetails();
            doc.setDocId(0);
            doc.setEditorFields(null);
            doc.setTitle(Prop.getInstance().getText("editor.json.pathNotSet"));
		} else {
			doc = DocDB.getInstance().getBasicDocDetails(docId, true);
		}
		return doc;
	}

	/**
	 * Vrati zoznam hlaviciek pre zobrazenie v DT/e
	 * @param addFromTemplateAndEmptyToSelect
	 * @return
	 */
    public List<DocDetails> getHeaderList(boolean addFromTemplateAndEmptyToSelect) {
		List<DocDetails> docs;
		GroupDetails localSystem = getLocalSystemGroup();
		if (localSystem != null){
			docs = getBasicDocDetailsByGroupRecursive(localSystem.getGroupId(), true);
		} else {
			docs = DocDB.getInstance().getDocByGroup(Constants.getInt("headerFooterGroupId"));
		}
		if (addFromTemplateAndEmptyToSelect) return addFromTemlateAndEmptyDoc(docs);
		return docs;
    }

	/**
	 * Vrati zoznam menu pre zobrazenie v DT/e
	 * @param addFromTemplateAndEmptyToSelect
	 * @return
	 */
    public List<DocDetails> getMenuList(boolean addFromTemplateAndEmptyToSelect) {
		List<DocDetails> docs;
		GroupDetails localSystem = getLocalSystemGroup();
		if (localSystem != null){
			docs = getBasicDocDetailsByGroupRecursive(localSystem.getGroupId(), true);
		} else {
			docs = DocDB.getInstance().getDocByGroup(Constants.getInt("menuGroupId"));
		}
		if (addFromTemplateAndEmptyToSelect) return addFromTemlateAndEmptyDoc(docs);
		return docs;
	}

	/**
	 * Vrati zoznam hlaviciek/paticiek pre zobrazenie v DT/e
	 * @param addFromTemplateAndEmptyToSelect
	 * @return
	 */
	public List<DocDetails> getHeaderFooterMenuList(boolean addFromTemplateAndEmptyToSelect) {
		List<DocDetails> docs;
		GroupDetails localSystem = getLocalSystemGroup();
		if (localSystem != null){
			docs = getBasicDocDetailsByGroupRecursive(localSystem.getGroupId(), true);
		} else {
			DocDB docDB = DocDB.getInstance();
			docs = docDB.getDocByGroup(Constants.getInt("headerFooterGroupId"));
			docs.addAll(docDB.getDocByGroup(Constants.getInt("menuGroupId")));
		}
		if (addFromTemplateAndEmptyToSelect) return addFromTemlateAndEmptyDoc(docs);
		return docs;
	}

	private List<DocDetails> addFromTemlateAndEmptyDoc(List<DocDetails> list)
	{
		addEmptyDoc(list, -2);
		addFromTemlateDoc(list);
		return list;
	}

	/**
	 * Do zoznamu DocDetails objektov prida na prvu poziciu fiktivny DocDetails
	 * s hodnotou "Zo sablony" a id -2
	 * @param list
	 * @return
	 */
    public List<DocDetails> addFromTemlateDoc(List<DocDetails> list)
	{
		DocDetails fromTemplate = new DocDetails();
		fromTemplate.setDocId(-1);
        fromTemplate.setTitle(prop.getText("editor.fromTemplate"));

		list.add(0, fromTemplate);

		return list;
    }

	/**
	 * Do zoznamu DocDetails objektov prida na prvu poziciu fiktivny DocDetails
	 * s hodnotou "Ziadna" a zadanym emptyDocId
	 * @param list
	 * @param emptyDocId - ID prazdneho dokumentu
	 * @return
	 */
	public List<DocDetails> addEmptyDoc(List<DocDetails> list, int emptyDocId)
	{
		DocDetails empty = new DocDetails();
		empty.setDocId(emptyDocId);
		empty.setTitle(prop.getText("editor.empyDoc"));

		list.add(0, empty);

		return list;
	}

	/**
	 * Ziska zoznam stranok z lokalneho system adresara vratane jeho podadresarov (PRVEJ UROVNE)
	 * @param groupId
	 * @param titleIncludePath - ak je true, bude vrateny objekt kopia povodneho a title bude upravene tak, ze obsahuje cestu (pre ciselniky)
	 * @return
	 */
	public static List<DocDetails> getBasicDocDetailsByGroupRecursive(int groupId, boolean titleIncludePath) {
		DocDB docDB = DocDB.getInstance();
		List<DocDetails> localDocsInGroup = filterUnavailableDocs(docDB.getBasicDocDetailsByGroup(groupId, -1));
		List<GroupDetails> subGroups = GroupsDB.getInstance().getGroups(groupId);
		Prop propSystem = Prop.getInstance(Constants.getString("defaultLanguage"));
        String trashDirName = propSystem.getText("config.trash_dir");
		for (GroupDetails subGroup : subGroups) {
			//trash preskakujeme
			if (trashDirName.equals(subGroup.getGroupName())) continue;

			List<DocDetails> subDocs = docDB.getBasicDocDetailsByGroup(subGroup.getGroupId(), -1);
			for (DocDetails subDoc : subDocs) {
				if (subDoc.isAvailable()==false) continue;

				if (titleIncludePath) {
					//upravime meno, aby obsahovalo aj meno adresara, aby bolo zrejme od kial pochadza
					DocDetails menuDoc = new DocDetails();
					menuDoc.setDocId(subDoc.getDocId());
					menuDoc.setTitle(subGroup.getGroupName()+"/"+subDoc.getTitle());
					localDocsInGroup.add(menuDoc);
				} else {
					localDocsInGroup.add(subDoc);
				}
			}
		}
		return localDocsInGroup;
	}

	/**
	 * Vrati lokalny /System adresar
	 * @return
	 */
    private GroupDetails getLocalSystemGroup() {
		if (localSystemGroup == null && Constants.getBoolean("templatesUseDomainLocalSystemFolder")) {
			localSystemGroup = GroupsDB.getInstance().getLocalSystemGroup();
		}

		return localSystemGroup;
	}

	/**
	 * Vrati nastaveny GroupDetails objekt
	 * @return
	 */
	public GroupDetails getGroup() {
		if (groupId > 0) {
			return GroupsDB.getInstance().getGroup(groupId);
		}

		return null;
	}

	/**
	 * Vrati ciselnik pre moznost interny adresar
	 * @return
	 */
	public List<LabelValueDetails> getOptionsInternal() {
		List<LabelValueDetails> list = new ArrayList<>();
		list.add(new LabelValueDetails(prop.getText("editor.available-public"), "false"));
		list.add(new LabelValueDetails(prop.getText("editor.notavailable-notpublic"), "true"));
		return list;
	}

	/**
	 * Vrati ciselnik pre zoznam sablon
	 */
	public List<LabelValueDetails> getOptionsTemplates(UserDetails currentUser, GroupDetails group) {
		TemplatesDB templatesDB = TemplatesDB.getInstance();
		List<TemplateDetails> allTemps;
		if (group != null) allTemps = templatesDB.getTemplates(group.getGroupId(), group.getTempId());
		else allTemps = templatesDB.getTemplatesSaved();

		List<TemplateDetails> templateDetailsList = TemplatesDB.filterTemplatesByUser(currentUser, allTemps);
		List<LabelValueDetails> templateNames = new ArrayList<>();

		templateDetailsList.forEach(templateDetails ->
			templateNames.add(new LabelValueDetails(templateDetails.getTempName(), String.valueOf(templateDetails.getTempId())))
		);

		return templateNames;
	}

	/**
	 * Vrati ciselnik pre vyber jazyka
	 */
	public List<LabelValueDetails> getOptionsLanguages(HttpServletRequest request) {
		LayoutService ls = new LayoutService(request);
		List<LabelValueDetails> incompleteLanguages = ls.getLanguages(false, true);
		List<LabelValueDetails> finalLanguages = new ArrayList<>();

		finalLanguages.add(new LabelValueDetails(prop.getText("groupedit.lng.default"), ""));
		finalLanguages.addAll(incompleteLanguages);

		return finalLanguages;
	}

	/**
	 * Vrati ciselnik pre vyber novej stranky
	 * @return
	 */
	public List<LabelValueDetails> getOptionsNewPageHTMLCode() {
		List<LabelValueDetails> list = new ArrayList<>();
		list.add(new LabelValueDetails(prop.getText("groupedit.new_page_template.empty"), "-1"));
		DocDB docDB = DocDB.getInstance();
		List<DocDetails> pageTemps = docDB.getDocByGroup(Constants.getInt("tempGroupId"));
		for (DocDetails pageTemp : pageTemps) {
			list.add(new LabelValueDetails(pageTemp.getTitle(), String.valueOf(pageTemp.getDocId())));
		}
		return list;
	}

	/**
	 * Vrati ciselnik pre moznosti navigacnej listy
	 * @param isLogged
	 * @return
	 */
	public List<LabelValueDetails> getOptionsNavbar(boolean isLogged) {
		List<LabelValueDetails> list = new ArrayList<>();
		if (isLogged) list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_same_as_normal"), "null"));
		else list.add(new LabelValueDetails(prop.getText("editor.navbar.same_as_menu"), "null"));

		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_onlydefault"), String.valueOf(GroupDetails.MENU_TYPE_ONLYDEFAULT)));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_hidden"), String.valueOf(GroupDetails.MENU_TYPE_HIDDEN)));
		//list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_nosub"), String.valueOf(GroupDetails.MENU_TYPE_NOSUB)));
		//list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_normal"), String.valueOf(GroupDetails.MENU_TYPE_NORMAL)));
		return list;
	}

	/**
	 * Vrati ciselnik pre moznost mapy stranok
	 * @param isLogged
	 * @return
	 */
	public List<LabelValueDetails> getOptionsSitemap(boolean isLogged) {
		List<LabelValueDetails> list = new ArrayList<>();
		if (isLogged) list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_same_as_normal"), "null"));
		else list.add(new LabelValueDetails(prop.getText("editor.navbar.same_as_menu"), "null"));

		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_onlydefault"), String.valueOf(GroupDetails.MENU_TYPE_ONLYDEFAULT)));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_hidden"), String.valueOf(GroupDetails.MENU_TYPE_HIDDEN)));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_nosub"), String.valueOf(GroupDetails.MENU_TYPE_NOSUB)));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_normal"), String.valueOf(GroupDetails.MENU_TYPE_NORMAL)));
		return list;
	}

	/**
	 * Vrati ciselnik pre sposob zobrazenia v menu
	 * @param isLogged
	 * @return
	 */
	public List<LabelValueDetails> getMenuType(boolean isLogged) {
		List<LabelValueDetails> list = new ArrayList<>();
		if (isLogged) list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_same_as_normal"), "-1"));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_onlydefault"), String.valueOf(GroupDetails.MENU_TYPE_ONLYDEFAULT)));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_hidden"), String.valueOf(GroupDetails.MENU_TYPE_HIDDEN)));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_nosub"), String.valueOf(GroupDetails.MENU_TYPE_NOSUB)));
		list.add(new LabelValueDetails(prop.getText("groupedit.menu_type_normal"), String.valueOf(GroupDetails.MENU_TYPE_NORMAL)));
		return list;
	}

	/**
	 * Vrati zoznam PerexGroupBean objektov
	 * @param recursive - ak je nastavene na true vrati aj PerexGroupBean z podadresarov
	 * @return
	 */
	public List<PerexGroupBean> getPerexGroups(boolean recursive) {
		List<PerexGroupBean> perexGroups = DocDB.getInstance().getPerexGroups(groupId, recursive);
		return perexGroups;
	}

	/**
	 * Pri zmene atributu nastavi jeho hodnotu do databazy dynamickym SQL prikazom
	 * @param groupId - ID adresara
	 * @param attributeName - meno DB stlpca
	 * @param attributeValue - hodnota
	 */
	public void  setAttributeToSubgroups(int groupId, String attributeName, Object attributeValue){
		GroupsDB groupsDB = GroupsDB.getInstance();
        Connection db_conn = DBPool.getConnection();
        PreparedStatement ps = null;
        String groupIds = groupsDB.getSubgroupsIds(groupId);

        Adminlog.add(Adminlog.TYPE_GROUP, "Force "+DB.removeSlashes(attributeName)+" to subgroups: " + groupIds + " value=" + attributeValue, groupId, -1);

        try {
			ps = db_conn.prepareStatement("UPDATE groups SET " + DB.removeSlashes(attributeName) + "=? WHERE group_id IN (" + groupIds + ")");

			if (attributeValue instanceof String) ps.setString(1, (String)attributeValue);
			else if (attributeValue instanceof Integer) ps.setInt(1, ((Integer)attributeValue).intValue());
			else if (attributeValue instanceof Boolean) ps.setBoolean(1, ((Boolean)attributeValue).booleanValue());
			else ps.setObject(1, attributeValue);

            ps.execute();
        } catch (Exception ex) {
            Logger.error(WebpagesService.class, ex);
        } finally {
            try {
                if (ps != null)
                    ps.close();
                if (db_conn != null)
                    db_conn.close();
            } catch (Exception ex) {
                Logger.error(WebpagesService.class, ex);
            }
		  }

		  //mame zmeny je najlepsie refreshnut celu GroupsDB
		  GroupsDB.getInstance(true);
    }

	/**
	 * Pregeneruje URL adresy stranok v zadanom adresari
	 * @param rootGroupId
	 * @param user
	 * @param request
	 * @param onlyChangeUrlInheritGroup //Default false, ak je true bude vykonane pregenerovanie iba pre tie kt. maju urlInheritGroup = true
	 */
	public static void regenerateUrl(int rootGroupId, Identity user, HttpServletRequest request, boolean onlyChangeUrlInheritGroup)
	{
		//ziskaj zoznam stranok v adresari
		List<DocDetails> docs = DocDB.getInstance().getDocByGroup(rootGroupId);
		EditorForm ef;
		for (DocDetails doc : docs)
		{

			if(onlyChangeUrlInheritGroup) {
				//Ak ma doc urlInheritGroup != true preskoc tento doc a nepregeneruj url
				if(Boolean.TRUE.equals(doc.getUrlInheritGroup())==false && Boolean.TRUE.equals(doc.getGenerateUrlFromTitle())==false) {
					continue;
				}
			}

			ef = EditorDB.getEditorForm(request, doc.getDocId(), -1, rootGroupId);
			//out.println("<strong>"+ef.getTitle()+"</strong> [docid:"+ef.getDocId()+"] - "+ef.getVirtualPath());

			if (ef.getVirtualPath().contains("*"))
			{
				//out.println(" skipping (contains *)<br/>");
				continue;
			}

			ef.setVirtualPath("");

			if (Boolean.TRUE.equals(doc.getUrlInheritGroup())) ef.setVirtualPath(doc.getEditorVirtualPath());

			//nastav aktualneho usera
			ef.setAuthorId(user.getUserId());
			ef.setPublish("1");

			EditorDB.saveEditorForm(ef, request);

			//out.println(" -> " + ef.getVirtualPath()+"<br>");

			EditorDB.cleanSessionData(request);

			//out.flush();
		}

		//out.flush();

		//rekurzivne sa zavolaj na podadresare
		List<GroupDetails> subGroups = GroupsDB.getInstance().getGroups(rootGroupId);
		for (GroupDetails group : subGroups)
		{
			regenerateUrl(group.getGroupId(), user, request, onlyChangeUrlInheritGroup);
		}
		//out.flush();
	}

	/**
	 * Zo zoznamu DocDetails odstrani objekty, ktore nie su dostupne na zobrazenie (available=false)
	 * @param original
	 * @return
	 */
	private static List<DocDetails> filterUnavailableDocs(List<DocDetails> original) {
		List<DocDetails> filtered = new ArrayList<>();
		for (DocDetails doc : original) {
			if (doc.isAvailable()==false) continue;
			filtered.add(doc);
		}
		return filtered;
	}

	/**
	 * Return list of groups (from this domain) that use specific template.
	 * @param tempId id of template that group must use
	 * @return list of groups
	 */
	public List<GroupDetails> getGroupsByTemplateId(int tempId) {

		GroupsDB groupsDB = GroupsDB.getInstance();
		List<GroupDetails> result = new ArrayList<>();

		for(GroupDetails group : groupsDB.getGroupsAll()) {
			if(group.getTempId() == tempId)
				result.add(group);
		}

		return filterGroupsByCurrentDomain(result);
	}

	/**
	 * Vrati zoznam adresarov podla zadanej skupiny pouzivatelov
	 * @param userGroupId
	 * @return
	 */
	public List<GroupDetails> getGroupsByPasswordProtected(int userGroupId) {

		GroupsDB groupsDB = GroupsDB.getInstance();
		List<GroupDetails> result = new ArrayList<>();

		for(GroupDetails group : groupsDB.getGroupsAll()) {

			//Its string of user_groups ids, separated by column
			String passwordProtected = group.getPasswordProtected();
			if (Tools.isEmpty(passwordProtected)) continue;

			//Split string to get ids
			String[] idsArray = passwordProtected.split(",");

			//Loop array of ids, and if id from passwordProtected if same as userGroupId push group to result list
			for(int i = 0; i < idsArray.length; i++) {

				if(Integer.parseInt(idsArray[i]) == userGroupId) {
					result.add(group);
				}
			}
		}

		return filterGroupsByCurrentDomain(result);
	}

	/**
	 * Vrati JPA podmienku pre ziskanie naposledy upravenych stranok
	 * @param userId
	 * @return
	 */
	private static Specification<DocDetails> getRecentPagesConditions(int userId) {
		return (Specification<DocDetails>) (root, query, builder) -> {
			final List<Predicate> predicates = new ArrayList<>();

			int domainId = CloudToolsForCore.getDomainId();
			Prop propSystem = Prop.getInstance(Constants.getString("defaultLanguage"));
			String trashDirName = propSystem.getText("config.trash_dir");

			predicates.add(builder.equal(root.get("authorId"), userId));
			predicates.add(builder.notLike(root.get("virtualPath"), "/files/%"));
			//toto uz nedavame, chceme zobrazit aj rozpracovane/vypnute stranky predicates.add(builder.isTrue(root.get("available")));

			if (InitServlet.isTypeCloud()) predicates.add(builder.equal(root.get("rootGroupL1"), domainId));
			else if (domainId > 0) {
				//ziskaj zoznam ROOT adresarov v zadanej domene
				GroupsDB groupsDB = GroupsDB.getInstance();
				GroupDetails domainGroup = groupsDB.getGroup(domainId);
				if (domainGroup != null && Tools.isNotEmpty(domainGroup.getDomainName())) {
					List<GroupDetails> rootGroups = groupsDB.getGroups(0);
					List<Integer> groupIds = new ArrayList<>();
					for (GroupDetails rootGroup : rootGroups) {
						if (rootGroup.getDomainName().equalsIgnoreCase(domainGroup.getDomainName())==false) continue;

						groupIds.add(rootGroup.getGroupId());
					}
					if (groupIds.isEmpty()==false) {
						predicates.add(root.get("rootGroupL1").in(groupIds));
					}

					//Vylúč všetky kôš adresáre
					List<Integer> groupIdsTrash = new ArrayList<>();
					for (GroupDetails group : groupsDB.getGroupsAll()) {
						if (trashDirName.equals(group.getFullPath())==false) continue;

						groupIdsTrash.add(group.getGroupId());
					}
					if (groupIdsTrash.isEmpty()==false) {
						predicates.add(builder.not(root.get("groupId").in(groupIdsTrash)));
						predicates.add(builder.not(root.get("rootGroupL2").in(groupIdsTrash)));
					}
				}
			}
			//Set order
			query.orderBy(builder.desc(root.get("dateCreated")));

			return builder.and(predicates.toArray(new Predicate[predicates.size()]));
		};
	}

	/**
	 * Vrati JPA podmienku pre zobrazenie podla zadaneho groupId a pripadne rekurzivneho zobrazenia
	 * @param groupId
	 * @param recursive
	 * @return
	 */
	public static List<Predicate> getGroupIdCondition(int groupId, boolean recursive, Root<DocDetails> root, CriteriaBuilder builder) {
		final List<Predicate> predicates = new ArrayList<>();

		if (recursive) {
			GroupsDB groupsDB = GroupsDB.getInstance();
			List<GroupDetails> subGroups = groupsDB.getGroupsTree(groupId, true, true);
			List<Integer> groupIds = subGroups.stream().map(GroupDetails::getGroupId).collect(Collectors.toList());
			if (groupIds.size()==1) {
				//ak sa jedna o posledny uzol daj to ako klasicku where kvoli efektivite
				predicates.add(builder.equal(root.get("groupId"), groupIds.get(0)));
			} else if (groupIds.isEmpty()==false) {
				predicates.add(root.get("groupId").in(groupIds));
			}
		} else if (groupId>0)  {
			predicates.add(builder.equal(root.get("groupId"), groupId));
		}
		return predicates;
	}

	/**
	 * Vykona nastavenie EditorFields atributov pred vratenim z REST rozhrania
	 * @param entity
	 * @param action
	 * @param request
	 * @return
	 */
    public static DocBasic processFromEntity(DocBasic entity, ProcessItemAction action,  HttpServletRequest request, boolean addFields) {

        int groupId = Tools.getIntValue(request.getParameter("groupId"), Constants.getInt("rootGroupId"));

        if (ProcessItemAction.GETONE.equals(action) && entity==null) {
            entity = new DocDetails();
        }

        if(entity != null) {

			if (ProcessItemAction.GETONE.equals(action)==false) entity.setData(DATA_NOT_LOADED);

			//Get doc author
			UserDetails user = UsersDB.getUser(entity.getAuthorId());
			if (user != null) {
				entity.setAuthorName(user.getFullName());
				entity.setAuthorEmail(user.getEmail());
				entity.setAuthorPhoto(user.getPhoto());
			} else {
				entity.setAuthorName("");
				entity.setAuthorEmail("");
				entity.setAuthorPhoto("");
			}

			if(groupId == Constants.getInt("systemPagesRecentPages")) {
				//There is no need edit for this doc's
			} else {
				boolean linkTypeHtml = false;
				if (Constants.getInt("linkType") == Constants.LINK_TYPE_HTML) {
					linkTypeHtml = true;
				}

				if (linkTypeHtml) {
					entity.setDocLink(entity.getVirtualPath());
				}

				//toto nerobime, lebo tam ma byt skutocne hodnota z DB entity.setNavbar(groupsDB.getNavbarNoHref(entity.getGroupId()));
			}

            DocEditorFields def = entity.getEditorFields();
			if (def == null) def = new DocEditorFields();
			boolean loadSubQueries = ProcessItemAction.GETONE.equals(action);
			def.fromDocDetails(entity, loadSubQueries, addFields);
			entity.setEditorFields(def);
        }
        return entity;
    }

	/**
	 * Vrati Templates dostupne pre dane groupId
	 */
	public List<TemplateDetails> getTemplates(int mustHaveTempId, boolean recursive)
	{
		//najskor potrebujeme zoznam parent skupin
		GroupsDB groupsDB = GroupsDB.getInstance();
		List<GroupDetails> parentGroups = groupsDB.getParentGroups(groupId);

		List<TemplateDetails> allTemps = TemplatesDB.getInstance().getTemplates();

	   Set<TemplateDetails> ret = new HashSet<> ();

	   //Loop all Templates and for each one call isGroupAvailable method (method will return true if this Template is availaible)
	   for (TemplateDetails temp : allTemps) {

		  if (temp.getTempId()<1) continue;

		  //List of Group Ids where temp is available
		  int[] tempAvailableGroups = temp.getAvailableGroupsInt();

		  //isGroupAvailable param "recursive" is set for getting Templates for child (all subfolders - if recursive is true)
		  if (tempAvailableGroups.length == 0 || groupId == -1 || temp.getTempId() == mustHaveTempId || isGroupAvailable(tempAvailableGroups, parentGroups, recursive)) {
			  ret.add(temp);
		  }
	   }

	   if (ret.isEmpty()) {
		   ret.add(allTemps.get(0));
	   }

	   List<TemplateDetails> sortedRet = ret.stream().sorted((e1, e2) -> e1.getTempName().compareTo(e2.getTempName())).collect(Collectors.toList());

	   return (new ArrayList<TemplateDetails>(sortedRet));
	}

	/**
	 * availableGroups - array of int Ids represent in which groups is Template available
	 * groups - list of parent groups of actual selected group in jsTree
	 * recursive - True (check if Template is available not only for parent groups but also for child group (konjuction) of selected group in jsTree)
	 */
	private boolean isGroupAvailable(int[] availableGroups, List<GroupDetails> groups, boolean recursive)
	{
		//check availability for parent groups
		for (int availableGroupId : availableGroups) {
			for (GroupDetails group : groups) {
				if (group.getGroupId() == availableGroupId) return true;
			}
		}

		//check availability for child groups
		if(recursive) {

			GroupsDB groupsDB = GroupsDB.getInstance();

			for (int availableGroupId : availableGroups) {

				int[] tmpParentGroupIds = Tools.getTokensInt(groupsDB.getParents(availableGroupId), ",");

				for(int tmpParentGroupId : tmpParentGroupIds) {
					if(tmpParentGroupId == groupId) return true;
				}
			}
		}

		return false;
	}

	/**
	 * Vrati zoznam web stranok podla zadanych kriterii v options objekte pre pouzitie v DT
	 * @param options
	 * @return
	 */
    public static DatatablePageImpl<DocDetails> getAllItems(GetAllItemsDocOptions options) {
        Page<DocDetails> page = null;

		if(options.getGroupId() == Constants.getInt("systemPagesRecentPages")) {
			Specification<DocDetails> spec = WebpagesService.getRecentPagesConditions(options.getUserId());
			//Combine spec (recent pages) with columnsSpecification (serch throu columns)
			if(options.getColumnsSpecification() != null) {
				spec = spec.and(options.getColumnsSpecification());
			}
			page = ((JpaSpecificationExecutor<DocDetails>)options.getDocDetailsRepository()).findAll(spec, options.getPageable());
		} else if(options.isUserGroupIdRequested()) {
            //chceme vratit stranky podla zadaneho ID skupiny pouzivatelov, pouziva sa na zoznam stranok s danou skupinou
            page = options.getDocDetailsRepository().findAllByPasswordProtectedLike(""+options.getUserGroupId(), options.getUserGroupId()+",%", "%,"+options.getUserGroupId(), "%," + options.getUserGroupId() + ",%", options.getPageable());
        } else if(options.isTempIdRequested()) {
			//We want to return web pages that use specific template (by tempId)
			page = options.getDocDetailsRepository().findAllByTempId(options.getTempId(), options.getPageable());
		} else if("true".equals(options.getRequest().getParameter("auditVersion"))) {
			/** We want all web pages sorted by date of change **/

			if(!options.getCurrentUser().isEnabledItem("cmp_adminlog")) throw new IllegalArgumentException("Access is denied");
			page = options.getDocDetailsRepository().findAllByOrderByDateCreatedDesc(options.getPageable());

		} else if("true".equals(options.getRequest().getParameter("isSearchVersion"))) {
			// return empty page, we need to have search text to get data (processed in addSpecSearch)
			page = new DatatablePageImpl<>(new ArrayList<>());
		} else {
			if (GroupsDB.isGroupEditable(options.getCurrentUser(), options.getGroupId())) {
				Map<String, String> params = new HashMap<>();
				if (options.getRequest()!=null) {
					params.putAll(DatatableRestControllerV2.getParamsMap(options.getRequest()));
				}
				//override groupId from options
				params.put("groupId", "" + options.getGroupId());

				@SuppressWarnings("java:S1602")
				Specification<DocDetails> spec = (Specification<DocDetails>) (root, query, builder) -> {
					final List<Predicate> predicates = new ArrayList<>();
					addSpecSearch(params, predicates, root, builder, options.getCurrentUser());
					return builder.and(predicates.toArray(new Predicate[predicates.size()]));
				};

				page = options.getDocDetailsRepository().findAll(spec, options.getPageable());
			} else {
				//pridaj stranky ak ma specialne nastavene z tohto adresara
				if (Tools.isNotEmpty(options.getCurrentUser().getEditablePages())) {
					List<DocDetails> docs = UserTools.getEditablePages(options.getCurrentUser().getEditablePages());
					List<DocDetails> availableDocs = new ArrayList<>();
            		for (DocDetails doc : docs) {
						if (doc.getGroupId() == options.getGroupId()) availableDocs.add(doc);
					}
					if (availableDocs.isEmpty()==false) page = new DatatablePageImpl<>(availableDocs);
				}
			}
		}

		return preparePage(page, options);
    }

	public static DatatablePageImpl<DocDetails> preparePage(Page<DocDetails> page, GetAllItemsDocOptions options) {
        DatatablePageImpl<DocDetails> pageImpl;

		if (page != null) pageImpl = new DatatablePageImpl<>(page);
		else pageImpl = new DatatablePageImpl<>(new ArrayList<>());

        addOptions(pageImpl, options);

        return pageImpl;
    }

	/**
	 * Add options to DatatablePage object
	 * @param pageImpl - current response Page object
	 * @param options - options object
	 */
	public static void addOptions(DatatablePageImpl<DocDetails> pageImpl, GetAllItemsDocOptions options) {
		Prop prop = Prop.getInstance(options.getRequest());
        WebpagesService ws = new WebpagesService(options.getGroupId(), options.getCurrentUser(), prop, options.getRequest());

		pageImpl.addOptions("tempId", ws.getTemplates(options.isRecursiveSubfolders()), "tempName", "tempId", true);
        pageImpl.addOptions("menuDocId,rightMenuDocId", ws.getMenuList(true), "title", "docId", false);
        pageImpl.addOptions("headerDocId,footerDocId", ws.getHeaderList(true), "title", "docId", false);
		pageImpl.addOptions("tempFieldADocId,tempFieldBDocId,tempFieldCDocId,tempFieldDDocId", ws.getHeaderFooterMenuList(true), "title", "docId", false);
        pageImpl.addOptions("editorFields.emails", UserGroupsDB.getInstance().getUserGroupsByTypeId(UserGroupDetails.TYPE_EMAIL), "userGroupName", "userGroupId", false);
        pageImpl.addOptions("editorFields.permisions", UserGroupsDB.getInstance().getUserGroupsByTypeId(UserGroupDetails.TYPE_PERMS), "userGroupName", "userGroupId", false);
        pageImpl.addOptions("perexGroups", ws.getPerexGroups(options.isRecursiveSubfolders()), "perexGroupName", "perexGroupId", false);

		//optiony pre ikonu
		pageImpl.addOptions("editorFields.statusIcons", getStatusIconOptions(options, prop), "label", "value", false);

		//attributes group
		if (options.getDocAtrDefRepository()!=null) {
			pageImpl.addOptions("editorFields.attrGroup", options.getDocAtrDefRepository().findDistinctGroups(CloudToolsForCore.getDomainId()) , "", "", false);
		}

		boolean addFields = true;
        for (DocDetails entity : pageImpl.getContent()) {
			WebpagesService.processFromEntity(entity, ProcessItemAction.GETALL, options.getRequest(), addFields);
			addFields = false;
		}
	}

	/**
	 * Vrati option pre DT so zoznamom stavovych ikon
	 * riesi kontrolu prav na app abtesting (ikony sa zobrazia len ak ma pouzivatel pravo)
	 * @param options
	 * @param prop
	 * @return
	 */
	private static List<LabelValue> getStatusIconOptions(GetAllItemsDocOptions options, Prop prop) {
		List<LabelValue> icons = new ArrayList<>();

		icons.add(new LabelValue("<i class=\"ti ti-star\"></i> "+prop.getText("editor.main_site"), "searchDefaultPage"));
		icons.add(new LabelValue("<i class=\"ti ti-map-pin\"></i> "+prop.getText("webpages.icons.showInMenu"), "showInMenu:true"));
		icons.add(new LabelValue("<i class=\"ti ti-map-pin-off\"></i> "+prop.getText("webpages.icons.notShowInMenu"), "showInMenu:false"));
		icons.add(new LabelValue("<i class=\"ti ti-lock\"></i> "+prop.getText("webpages.icons.onlyForLogged"), "passwordProtected:notEmpty"));
		icons.add(new LabelValue("<span style=\"color: #FF4B58\">"+prop.getText("webpages.icons.disabled")+"</span>", "available:false"));
		icons.add(new LabelValue("<i class=\"ti ti-external-link\"></i> "+prop.getText("webpages.icons.externalLink"), "externalLink:notEmpty"));
		icons.add(new LabelValue("<i class=\"ti ti-eye-off\"></i> "+prop.getText("webpages.icons.notSearchable"), "searchable:false"));

		if (options.getCurrentUser().isEnabledItem("cmp_abtesting")) {
			icons.add(new LabelValue("<i class=\"ti ti-a-b\"></i> "+prop.getText("webpages.icons.avariant"), "virtualPath:!%"+Constants.getString("ABTestingName")+"%"));
			icons.add(new LabelValue("<i class=\"ti ti-a-b\"></i> "+prop.getText("webpages.icons.bvariant"), "virtualPath:%"+Constants.getString("ABTestingName")+"%"));
		}

		return icons;
	}

	/**
	 * Vrati DocDetails (ako docDB.getBasicDocDetails) zo zadanej URL adresy
	 * Ta moze byt v tvare:
	 * http://domena.sk/adresar/stranka.html?nejakyParameter=aaa
	 * http://domena.sk/showdoc.do?docid=xxx&nejakyParameter=aaa
	 * /adresar/stranka.html
	 * /showdoc.do?docid=xxx
	 * @param url
	 * @return
	 */
	public static DocDetails getBasicDocFromUrl(String url) {
		DocDetails doc = null;
		try {
			if (Tools.isNotEmpty(url)) {
				if (url.startsWith("http")==false) {
					url = "http://"+CloudToolsForCore.getDomainName()+url;
				}
				DocDB docDB = DocDB.getInstance();
				int to = url.indexOf("/", 8);
				if (to==-1) to = url.indexOf(":", 8);
				if (to==-1) to = url.indexOf("?", 8);

				String domainName = url.substring(url.indexOf("://")+3, to);
				int portDelimiter = domainName.indexOf(":");
				if (portDelimiter > 0) domainName = domainName.substring(0, portDelimiter);

				to = url.indexOf("/", 8);
				String path = "/";
				if (to>0) path = url.substring(to);
				path = Tools.replace(path, "//", "/");

				int docId = -1;
				if (path.startsWith("/showdoc.do")) {
					docId = Tools.getIntValue(Tools.getParameterFromUrl(path, "docid"), -1);
				} else {
					String pathNoParams = path;
					int i = pathNoParams.indexOf("?");
					if (i>0) pathNoParams = pathNoParams.substring(0, i);

					docId = docDB.getDocIdFromURLImpl(pathNoParams, domainName);
				}
				if (docId > 0) {
					doc = docDB.getBasicDocDetails(docId, false);
				}
			}
		} catch (Exception e) {
			Logger.error(WebpagesService.class, e);
		}
		return doc;
	}

	/**
	 * Overi, ci zadany pouzivatel ma zapnute zobrazovanie web stranok v stromovej strukture
	 * @param user
	 * @return
	 */
	public static boolean isTreeShowPages(UserDetails user) {
		//ak je zapnute zobrazenie zoznamu stranok pre novu stranku musim spravit reload
        //ostatne ako zmena adresara vyvolava reload uz standardne
        AdminSettingsService ass = new AdminSettingsService(user);
        return ass.getJsonBooleanValue(ADMIN_SETTINGS_KEY, "showPages");
	}

	public static int getUserFirstEditableGroup(Identity user)
	{
		int[] editableGroups = Tools.getTokensInt(user.getEditableGroups(), ",");
		if (editableGroups!=null && editableGroups.length>0)
		{
			for (int groupId : editableGroups)
			{
				if (groupId > 0)
				{
					return groupId;
				}
			}
		}
		return -1;
	}

	/**
	 * Vrati posledne zapamatane groupId pre daneho pouzivatela, alebo prve jeho nastavene podla prav, alebo defaultne
	 * @param user
	 * @param request
	 * @return
	 */
	public static int getUserLastGroupId(Identity user, HttpServletRequest request)
	{
		HttpSession session = request.getSession();

		int group_id = Constants.getInt("rootGroupId");

		int groupId = getUserFirstEditableGroup(user);
		if (groupId > 0)
		{
			group_id = groupId;
		}

		try
		{
			if (request.getParameter("groupid") != null)
			{
				group_id = Integer.parseInt(request.getParameter("groupid"));
			}
			else
			{
				//skus ziskat data zo session
				if (session.getAttribute(Constants.SESSION_GROUP_ID) != null)
				{
					group_id = Integer.parseInt((String) session.getAttribute(Constants.SESSION_GROUP_ID));
				}

			}
		}
		catch (Exception ex)
		{

		}

		return group_id;
	}

	/**
	 * Add special conditions to search query based on request parameters
	 */
	public static void addSpecSearch(Map<String, String> params, List<Predicate> predicates, Root<DocDetails> root, CriteriaBuilder builder, Identity user) {

        SpecSearch<DocDetails> specSearch = new SpecSearch<>();
        GroupsDB groupsDB = GroupsDB.getInstance();

		//remove groupId predicate which was auto binded, it will be set later in this method depending on recursive attribute
		JpaTools.removePredicateWithName("groupId", predicates);

        //vyhladanie na zaklade Meno autora, hladane v DB tabulke nasledne v stlpci authorId
        String searchAuthorName = params.get("searchAuthorName");
        if (searchAuthorName != null)
            specSearch.addSpecSearchUserFullName(searchAuthorName, "authorId", predicates, root, builder);

        String permissions = params.get("searchEditorFields.permisions");
        if (permissions != null)
            specSearch.addSpecSearchPasswordProtected(permissions, "passwordProtected", predicates, root, builder);

        String emails = params.get("searchEditorFields.emails");
        if (emails != null)
            specSearch.addSpecSearchPasswordProtected(emails, "passwordProtected", predicates, root, builder);

        int userGroupId = Tools.getIntValue(params.get("userGroupId"), -1);
        if (userGroupId > 0)
            specSearch.addSpecSearchPasswordProtected(userGroupId, "passwordProtected", predicates, root, builder);

		String groupIdListParam = params.get("groupIdList");
		if (Tools.isEmpty(groupIdListParam) && Tools.isNotEmpty(params.get("groupId"))) {
			groupIdListParam = params.get("groupId");
			if ("true".equals(params.get("recursive"))) groupIdListParam+="*";
		}

		if(Boolean.TRUE.equals( Tools.getBooleanValue(params.get("isSearchVersion"), false) )) {
			// SEARCH_ALL VERSION
			SearchService.getWebPagesData(params, user, predicates, builder, root);
		} else {
			String[] groupIdListArray = Tools.getTokens(groupIdListParam, ",", true);
			int groupId;
			if (groupIdListArray.length>0) {
				List<Integer> groupIds = new ArrayList<>();
				for (String id : groupIdListArray) {
					if (id.endsWith("*") && id.length()>1) {

						groupId = Tools.getIntValue(id.substring(0, id.length()-1), -1);
						GroupDetails baseGroup = groupsDB.getGroup(groupId);
						//to filter FullTextIndex of files
						final boolean baseGroupIsFiles = baseGroup.getFullPath().contains("/files");
						List<GroupDetails> subGroups = groupsDB.getGroupsTree(groupId, true, true);

						groupIds.addAll(subGroups.stream()
							.filter(g -> baseGroupIsFiles || !g.getFullPath().contains("/files"))
							.map(g -> g.getGroupId())
							.collect(Collectors.toList()));

					} else {

						groupIds.add(Tools.getIntValue(id, -1));

					}
				}
				if (groupIds.size()==1) {
					predicates.add(builder.equal(root.get("groupId"), groupIds.get(0)));
				} else if (groupIds.size()>1) {
					predicates.add(root.get("groupId").in(groupIds));
				}

				//filter iba hlavnych stranok adresarov
				String searchStatusIcon = params.get("searchEditorFields.statusIcons");
				if ("searchDefaultPage".equals(searchStatusIcon) && groupIds.size()>0) {
					//ziskaj zoznam default_doc_id pre zvolene adresare
					String ids = groupIds.stream().map(String::valueOf).collect(Collectors.joining(","));
					List<Integer> defaultDocIds = (new SimpleQuery()).forListInteger("SELECT DISTINCT default_doc_id FROM groups WHERE group_id IN ("+ids+")");
					//pridaj to ako predikat
					predicates.add(root.get("id").in(defaultDocIds));
				}
			}
		}
    }

	public static DocDetails getOneItem(long id, int groupId, int historyId, EditorFacade editorFacade, DocAtrDefRepository docAtrDefRepository, List<NotifyBean> notifyList, HttpServletRequest request) {
		if (groupId == Constants.getInt("systemPagesDocsToApprove")) {
            //pre tento pripad mame otocene docid a historyid, ale principialne dostavame v id hodnotu historyid, takze to potrebujeme takto nacitat
            historyId = (int)id;
            //ziskaj docid podla historyid
            id = (new SimpleQuery()).forInt("SELECT doc_id FROM documents_history WHERE history_id=?", historyId);
        }

        Prop prop = Prop.getInstance(request);

		DocDetails doc = editorFacade.getDocForEditor((int) id, historyId, groupId);

        if(id == -1) {
            doc.setGenerateUrlFromTitle(true);
        }

        if (ContextFilter.isRunning(request)) {
            // do editoru nahrame texty s pridanymi linkami
            doc.setData(ContextFilter.addContextPath(request.getContextPath(), doc.getData()));
		}

		if (groupId == Constants.getInt("systemPagesDocsToApprove")) {
			int docId = doc.getDocId();
			doc.setDocId(historyId);
			doc.setHistoryId(docId);
        }

		//over, ci existuju neschvalene/rozpracovane verzie, ak ano, zobraz notifikaciu
        HistoryDB historyDB = new HistoryDB("iwcm");
        List<DocDetails> history = historyDB.getHistory(doc.getDocId(), false, true);
        if (history != null && history.isEmpty()==false) {
            if (historyId < 1) {
                //ak nemame zadane historyId pridaj notifikaciu o tom, ze existuje novsia verzia
                NotifyBean notify = new NotifyBean(prop.getText("text.warning"), prop.getText("editor.notify.checkHistory"), NotifyBean.NotifyType.WARNING, 15000);
				notify.addButton(new NotifyButton(prop.getText("editor.notify.editFromHistory"), "btn btn-primary", "ti ti-history", "editFromHistory("+history.get(0).getDocId()+", "+history.get(0).getHistoryId()+")"));
                notifyList.add(notify);
            }
            request.getSession().removeAttribute("docHistory");
        }

        List<DocAtrDefEntity> atrDefs = docAtrDefRepository.findAllByDocId(MultigroupMappingDB.getMasterDocId(doc.getDocId(), true), CloudToolsForCore.getDomainId());
        atrDefs.forEach(f -> {
            if (f.getDocAtrEntities()!=null && f.getDocAtrEntities().isEmpty()==false) {
                //normally in JSON we don't want to send all DocAtrEntity relationship (it's lazy loaded ant it will be populated), it's JsonIgnored,
                //we just need first entity to be sent, so set it here for this specific case
                f.setDocAtrEntityFirst(f.getDocAtrEntities().get(0));
            }
        });
        doc.getEditorFields().setAttrs(atrDefs);

        String newPageTitleKey = request.getParameter("newPageTitleKey");
        if (Tools.isNotEmpty(newPageTitleKey) && doc.getDocId()<1) {
            doc.setTitle(prop.getText(newPageTitleKey));
        }

        return doc;
	}

	public static String getTreeSortType(UserDetails user) {
        AdminSettingsService ass = new AdminSettingsService(user);
		String sortType = ass.getJsonValue(ADMIN_SETTINGS_KEY, "treeSortType");

		if(sortType == null || Tools.isEmpty(sortType) == true) return "priority";
		else return sortType;
	}

	public static boolean isTreeSortOrderAsc(UserDetails user) {
		AdminSettingsService ass = new AdminSettingsService(user);
		return ass.getJsonBooleanValue(ADMIN_SETTINGS_KEY, "treeSortOrderAsc");
	}
}