Instruction:
作为 Java 安全专家，检测此代码中的安全漏洞：
## 内容1
### 文件路径：
wflow-consoleweb/src/main/java/org/joget/apps/app/controller/UserviewWebController.java
### 代码内容：
package org.joget.apps.app.controller;

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.joget.apm.APMUtil;
import org.joget.apps.app.dao.UserviewDefinitionDao;
import org.joget.apps.app.model.AppDefinition;
import org.joget.apps.app.model.UserviewDefinition;
import org.joget.apps.app.service.AppService;
import org.joget.apps.userview.model.Userview;
import org.joget.apps.userview.service.UserviewService;
import org.joget.apps.userview.service.UserviewThemeProcesser;
import org.joget.apps.userview.service.UserviewUtil;
import org.joget.commons.util.SecurityUtil;
import org.joget.commons.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

@Controller
public class UserviewWebController {

    @Autowired
    private UserviewService userviewService;
    @Autowired
    AppService appService;
    @Autowired
    UserviewDefinitionDao userviewDefinitionDao;

    @RequestMapping({"/userview/(*:appId)/(*:userviewId)/(~:key)","/userview/(*:appId)/(*:userviewId)","/userview/(*:appId)/(*:userviewId)/(*:key)/(*:menuId)"})
    public String view(ModelMap map, HttpServletRequest request, HttpServletResponse response, @RequestParam("appId") String appId, @RequestParam("userviewId") String userviewId, @RequestParam(value = "menuId", required = false) String menuId, @RequestParam(value = "key", required = false) String key, @RequestParam(value = "embed", required = false) Boolean embed) throws Exception {
        if (embed == null) {
            embed = false;
        }
        return embedView(map, request, response, appId, userviewId, menuId, key, embed, null);
    }

    @RequestMapping({"/embed/userview/(*:appId)/(*:userviewId)/(~:key)","/embed/userview/(*:appId)/(*:userviewId)","/embed/userview/(*:appId)/(*:userviewId)/(*:key)/(*:menuId)"})
    public String embedView(ModelMap map, HttpServletRequest request, HttpServletResponse response, @RequestParam("appId") String appId, @RequestParam("userviewId") String userviewId, @RequestParam(value = "menuId", required = false) String menuId, @RequestParam(value = "key", required = false) String key, Boolean embed, @RequestParam(value = "embed", required = false) Boolean embedParam) throws Exception {
        if (APMUtil.isGlowrootAvailable()) {
            //remove key & embed keyword from the url for better tracking
            String url = request.getRequestURL().toString();
            url = url.substring(0, url.indexOf("/userview")) + "/userview/" + appId + "/" + userviewId;
            if (menuId != null && !menuId.isEmpty()) {
                url += "/" + menuId;
            }
            APMUtil.setTransactionName(url, 1001);
        }

        // validate input
        appId = SecurityUtil.validateStringInput(appId);
        userviewId = SecurityUtil.validateStringInput(userviewId);
        menuId = SecurityUtil.validateStringInput(menuId);
        key = SecurityUtil.validateStringInput(key);
        SecurityUtil.validateBooleanInput(embed);
        SecurityUtil.validateBooleanInput(embedParam);

        if (embedParam != null && !embedParam) {
            //exit embed mode by param
            return "redirect:/web/userview/" + appId + "/" + userviewId + "/" + ((key != null )?key:"") + "/" + menuId + '?' +StringUtil.decodeURL(request.getQueryString());
        } else if (embed == null) {
            embed = true;
        }

        //check for empty key
        if (key != null && key.equals(Userview.USERVIEW_KEY_EMPTY_VALUE)) {
            key = "";
        }

        // retrieve app and userview
        AppDefinition appDef = appService.getPublishedAppDefinition(appId);
        if (appDef == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }
        map.addAttribute("appId", appDef.getId());
        map.addAttribute("appDefinition", appDef);
        map.addAttribute("appVersion", appDef.getVersion());
        map.addAttribute("key", key);
        map.addAttribute("menuId", menuId);
        map.addAttribute("embed", embed);
        map.addAttribute("queryString", request.getQueryString());
        if (userviewService.isDefaultUserview(appDef.getId(), userviewId)) {
            request.setAttribute("isDefaultUserview", Boolean.TRUE);
        }
        UserviewDefinition userview = userviewDefinitionDao.loadById(userviewId, appDef);
        if (userview != null) {
            String json = userview.getJson();
            Userview userviewObject = userviewService.createUserview(json, menuId, false, request.getContextPath(), request.getParameterMap(), key, embed);
            UserviewThemeProcesser processer = new UserviewThemeProcesser(userviewObject, request);
            map.addAttribute("userview", userviewObject);
            map.addAttribute("processer", processer);
            String view = processer.getView();
            if (view != null) {
                if (view.startsWith("redirect:")) {
                    map.clear();
                }
                return view;
            }
        }
        return "ubuilder/view";
    }

    @RequestMapping({"/ulogin/(*:appId)/(*:userviewId)/(~:key)","/ulogin/(*:appId)/(*:userviewId)","/ulogin/(*:appId)/(*:userviewId)/(*:key)/(*:menuId)"})
    public String login(ModelMap map, HttpServletRequest request, HttpServletResponse response, @RequestParam("appId") String appId, @RequestParam("userviewId") String userviewId, @RequestParam(value = "menuId", required = false) String menuId, @RequestParam(value = "key", required = false) String key, @RequestParam(value = "embed", required = false) Boolean embed) throws Exception {
        if (embed == null) {
            embed = false;
        }
        return embedLogin(map, request, response, appId, userviewId, menuId, key, embed);
    }

    @RequestMapping({"/embed/ulogin/(*:appId)/(*:userviewId)/(~:key)","/embed/ulogin/(*:appId)/(*:userviewId)","/embed/ulogin/(*:appId)/(*:userviewId)/(*:key)/(*:menuId)"})
    public String embedLogin(ModelMap map, HttpServletRequest request, HttpServletResponse response, @RequestParam("appId") String appId, @RequestParam("userviewId") String userviewId, @RequestParam(value = "menuId", required = false) String menuId, @RequestParam(value = "key", required = false) String key, Boolean embed) throws Exception {
        if (embed == null) {
            embed = true;
        }

        //check for empty key
        if (key != null && key.equals(Userview.USERVIEW_KEY_EMPTY_VALUE)) {
            key = null;
        }

        // validate input
        SecurityUtil.validateStringInput(appId);
        SecurityUtil.validateStringInput(menuId);
        SecurityUtil.validateStringInput(key);
        SecurityUtil.validateBooleanInput(embed);

        // retrieve app and userview
        AppDefinition appDef = appService.getPublishedAppDefinition(appId);
        if (appDef == null) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }
        map.addAttribute("appId", appDef.getId());
        map.addAttribute("appDefinition", appDef);
        map.addAttribute("appVersion", appDef.getVersion());
        map.addAttribute("key", key);
        map.addAttribute("menuId", menuId);
        map.addAttribute("embed", embed);
        map.addAttribute("queryString", request.getQueryString());
        UserviewDefinition userview = userviewDefinitionDao.loadById(userviewId, appDef);
        if (userview != null) {
            String json = userview.getJson();
            Userview userviewObject = userviewService.createUserview(json, menuId, false, request.getContextPath(), request.getParameterMap(), key, embed);
            UserviewThemeProcesser processer = new UserviewThemeProcesser(userviewObject, request);
            map.addAttribute("userview", userviewObject);
            map.addAttribute("processer", processer);
            String view = processer.getLoginView();
            if (view != null) {
                if (view.startsWith("redirect:")) {
                    map.clear();
                }
                return view;
            }
        }

        return "ubuilder/login";
    }

    @RequestMapping({"/userview/(*:appId)/(*:userviewId)/manifest"})
    public void manifest(ModelMap map, HttpServletRequest request, HttpServletResponse response, @RequestParam("appId") String appId, @RequestParam("userviewId") String userviewId) throws IOException {
        String manifest = UserviewUtil.getManifest(SecurityUtil.validateStringInput(appId), SecurityUtil.validateStringInput(userviewId));
        response.setContentType("application/manifest+json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.println(manifest);
    }

    @RequestMapping({"/userview/(*:appId)/(*:userviewId)/(*:key)/serviceworker"})
    public void serviceWorker(ModelMap map, HttpServletRequest request, HttpServletResponse response, @RequestParam("appId") String appId, @RequestParam("userviewId") String userviewId, @RequestParam("key") String userviewKey) throws IOException {
        String serviceWorker = UserviewUtil.getServiceWorker(SecurityUtil.validateStringInput(appId), SecurityUtil.validateStringInput(userviewId), SecurityUtil.validateStringInput(userviewKey));
        response.setContentType("application/javascript;charset=UTF-8");
        response.setHeader("Service-Worker-Allowed", (request.getContextPath().isEmpty())?"/":request.getContextPath());
        PrintWriter writer = response.getWriter();
        writer.println(serviceWorker);
    }

    @RequestMapping({"/userview/(*:appId)/(*:userviewId)/(*:key)/cacheUrls"})
    public void cacheUrls(ModelMap map, HttpServletRequest request, HttpServletResponse response, @RequestParam("appId") String appId, @RequestParam("userviewId") String userviewId, @RequestParam("key") String userviewKey) throws IOException {
        String cacheUrlsJSON = UserviewUtil.getCacheUrls(SecurityUtil.validateStringInput(appId), SecurityUtil.validateStringInput(userviewId), SecurityUtil.validateStringInput(userviewKey), request.getContextPath());
        response.setContentType("application/json;charset=UTF-8");
        PrintWriter writer = response.getWriter();
        writer.println(cacheUrlsJSON);
    }
}

## 内容2
### 文件路径：
wflow-core/src/main/java/org/joget/apps/userview/service/UserviewService.java
### 代码内容：
package org.joget.apps.userview.service;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringEscapeUtils;
import org.joget.apps.app.dao.UserviewDefinitionDao;
import org.joget.apps.app.model.AppDefinition;
import org.joget.apps.app.model.MobileElement;
import org.joget.apps.app.model.UserviewDefinition;
import org.joget.apps.app.service.AppService;
import org.joget.apps.app.service.AppUtil;
import org.joget.apps.app.service.MobileUtil;
import org.joget.apps.userview.model.CachedUserviewMenu;
import org.joget.apps.userview.model.Userview;
import org.joget.apps.userview.model.UserviewCategory;
import org.joget.apps.userview.model.UserviewMenu;
import org.joget.apps.userview.model.Permission;
import org.joget.apps.userview.model.UserviewSetting;
import org.joget.apps.userview.model.UserviewTheme;
import org.joget.commons.spring.model.Setting;
import org.joget.commons.util.LogUtil;
import org.joget.commons.util.ResourceBundleUtil;
import org.joget.commons.util.SetupManager;
import org.joget.commons.util.StringUtil;
import org.joget.directory.model.User;
import org.joget.directory.model.service.ExtDirectoryManager;
import org.joget.plugin.base.PluginManager;
import org.joget.plugin.property.service.PropertyUtil;
import org.joget.workflow.model.service.WorkflowUserManager;
import org.joget.workflow.util.WorkflowUtil;
import org.json.JSONArray;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * Service methods used to parse userview json definition to create Userview
 *
 */
@Service("userviewService")
public class UserviewService {

    @Autowired
    private PluginManager pluginManager;
    @Autowired
    private WorkflowUserManager workflowUserManager;
    @Autowired
    private AppService appService;
    @Autowired
    UserviewDefinitionDao userviewDefinitionDao;
    @Autowired
    private SetupManager setupManager;
    @Autowired
    @Qualifier("main")
    ExtDirectoryManager directoryManager;

    /**
     * Get userview setting object
     * @param appDef
     * @param json
     * @return setting
     */
    public UserviewSetting getUserviewSetting(AppDefinition appDef, String json) {
        UserviewSetting setting = null;

        //process json with hash variable
        json = AppUtil.processHashVariable(json, null, StringUtil.TYPE_JSON, null, appDef);

        User currentUser = workflowUserManager.getCurrentUser();

        Map<String, Object> requestParameters = new HashMap<String, Object>();
        requestParameters.put("appId", appDef.getAppId());
        requestParameters.put("appVersion", appDef.getVersion().toString());

        Userview userview = new Userview();

        try {
            //set userview properties
            JSONObject userviewObj = new JSONObject(json);
            userview.setProperties(PropertyUtil.getPropertiesValueFromJson(userviewObj.getJSONObject("properties").toString()));

            //set Setting
            JSONObject settingObj = userviewObj.getJSONObject("setting");
            setting = new UserviewSetting();
            setting.setProperties(PropertyUtil.getPropertiesValueFromJson(settingObj.getJSONObject("properties").toString()));

            //set theme & permission
            try {
                JSONObject themeObj = settingObj.getJSONObject("properties").getJSONObject("theme");
                UserviewTheme theme = (UserviewTheme) pluginManager.getPlugin(themeObj.getString("className"));
                theme.setProperties(PropertyUtil.getPropertiesValueFromJson(themeObj.getJSONObject("properties").toString()));
                theme.setRequestParameters(requestParameters);
                theme.setUserview(userview);
                setting.setTheme(theme);
            } catch (Exception e) {
                LogUtil.debug(getClass().getName(), "set theme error.");
            }
            try {
                if (!"true".equals(setting.getPropertyString("tempDisablePermissionChecking"))) {
                    JSONObject permissionObj = settingObj.getJSONObject("properties").getJSONObject("permission");
                    Permission permission = null;
                    String permissionClassName = permissionObj.getString("className");
                    if (permissionClassName != null && !permissionClassName.isEmpty()) {
                        permission = (Permission) pluginManager.getPlugin(permissionClassName);
                    }
                    if (permission != null) {
                        permission.setProperties(PropertyUtil.getPropertiesValueFromJson(permissionObj.getJSONObject("properties").toString()));
                        permission.setRequestParameters(requestParameters);
                        permission.setCurrentUser(currentUser);
                        setting.setPermission(permission);
                    }
                }
            } catch (Exception e) {
                LogUtil.debug(getClass().getName(), "set permission error.");
            }
            userview.setSetting(setting);
        } catch (Exception ex) {
            LogUtil.debug(getClass().getName(), "set userview setting error.");
        }

        return setting;
    }

    /**
     * Create userview fron json
     * @return
     */
    public Userview createUserview(String json, String menuId, boolean preview, String contextPath, Map requestParameters, String key, Boolean embed) {
        AppDefinition appDef = AppUtil.getCurrentAppDefinition();
        return createUserview(appDef, json, menuId, preview, contextPath, requestParameters, key, embed);
    }

    /**
     * Create userview fron json
     * @return
     */
    public Userview createUserview(AppDefinition appDef, String json, String menuId, boolean preview, String contextPath, Map requestParameters, String key, Boolean embed) {
        String permissionKey = Permission.DEFAULT;

        if (key != null && key.trim().length() == 0) {
            key = null;
        }
        if (key != null) {
            key = StringEscapeUtils.escapeHtml(key);
        }

        //process json with hash variable
        json = AppUtil.processHashVariable(json, null, StringUtil.TYPE_JSON, null, appDef);
        json = AppUtil.replaceAppMessages(json, StringUtil.TYPE_JSON);

        User currentUser = workflowUserManager.getCurrentUser();

        if (requestParameters == null) {
            requestParameters = new HashMap<String, Object>();
        }
        requestParameters = convertRequestParamMap(requestParameters);
        requestParameters.put("contextPath", contextPath);
        requestParameters.put("isPreview", Boolean.toString(preview));
        requestParameters.put("embed", Boolean.toString(embed));
        requestParameters.put("appId", appDef.getAppId());
        requestParameters.put("appVersion", appDef.getVersion().toString());

        String appId = appDef.getId();
        String appVersion = appDef.getVersion().toString();
        Userview userview = new Userview();
        userview.setParams(requestParameters);

        boolean userviewPermission = false;

        //if screenshot, set user to null (anonymous)
        User currentThreadUser = currentUser;
        boolean isScreenCapture = workflowUserManager.isCurrentUserInRole(WorkflowUserManager.ROLE_ADMIN) && "true".equalsIgnoreCase((String) requestParameters.get("_isScreenCapture"));
        if (isScreenCapture) {
            currentUser = null;
            workflowUserManager.setCurrentThreadUser(WorkflowUserManager.ROLE_ANONYMOUS);
        }

        try {
            //set userview properties
            JSONObject userviewObj = new JSONObject(json);
            userview.setProperties(PropertyUtil.getProperties(userviewObj.getJSONObject("properties")));

            //set Setting
            JSONObject settingObj = userviewObj.getJSONObject("setting");
            UserviewSetting setting = new UserviewSetting();
            setting.setProperties(PropertyUtil.getProperties(settingObj.getJSONObject("properties")));

            //set theme & permission
            try {
                JSONObject themeObj = settingObj.getJSONObject("properties").getJSONObject("theme");
                JSONObject themeProperties = themeObj.getJSONObject("properties");
                UserviewTheme theme = (UserviewTheme) pluginManager.getPlugin(themeObj.getString("className"));
                if (theme == null) {
                    String defaultTheme = ResourceBundleUtil.getMessage("generator.userview.theme");
                    theme = (UserviewTheme) pluginManager.getPlugin(defaultTheme);
                    String defaultThemePropertiesKey = "generator.userview.theme." + defaultTheme + ".properties";
                    String defaultThemeProperties = "{" + ResourceBundleUtil.getMessage(defaultThemePropertiesKey) + "}";
                    themeProperties = new JSONObject(defaultThemeProperties);
                }
                theme.setProperties(PropertyUtil.getProperties(themeProperties));
                theme.setRequestParameters(requestParameters);
                theme.setUserview(userview);
                setting.setTheme(theme);
            } catch (Exception e) {
                LogUtil.debug(getClass().getName(), "set theme error.");
            }
            try {
                if (!"true".equals(setting.getPropertyString("tempDisablePermissionChecking"))) {
                    if (settingObj.getJSONObject("properties").has("permission_rules")) {
                        JSONArray permissionRules = settingObj.getJSONObject("properties").getJSONArray("permission_rules");
                        if (permissionRules != null && permissionRules.length() > 0) {
                            for (int i = 0; i < permissionRules.length(); i++) {
                                JSONObject rule = permissionRules.getJSONObject(i);
                                if (rule.has("permission")) {
                                    JSONObject permissionObj = rule.optJSONObject("permission");
                                    userviewPermission = UserviewUtil.getPermisionResult(permissionObj, requestParameters, currentUser);
                                    if (userviewPermission) {
                                        permissionKey = rule.getString("permission_key");
                                        break;
                                    }
                                }
                            }
                        }
                    }

                    if (!userviewPermission) {
                        if (settingObj.getJSONObject("properties").has("permission")) {
                            JSONObject permissionObj = settingObj.getJSONObject("properties").getJSONObject("permission");
                            userviewPermission = UserviewUtil.getPermisionResult(permissionObj, requestParameters, currentUser);
                        } else {
                            userviewPermission = true;
                        }
                    }
                } else {
                    userviewPermission = true;
                }
            } catch (Exception e) {
                LogUtil.debug(getClass().getName(), "set permission error.");
            }
            userview.setSetting(setting);

            //set categories
            Collection<UserviewCategory> categories = new ArrayList<UserviewCategory>();

            if (userviewPermission) {
                JSONArray categoriesArray = userviewObj.getJSONArray("categories");
                for (int i = 0; i < categoriesArray.length(); i++) {
                    JSONObject categoryObj = (JSONObject) categoriesArray.get(i);

                    UserviewCategory category = new UserviewCategory();
                    category.setProperties(PropertyUtil.getProperties(categoryObj.getJSONObject("properties")));

                    //check for permission
                    JSONObject ruleObj = null;
                    if (Permission.DEFAULT.equals(permissionKey)) {
                        ruleObj = categoryObj.getJSONObject("properties");
                    } else if (categoryObj.getJSONObject("properties").has("permission_rules")) {
                        JSONObject permissionRules = categoryObj.getJSONObject("properties").getJSONObject("permission_rules");
                        if (permissionRules != null && permissionRules.has(permissionKey)) {
                            ruleObj = permissionRules.getJSONObject(permissionKey);
                        }
                    }

                    boolean hasPermis = false;
                    if (preview || "true".equals(setting.getPropertyString("tempDisablePermissionChecking"))) {
                        hasPermis = true;
                    } else {
                        if (ruleObj != null) {
                            if (ruleObj.has("permissionDeny") && "true".equals(ruleObj.getString("permissionDeny"))) {
                                hasPermis = false;
                            } else if (ruleObj.has("permission")){
                                try {
                                    JSONObject permissionObj = ruleObj.getJSONObject("permission");
                                    hasPermis = UserviewUtil.getPermisionResult(permissionObj, requestParameters, currentUser);
                                } catch (Exception e) {
                                    LogUtil.debug(getClass().getName(), "set category permission error.");
                                }
                            } else {
                                hasPermis = true;
                            }

                            //handle for permission rule to override the default setting
                            if (ruleObj.has("hide") && "yes".equals(ruleObj.getString("hide"))) {
                                category.setProperty("hide", "yes");
                            } else {
                                category.setProperty("hide", "");
                            }
                        } else { //when no properties found for the category object
                            hasPermis = true;
                            category.setProperty("hide", "");
                        }
                    }

                    if (hasPermis) {
                        //set menus
                        JSONArray menusArray = categoryObj.getJSONArray("menus");
                        Collection<UserviewMenu> menus = new ArrayList<UserviewMenu>();
                        for (int j = 0; j < menusArray.length(); j++) {
                            try {
                                //set menu
                                JSONObject menuObj = (JSONObject) menusArray.get(j);
                                UserviewMenu menu = (UserviewMenu) pluginManager.getPlugin(menuObj.getString("className"));

                                // check for mobile support
                                boolean isMobileView = MobileUtil.isMobileView();
                                if (isMobileView && (menu instanceof MobileElement) && !((MobileElement)menu).isMobileSupported()) {
                                    // mobile not supported, skip this menu
                                    continue;
                                }

                                //check for deny
                                JSONObject menuRuleObj = null;
                                if (Permission.DEFAULT.equals(permissionKey)) {
                                    menuRuleObj = menuObj.getJSONObject("properties");
                                } else if (menuObj.getJSONObject("properties").has("permission_rules")) {
                                    JSONObject permissionRules = menuObj.getJSONObject("properties").getJSONObject("permission_rules");
                                    if (permissionRules != null && permissionRules.has(permissionKey)) {
                                        menuRuleObj = permissionRules.getJSONObject(permissionKey);
                                    }
                                }
                                if (menuRuleObj != null && menuRuleObj.has("permissionDeny") && "true".equals(menuRuleObj.getString("permissionDeny"))) {
                                    continue;
                                }

                                menu.setProperties(PropertyUtil.getProperties(menuObj.getJSONObject("properties")));
                                menu.setRequestParameters(requestParameters);
                                menu.setUserview(userview);
                                String mId = getMenuId(menu);
                                menu.setProperty("menuId", mId);

                                if (preview) {
                                    menu.setUrl(contextPath + "/web/console/app/" + appId + "/" + appVersion + "/userview/builderPreview/" + userview.getPropertyString("id") + "/" + mId);
                                } else {
                                    menu.setKey(key);
                                    String prefix = "/web/userview/";

                                    if (embed) {
                                        prefix = "/web/embed/userview/";
                                    }

                                    menu.setUrl(contextPath + prefix + appId + "/" + userview.getPropertyString("id") + "/" + ((key != null) ? StringEscapeUtils.escapeHtml(key) : Userview.USERVIEW_KEY_EMPTY_VALUE) + "/" + mId);
                                }

                                //set Current, if current menu id is empty, search the 1st valid menu
                                if ((("".equals(menuId) || "index".equals(menuId) || menuId == null) && userview.getCurrent() == null && menu.isHomePageSupported())
                                        || (menuId != null && menuId.equals(mId))) {
                                    userview.setCurrent(menu);
                                    userview.setCurrentCategory(category);
                                }

                                //set home menu Id
                                if (userview.getPropertyString("homeMenuId") == null || userview.getPropertyString("homeMenuId").isEmpty() && menu.isHomePageSupported()) {
                                    userview.setProperty("homeMenuId", mId);
                                }

                                if (menuRuleObj == null || !menuRuleObj.has("permissionHidden") || !"true".equals(menuRuleObj.getString("permissionHidden"))) {
                                    menu = new CachedUserviewMenu(menu);
                                    menus.add(menu);
                                }
                            } catch (Exception e) {
                                LogUtil.debug(getClass().getName(), "Userview Menu class file not found");
                            }
                        }

                        category.setMenus(menus);
                        if (!"yes".equals(category.getPropertyString("hide")) && menus.size() > 0) {
                            categories.add(category);
                        }
                    }
                }
            }
            userview.setCategories(categories);
        } catch (Exception ex) {
            LogUtil.error(getClass().getName(), ex, "Create Userview Error!!");
        } finally {
            if (isScreenCapture) {
                workflowUserManager.setCurrentThreadUser(currentThreadUser);
            }
        }
        return userview;
    }

    /**
     * Gets the id of an userview menu
     * @param menu
     * @return
     */
    public String getMenuId(UserviewMenu menu) {
        String menuId = menu.getPropertyString("id");
        if (menu.getPropertyString("customId") != null && menu.getPropertyString("customId").trim().length() > 0) {
            menuId = menu.getPropertyString("customId");
        }
        return menuId;
    }

    /**
     * Gets the name of a userview from json definition
     * @param json
     * @return
     */
    public String getUserviewName(String json) {
        try {
            JSONObject userviewObj = new JSONObject(json);
            return PropertyUtil.getProperties(userviewObj.getJSONObject("properties")).get("name").toString();
        } catch (Exception ex) {
            LogUtil.error(getClass().getName(), ex, "Get Userview Name Error!!");
        }
        return "";
    }

    /**
     * Gets the userview theme used by an userview
     * @param appId
     * @param userviewId
     * @return
     */
    public UserviewTheme getUserviewTheme(String appId, String userviewId) {
        UserviewTheme theme = null;

        AppDefinition appDef = appService.getPublishedAppDefinition(appId);
        HttpServletRequest request = WorkflowUtil.getHttpServletRequest();
        if (appDef != null && request != null) {
            UserviewDefinition userviewDef = userviewDefinitionDao.loadById(userviewId, appDef);
            if (userviewDef != null) {
                String json = userviewDef.getJson();
                //process json with hash variable
                json = AppUtil.processHashVariable(json, null, StringUtil.TYPE_JSON, null, appDef);

                Map requestParameters = convertRequestParamMap(request.getParameterMap());
                requestParameters.put("contextPath", request.getContextPath());
                requestParameters.put("appId", appDef.getAppId());
                requestParameters.put("appVersion", appDef.getVersion().toString());

                try {
                    Userview userview = new Userview();

                    //set userview properties
                    JSONObject userviewObj = new JSONObject(json);
                    userview.setProperties(PropertyUtil.getProperties(userviewObj.getJSONObject("properties")));

                    JSONObject settingObj = userviewObj.getJSONObject("setting");
                    JSONObject themeObj = settingObj.getJSONObject("properties").getJSONObject("theme");

                    theme = (UserviewTheme) pluginManager.getPlugin(themeObj.getString("className"));
                    if (theme != null) {
                        theme.setProperties(PropertyUtil.getProperties(themeObj.getJSONObject("properties")));
                        theme.setRequestParameters(requestParameters);
                        theme.setUserview(userview);
                    }

                } catch (Exception e) {
                    LogUtil.debug(getClass().getName(), "get userview theme error.");
                }
            }
        }
        return theme;
    }

    /**
     * Gets userview description from json definition
     * @param json
     * @return
     */
    public String getUserviewDescription(String json) {
        try {
            JSONObject userviewObj = new JSONObject(json);
            JSONObject settingObj = userviewObj.getJSONObject("setting");
            Object description = PropertyUtil.getProperties(settingObj.getJSONObject("properties")).get("userviewDescription");
            return (description != null) ? description.toString() : "";
        } catch (Exception ex) {
            LogUtil.error(getClass().getName(), ex, "Get Userview Description Error!!");
        }
        return "";
    }

    /**
     * Gets the userview theme used by an userview
     * @param appId
     * @param version
     * @param userviewId
     * @return
     */
    public Set<String> getAllMenuIds(String appId, String version, String userviewId) {
        Set<String> ids = new HashSet<String>();

        AppDefinition appDef = appService.getAppDefinition(appId, version);
        UserviewDefinition userviewDef = userviewDefinitionDao.loadById(userviewId, appDef);
        if (userviewDef != null) {
            String json = userviewDef.getJson();

            try {
                //set userview properties
                JSONObject userviewObj = new JSONObject(json);
                JSONArray categoriesArray = userviewObj.getJSONArray("categories");
                for (int i = 0; i < categoriesArray.length(); i++) {
                    JSONObject categoryObj = (JSONObject) categoriesArray.get(i);
                    JSONArray menusArray = categoryObj.getJSONArray("menus");
                    for (int j = 0; j < menusArray.length(); j++) {
                        JSONObject menuObj = (JSONObject) menusArray.get(j);
                        JSONObject props = menuObj.getJSONObject("properties");
                        String id = props.getString("id");
                        String customId = (props.has("customId"))?props.getString("customId"):null;
                        if (customId != null && !customId.isEmpty()) {
                            ids.add(customId);
                        } else {
                            ids.add(id);
                        }
                    }
                }
            } catch (Exception e) {
                LogUtil.debug(getClass().getName(), "get userview menu ids error.");
            }
        }

        return ids;
    }

    private Map convertRequestParamMap(Map params) {
        Map result = new HashMap();
        for (String key : (Set<String>) params.keySet()) {
            key = StringEscapeUtils.escapeHtml(key);
            String[] paramValue = (String[]) params.get(key);
            if (paramValue.length == 1) {
                result.put(key, paramValue[0]);
            } else {
                result.put(key, paramValue);
            }
        }
        return result;
    }

    public UserviewDefinition getDefaultUserview() {
        // check for app center userview setting
        String defaultUserviewProperty = "defaultUserview";
        UserviewDefinition defaultUserview = null;
        Setting defaultUserviewSetting = setupManager.getSettingByProperty(defaultUserviewProperty);
        if (defaultUserviewSetting != null) {
            // check app center userview is published
            String defaultUserviewValue = defaultUserviewSetting.getValue();
            StringTokenizer st = new StringTokenizer(defaultUserviewValue, "/");
            String appId = (st.hasMoreTokens()) ? st.nextToken() : null;
            String userviewId = (st.hasMoreTokens()) ? st.nextToken() : null;
            if (appId != null && userviewId != null) {
                AppDefinition appDef = appService.getPublishedAppDefinition(appId);
                if (appDef != null) {
                    defaultUserview = userviewDefinitionDao.loadById(userviewId, appDef);
                }
            }
        } else {
            // import default app center app
            String path = "/setup/apps/APP_appcenter7-1.zip";
            LogUtil.info(getClass().getName(), "Import default app center " + path);
            InputStream in = null;
            try {
                in = getClass().getResourceAsStream(path);
                byte[] fileContent = IOUtils.toByteArray(in);
                final AppDefinition appDef = appService.importApp(fileContent);
                if (appDef != null) {
                    TransactionTemplate transactionTemplate = (TransactionTemplate) AppUtil.getApplicationContext().getBean("transactionTemplate");
                    transactionTemplate.execute(new TransactionCallback<Object>() {
                        public Object doInTransaction(TransactionStatus ts) {
                            appService.publishApp(appDef.getId(), null);
                            return null;
                        }
                    });
                    // get app center userview
                    Collection<UserviewDefinition> userviewList = appDef.getUserviewDefinitionList();
                    if (!userviewList.isEmpty()) {
                        String userviewId = userviewList.iterator().next().getId();
                        defaultUserview = userviewDefinitionDao.loadById(userviewId, appDef);

                        // save setting
                        String value = defaultUserview.getAppId() + "/" + defaultUserview.getId();
                        Setting newSetting = new Setting();
                        newSetting.setProperty(defaultUserviewProperty);
                        newSetting.setValue(value);
                        setupManager.saveSetting(newSetting);
                    }
                }
            } catch (Exception ex) {
                LogUtil.error(getClass().getName(), ex, "Failed to import default app center " + path);
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException e) {
                }
            }
        }
        return defaultUserview;
    }

    public boolean isDefaultUserview(String appId, String userviewId) {
        boolean result = false;
        String userviewPath = appId + "/" + userviewId;
        Setting defaultUserviewSetting = setupManager.getSettingByProperty("defaultUserview");
        if (defaultUserviewSetting != null) {
            // check app center userview is published
            String defaultUserviewValue = defaultUserviewSetting.getValue();
            result = userviewPath.equals(defaultUserviewValue);
        }
        return result;
    }

}

## 内容3
### 文件路径：
wflow-core/src/main/java/org/joget/plugin/enterprise/UniversalTheme.java
### 代码内容：
package org.joget.plugin.enterprise;

import com.asual.lesscss.LessEngine;
import de.bripkens.gravatar.DefaultImage;
import de.bripkens.gravatar.Gravatar;
import de.bripkens.gravatar.Rating;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.apache.commons.lang.StringEscapeUtils;
import org.joget.apps.app.service.AppUtil;
import org.joget.apps.app.service.MobileUtil;
import org.joget.apps.userview.lib.InboxMenu;
import org.joget.apps.userview.model.Userview;
import org.joget.apps.userview.model.UserviewCategory;
import org.joget.apps.userview.model.UserviewMenu;
import org.joget.apps.userview.model.UserviewPwaTheme;
import org.joget.apps.userview.model.UserviewSetting;
import org.joget.apps.userview.model.UserviewV5Theme;
import org.joget.apps.userview.service.UserviewUtil;
import org.joget.commons.util.LogUtil;
import org.joget.commons.util.ResourceBundleUtil;
import org.joget.commons.util.StringUtil;
import org.joget.commons.util.TimeZoneUtil;
import org.joget.directory.model.User;
import org.joget.directory.model.service.DirectoryUtil;
import org.joget.plugin.base.PluginWebSupport;
import org.joget.workflow.model.WorkflowAssignment;
import org.joget.workflow.model.service.WorkflowManager;
import org.joget.workflow.model.service.WorkflowUserManager;
import org.joget.workflow.util.WorkflowUtil;
import org.json.JSONObject;

public class UniversalTheme extends UserviewV5Theme implements UserviewPwaTheme, PluginWebSupport {
    protected final static String PROFILE = "_ja_profile";
    protected final static String INBOX = "_ja_inbox";
    protected static LessEngine lessEngine = new LessEngine();

    public enum Color {
        RED("#F44336", "#D32F2F", ""),
        PINK("#E91E63", "#C2185B", ""),
        PURPLE("#9C27B0", "#7B1FA2", ""),
        DEEP_PURPLE("#673AB7", "#512DA8", ""),
        INDIGO("#3F51B5", "#303F9F", ""),
        BLUE("#0D6EFD", "#1976D2", ""),
        LIGHT_BLUE("#03A9F4", "#0288D1", ""),
        CYAN("#00BCD4", "#0097A7", ""),
        TEAL("#009688", "#00796B", ""),
        GREEN("#4CAF50", "#388E3C", ""),
        LIGHT_GREEN("#8BC34A", "#689F38", ""),
        LIME("#CDDC39", "#AFB42B", ""),
        YELLOW("#FFEB3B", "#FBC02D", ""),
        AMBER("#FFC107", "#FFA000", ""),
        ORANGE("#FF9800", "#F57C00", ""),
        DEEP_ORANGE("#FF5722", "#E64A19", ""),
        BROWN("#795548", "#795548", ""),
        GREY("#6c757D", "#616161", ""),
        BLUE_GREY("#607D8B", "#455A64", ""),
        DEEP_GREY("#2B343A", "#1E262B", "#222c32"),
        LAVENDERBLUSH("#FFF0F5", "", ""),
        THISTLE("#D8BFD8", "", ""),
        PLUM("#DDA0DD", "", ""),
        LAVENDER("#E6E6FA", "", ""),
        GHOSTWHITE("#F8F8FF", "", ""),
        DARKROYALBLUE("#3b5998", "", ""),
        ROYALBLUE("#4169E1", "", ""),
        CORNFLOWERBLUE("#6495ED", "", ""),
        ALICEBLUE("#F0F8FF", "", ""),
        LIGHTSTEELBLUE("#B0C4DE", "", ""),
        STEELBLUE("#4682B4", "", ""),
        LIGHTSKYBLUE("#87CEFA", "", ""),
        SKYBLUE("#87CEEB", "", ""),
        DEEPSKYBLUE("#00BFFF", "", ""),
        AZURE("#F0FFFF", "", ""),
        LIGHTCYAN("#E1FFFF", "", ""),
        IVORY("#FFFFF0", "", ""),
        LEMONCHIFFON("#FFFACD", "", ""),
        WHEAT("#F5DEB3", "", ""),
        LIGHTGREY("#D3D3D3", "", ""),
        SILVER("#C0C0C0", "", ""),
        BLACK("#000000", "#222222", ""),
        WHITE("#FFFFFF", "", "#DDDDDD");

        protected final String color;
        protected final String dark;
        protected final String light;
        Color(String color, String dark, String light) {
            this.color = color;
            this.dark = dark;
            this.light = light;
        }
    }

    @Override
    public String getName() {
        return "V6 Universal Theme";
    }

    @Override
    public String getVersion() {
        return "6.0.0";
    }

    @Override
    public String getDescription() {
        return "A universal responsive Userview Theme based on Material Design";
    }

    @Override
    public String getLabel() {
        return getName();
    }

    @Override
    public String getClassName() {
        return getClass().getName();
    }

    public String getPathName() {
        return "universal";
    }

    @Override
    public String getPropertyOptions() {
        return AppUtil.readPluginResource(getClass().getName(), "/properties/userview/" + getPathName() + "Theme.json", null, true, null);
    }

    @Override
    public String getMetas(Map<String, Object> data) {
        String maxScale = "";
        if (MobileUtil.isIOS()) { //used to prevent text field zoom on focus in ios
            maxScale = ", maximum-scale=1";
        }

        String meta = super.getMetas(data) + "\n";
        if ((Boolean) data.get("is_login_page")) {
            meta += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1"+maxScale+", user-scalable=no\">\n";
        } else {
            meta += "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1"+maxScale+"\">\n";
        }
        meta += "<meta name=\"msapplication-tap-highlight\" content=\"no\"/>\n";
        meta += getInternalMetas(data);
        return meta;
    }

    protected String getInternalMetas(Map<String, Object> data) {
        String meta = "";
        // set description
        String description = userview.getPropertyString("description");
        if (description != null && !description.trim().isEmpty()) {
            meta += "<meta name=\"Description\" content=\"" + StringEscapeUtils.escapeHtml(description) + "\"/>\n";
        }

        // PWA: set address bar theme color
        Userview uv = (Userview)data.get("userview");
        String primary = getPrimaryColor();
        meta += "<meta name=\"theme-color\" content=\"" + primary + "\"/>\n";

        // PWA: set apple-touch-icon
        HttpServletRequest request = WorkflowUtil.getHttpServletRequest();
        String icon = request.getContextPath() + "/images/logo_512x512.png";
        UserviewSetting userviewSetting = getUserview().getSetting();
        if (!userviewSetting.getPropertyString("userview_thumbnail").isEmpty()) {
            icon = userviewSetting.getPropertyString("userview_thumbnail");
        }
        meta += "<link rel=\"apple-touch-icon\" href=\"" + icon + "\">\n";

        // PWA: set manifest URL
        String appId = uv.getParamString("appId");
        String userviewId = uv.getPropertyString("id");
        String manifestUrl = request.getContextPath() + "/web/userview/" + appId + "/" + userviewId + "/manifest";
        meta += "<link rel=\"manifest\" href=\"" + manifestUrl + "\"  crossorigin=\"use-credentials\">";
        return meta;
    }

    protected String getPrimaryColor() {
        Color p = Color.valueOf(getDefaultColor("primary"));
        String primary = p.color;
        if ("custom".equals(getPropertyString("primaryColor"))) {
            primary = getPropertyString("customPrimary");
        } else if (!getPropertyString("primaryColor").isEmpty()) {
            p = Color.valueOf(getPropertyString("primaryColor"));
            if (p != null) {
                primary = p.color;
            }
        }
        return primary;
    }

    @Override
    public String getManifest(String appId, String userviewId) {
        String userviewName = getUserview().getPropertyString("name");
        HttpServletRequest request = WorkflowUtil.getHttpServletRequest();
        String icon = request.getContextPath() + "/images/logo_512x512.png";
        UserviewSetting userviewSetting = getUserview().getSetting();
        if (!userviewSetting.getPropertyString("userview_thumbnail").isEmpty()) {
            icon = userviewSetting.getPropertyString("userview_thumbnail");
        }
        String startUrl = request.getContextPath() + "/web/userview/" + appId + "/" + userviewId + "/_/index";
        String primaryColor = getPrimaryColor();
        String backgroundColor = "#FFFFFF";
        String scope = request.getContextPath() + "/web/userview/" + appId + "/" + userviewId + "/";
        userviewName = StringUtil.stripAllHtmlTag(userviewName);
        userviewName = userviewName.replaceAll(StringUtil.escapeRegex(" "), " ");
        userviewName = userviewName.trim();
        String shortName = (userviewName.length() > 12) ? userviewName.substring(0, 10) + ".." : userviewName;
        String manifest = "{\n" +
            "  \"short_name\": \"" + StringUtil.escapeString(shortName, StringUtil.TYPE_JSON, null) + "\",\n" +
            "  \"name\": \"" + StringUtil.escapeString(userviewName, StringUtil.TYPE_JSON, null) + "\",\n" +
            "  \"icons\": [\n" +
            "    {\n" +
            "      \"src\": \"" + icon + "\",\n" +
            "      \"type\": \"image/png\",\n" +
            "      \"sizes\": \"512x512\",\n" +
            "      \"purpose\": \"any maskable\"\n" +
            "    }\n" +
            "  ],\n" +
            "  \"start_url\": \"" + startUrl + "\",\n" +
            "  \"background_color\": \"" + backgroundColor + "\",\n" +
            "  \"display\": \"standalone\",\n" +
            "  \"scope\": \"" + scope + "\",\n" +
            "  \"theme_color\": \"" + primaryColor + "\"\n" +
            "}";
        return manifest;
    }

    @Override
    public Set<String> getOfflineStaticResources() {
        Set<String> urls = new HashSet<String>();
        String contextPath = AppUtil.getRequestContextPath();
        String pathName = getPathName();
        String bn = ResourceBundleUtil.getMessage("build.number");
        urls.add(contextPath + "/wro/common.css");
        urls.add(contextPath + "/wro/" + pathName + ".preload.min.css");
        urls.add(contextPath + "/wro/" + pathName + ".min.css");
        urls.add(contextPath + "/wro/common.preload.js?build=" + bn);
        urls.add(contextPath + "/wro/common.js?build=" + bn);
        urls.add(contextPath + "/wro/form_common.js?build=" + bn);
        urls.add(contextPath + "/wro/" + pathName + ".preload.min.js");
        urls.add(contextPath + "/wro/" + pathName + ".min.js");
        urls.add(contextPath + "/" + pathName +"/lib/responsive-switch.min.js");

        return urls;
    }

    public Set<String> getCacheUrls(String appId, String userviewId, String userviewKey) {
        HttpServletRequest request = WorkflowUtil.getHttpServletRequest();
        String contextPath = request.getContextPath();

        Set<String> urls = new HashSet<String>();
        urls.add(contextPath + "/web/userview/" + appId + "/" + userviewId + "/"+userviewKey+"/index");

        if (!getPropertyString("urlsToCache").isEmpty()) {
            String urlsToCache = getPropertyString("urlsToCache");
            if (urlsToCache != null) {
                StringTokenizer st = new StringTokenizer(urlsToCache, "\n");
                while (st.hasMoreTokens()) {
                    String url = st.nextToken().trim();
                    if (url.startsWith("/") && !url.startsWith(contextPath)) {
                        url = contextPath + url;
                    }
                    urls.add(url);
                }
            }
        }

        return urls;
    }

    @Override
    public String getServiceWorker(String appId, String userviewId, String userviewKey) {
        Set<String> urls = getCacheUrls(appId, userviewId, userviewKey);
        urls.addAll(UserviewUtil.getAppStaticResources(AppUtil.getCurrentAppDefinition()));
        String urlsToCache = "";
        for (String url : urls) {
            if (!urlsToCache.isEmpty()) {
                urlsToCache += ", ";
            }
            urlsToCache += "'" + url + "'";
        }

        HttpServletRequest request = WorkflowUtil.getHttpServletRequest();

        String appUserviewId = appId + "-" + userviewId;
        Object[] arguments = new Object[]{
            request.getContextPath(),
            appUserviewId,
            urlsToCache
        };

        String js = AppUtil.readPluginResource(getClass().getName(), "/resources/themes/universal/sw.js", arguments, false, "");
        return js;
    }

    @Override
    public String getCss(Map<String, Object> data) {
        String css = getPropertyString("css");
        if ("true".equals(getPropertyString("removeAssignmentTitle"))) {
            css += "\nbody .viewForm-body-header, body .runProcess-body-header {display:none;}";
        }
        return css;
    }

    @Override
    public String getJs(Map<String, Object> data) {
        return getPropertyString("js");
    }

    @Override
    public String getJsCssLib(Map<String, Object> data) {
        String path = data.get("context_path") + "/" + getPathName();

        String jsCssLink = "";
        jsCssLink += "<link href=\"" + data.get("context_path") + "/wro/" + getPathName() + ".preload.min.css" + "\" rel=\"stylesheet\" />\n";
        jsCssLink += "<link rel=\"preload\" href=\"" + data.get("context_path") + "/js/fontawesome5/fonts/fontawesome-webfont.woff2?v=4.6.1\" as=\"font\" crossorigin />\n";
        jsCssLink += "<link rel=\"preload\" href=\"" + data.get("context_path") + "/js/fontawesome5/webfonts/fa-brands-400.woff2\" as=\"font\" crossorigin />\n";
        jsCssLink += "<link rel=\"preload\" href=\"" + data.get("context_path") + "/js/fontawesome5/webfonts/fa-solid-900.woff2\" as=\"font\" crossorigin />\n";
        jsCssLink += "<link rel=\"preload\" href=\"" + data.get("context_path") + "/universal/lib/material-design-iconic-font/fonts/Material-Design-Iconic-Font.woff2?v=2.2.0\" as=\"font\" crossorigin />\n";
        jsCssLink += "<script>loadCSS(\"" + data.get("context_path") + "/wro/" + getPathName() + ".min.css" + "\")</script>\n";

        jsCssLink += "<style>" + generateLessCss() + "</style>";

        jsCssLink += "<script src=\"" + data.get("context_path") + "/wro/" + getPathName() + ".min.js\" async></script>\n";

        if (enableResponsiveSwitch()) {
            jsCssLink += "<script src=\"" + data.get("context_path") + "/" + getPathName() +"/lib/responsive-switch.min.js\" defer></script>\n";
        }
        jsCssLink += "<script>var _enableResponsiveTable = true;</script>\n";
        jsCssLink += getInternalJsCssLib(data);

        return jsCssLink;
    }

    protected String getInternalJsCssLib(Map<String, Object> data) {
        String jsCssLink = "";

        // PWA: register service worker
        if (!"true".equals(getPropertyString("disablePwa"))) {
            WorkflowUserManager workflowUserManager = (WorkflowUserManager)AppUtil.getApplicationContext().getBean("workflowUserManager");
            boolean pushEnabled = !"true".equals(getPropertyString("disablePush")) && !workflowUserManager.isCurrentUserAnonymous();
            String appId = userview.getParamString("appId");
            if (appId != null && !appId.isEmpty()) {
                String userviewId = userview.getPropertyString("id");
                String key = userview.getParamString("key");
                if (key.isEmpty()) {
                    key = Userview.USERVIEW_KEY_EMPTY_VALUE;
                }

                boolean isEmbedded = false;
                if(data.get("embed") != null){
                    isEmbedded = (Boolean) data.get("embed");
                };

                String pwaOnlineNotificationMessage = ResourceBundleUtil.getMessage("pwa.onlineNow");
                String pwaOfflineNotificationMessage = ResourceBundleUtil.getMessage("pwa.offlineNow");
                String pwaLoginPromptMessage = ResourceBundleUtil.getMessage("pwa.loginPrompt");
                String pwaSyncingMessage = ResourceBundleUtil.getMessage("pwa.syncing");
                String pwaSyncFailedMessage = ResourceBundleUtil.getMessage("pwa.syncFailed");
                String pwaSyncSuccessMessage = ResourceBundleUtil.getMessage("pwa.syncSuccess");
                String buildNumber = ResourceBundleUtil.getMessage("build.number");

                String serviceWorkerUrl = data.get("context_path") + "/web/userview/" + appId + "/" + userviewId + "/"+key+"/serviceworker";
                jsCssLink += "<script>$(function() {"
                        + "var initPwaUtil = function(){"
                        + "PwaUtil.contextPath = '" + data.get("context_path") + "';"
                        + "PwaUtil.userviewKey = '" + key + "';"
                        + "PwaUtil.homePageLink = '" + data.get("home_page_link") + "';"
                        + "PwaUtil.serviceWorkerPath = '" + serviceWorkerUrl + "';"
                        + "PwaUtil.subscriptionApiPath = '" + data.get("context_path") + "/web/console/profile/subscription';"
                        + "PwaUtil.pushEnabled = " + pushEnabled + ";"
                        + "PwaUtil.currentUsername = '" + workflowUserManager.getCurrentUsername() + "';"
                        + "PwaUtil.onlineNotificationMessage = '" + pwaOnlineNotificationMessage + "';"
                        + "PwaUtil.offlineNotificationMessage = '" + pwaOfflineNotificationMessage + "';"
                        + "PwaUtil.loginPromptMessage = '" + pwaLoginPromptMessage + "';"
                        + "PwaUtil.syncingMessage = '" + pwaSyncingMessage + "';"
                        + "PwaUtil.syncFailedMessage = '" + pwaSyncFailedMessage + "';"
                        + "PwaUtil.syncSuccessMessage = '" + pwaSyncSuccessMessage + "';"
                        + "PwaUtil.isEmbedded = " + isEmbedded + ";"
                        + "PwaUtil.register();"
                        + "PwaUtil.init();"
                        + "};"
                        + "if (typeof PwaUtil !== \"undefined\") {initPwaUtil();} else { $(document).on(\"PwaUtil.ready\", function(){ initPwaUtil(); });}"
                        + "});</script>";
            }
        }
        return jsCssLink;
    }

    protected String getDefaultColor(String defaultColor) {
        if (defaultColor.equals("primary")) {
            defaultColor = "DARKROYALBLUE";
        }
        else if (defaultColor.equals("accent")) {
            defaultColor = "#0D6EFD";
        }
        else if (defaultColor.equals("button")) {
            defaultColor = "#6c757D";
        }
        else if (defaultColor.equals("buttonText")) {
            defaultColor = "#FFFFFF";
        }
        else if (defaultColor.equals("menuFont")) {
            defaultColor = "#000000";
        }
        else if (defaultColor.equals("font")) {
            defaultColor = "#FFFFFF";
        }
        return defaultColor;
    }

    protected String generateLessCss() {
        String css = "";
        String lessVariables = "";
        String primary = "";
        String dark = "darken(@primary , 10%)";
        String light = "lighten(@primary , 5%)";
        String accent = getDefaultColor("accent");
        String lightAccent = "lighten(@accent , 10%)";
        String button = getDefaultColor("button");
        String buttonText = getDefaultColor("buttonText");
        String font = getDefaultColor("font");

        if ("custom".equals(getPropertyString("primaryColor"))) {
            primary = getPropertyString("customPrimary");
            if (!getPropertyString("customPrimaryDark").isEmpty()) {
                dark = getPropertyString("customPrimaryDark");
            }
            if (!getPropertyString("customPrimaryLight").isEmpty()) {
                light = getPropertyString("customPrimaryLight");
            }
        } else {
            Color p = Color.valueOf(getDefaultColor("primary"));
            if (!getPropertyString("primaryColor").isEmpty()){
                p = Color.valueOf(getPropertyString("primaryColor"));
            }
            if (p != null) {
                primary = p.color;
                dark = (p.dark.isEmpty())?dark:p.dark;
                if ("light".equals(getPropertyString("themeScheme"))) {
                    light = "screen(@primary, #eeeeee)";
                } else {
                    light = (p.light.isEmpty())?light:p.light;
                }
            }
        }

        if ("custom".equals(getPropertyString("accentColor"))) {
            accent = getPropertyString("customAccent");
            if (!getPropertyString("customAccentLight").isEmpty()) {
                lightAccent = getPropertyString("customAccentLight");
            }
        }  else if (!getPropertyString("accentColor").isEmpty()) {
            Color a = Color.valueOf(getPropertyString("accentColor"));
            if (a != null) {
                accent = a.color;
                lightAccent = (a.light.isEmpty())?lightAccent:a.light;
            }
        }

        if ("custom".equals(getPropertyString("buttonColor"))) {
            button = getPropertyString("customButton");
        } else if (!getPropertyString("buttonColor").isEmpty()) {
            Color a = Color.valueOf(getPropertyString("buttonColor"));
            if (a != null) {
                button = a.color;
            }
        }

        if ("custom".equals(getPropertyString("buttonTextColor"))) {
            buttonText = getPropertyString("customButtonText");
        } else if (!getPropertyString("buttonTextColor").isEmpty()) {
            Color a = Color.valueOf(getPropertyString("buttonTextColor"));
            if (a != null) {
                buttonText = a.color;
            }
        }

        if ("custom".equals(getPropertyString("fontColor"))) {
            font = getPropertyString("customFontColor");
        } else if (!getPropertyString("fontColor").isEmpty()) {
            Color a = Color.valueOf(getPropertyString("fontColor"));
            if (a != null) {
                font = a.color;
            }
        }

        if ("light".equals(getPropertyString("themeScheme"))) {
            String menuFont = getDefaultColor("menuFont");
            if ("custom".equals(getPropertyString("menuFontColor"))) {
                menuFont = getPropertyString("customMenuFontColor");
            } else if (!getPropertyString("menuFontColor").isEmpty()) {
                Color a = Color.valueOf(getPropertyString("menuFontColor"));
                if (a != null) {
                    menuFont = a.color;
                }
            }

            lessVariables += "@primary: " + primary + "; @darkPrimary: " + dark + "; @lightPrimary: " + light + "; @accent: " + accent + "; @lightAccent: " + lightAccent + "; @menuFont: " + menuFont + "; @button: " + button + "; @buttonText: " + buttonText + "; @defaultFontColor : " + font + ";";
        } else {
            lessVariables += "@primary: " + primary + "; @darkPrimary: " + dark + "; @lightPrimary: " + light + "; @accent: " + accent + "; @lightAccent: " + lightAccent + "; @button: " + button + "; @buttonText: " + buttonText + "; @defaultFontColor : " + font + ";";
        }

        // process LESS
        String less = AppUtil.readPluginResource(getClass().getName(), "resources/themes/" + getPathName() + "/" + getPropertyString("themeScheme") + ".less");
        less = lessVariables + "\n" + less;
        // read CSS from cache
        Cache cache = (Cache) AppUtil.getApplicationContext().getBean("cssCache");
        if (cache != null) {
            Element element = cache.get(less);
            if (element != null) {
                css = (String) element.getObjectValue();
            }
        }
        if (css == null || css.isEmpty()) {
            // not available in cache, compile LESS
            css = compileLess(less);
            // store CSS in cache
            if (cache != null) {
                Element element = new Element(less, css);
                cache.put(element);
            }
        }
        return css;
    }

    protected String compileLess(String less) {
        String css = "";
        try {
            css = lessEngine.compile(less);
        } catch(Exception e) {
            LogUtil.error(this.getClass().getName(), e, "Error compiling LESS");
            LogUtil.debug(this.getClass().getName(), "LESS: " + less);
        }
        return css;
    }

    @Override
    public String getHeader(Map<String, Object> data) {
        if ("true".equals(getPropertyString("horizontal_menu"))) {
            data.put("header_after", UserviewUtil.getTemplate(this, data, "/templates/" + getPathName() + "Theme_horizontalMenu.ftl"));
        } else if ("horizontal_inline".equals(getPropertyString("horizontal_menu"))) {
            data.put("header_name_inner_after", UserviewUtil.getTemplate(this, data, "/templates/" + getPathName() + "Theme_horizontalMenu.ftl"));
        }

        data.put("header_classes", "navbar");
        data.put("header_inner_before", "<div class=\"navbar-inner\"><div class=\"container-fluid\"><div class=\"hi-trigger ma-trigger\" id=\"sidebar-trigger\"><div class=\"line-wrap\"><div class=\"line top\"></div><div class=\"line center\"></div><div class=\"line bottom\"></div></div></div>");
        data.put("header_inner_after", "</div></div>" + getPropertyString("subheader"));
        data.put("header_link_classes", "brand");
        data.put("header_info_classes", "inline-block");
        data.put("header_name_classes", "inline-block");

        if (!getPropertyString("logo").isEmpty()) {
            data.put("header_name_inner_before", "<img class=\"logo\" alt=\"logo\" src=\""+getPropertyString("logo")+"\" />");
        }

        data.put("header_description_classes", "inline-block visible-desktop");
        data.put("header_description_span_classes", "brand");
        data.put("header_message_after", getNavbar(data));
        return UserviewUtil.getTemplate(this, data, "/templates/userview/header.ftl");
    }

    protected boolean showHomeBanner() {
        String menuId = (String)getRequestParameter("menuId");
        String homeMenuId = getUserview().getPropertyString("homeMenuId");
        String pwaStartUrl = "index";
        boolean showHomeBanner = (menuId == null || homeMenuId.equals(menuId) || pwaStartUrl.equals(menuId)) && !getPropertyString("homeAttractBanner").isEmpty();
        return showHomeBanner;
    }

    @Override
    public String getContentContainer(Map<String, Object> data) {
        if (!getPropertyString("horizontal_menu").isEmpty()) {
            data.put("hide_nav", true);
        }

        if (showHomeBanner()) {
            data.put("main_container_before", "<div class=\"home_banner\"><div class=\"home_banner_inner\">"+getPropertyString("homeAttractBanner")+"</div></div>");
        }

        data.put("main_container_classes", "container-fluid-full");
        data.put("main_container_inner_classes", "row-fluid");
        data.put("sidebar_classes", "span2");
        if (((Boolean) data.get("embed")) || ((Boolean) data.get("hide_nav"))) {
            data.put("content_classes", "span12");
        } else {
            data.put("content_classes", "span10");
        }

        String ContentInnerBefore = getBreadcrumb(data);
        if (getPropertyString("fontControl").equalsIgnoreCase("true")) {
            ContentInnerBefore += getFontSizeController(data);
        }
        data.put("content_inner_before", ContentInnerBefore);
        return super.getContentContainer(data);
    }

    @Override
    public String getLayout(Map<String, Object> data) {
        if ("true".equals(getPropertyString("horizontal_menu"))) {
            data.put("body_classes", data.get("body_classes").toString() + " horizontal_menu");
        } else if ("horizontal_inline".equals(getPropertyString("horizontal_menu"))){
            data.put("body_classes", data.get("body_classes").toString() + " horizontal_menu inline_menu");
        } else if ("no".equals(getPropertyString("horizontal_menu"))) {
            data.put("body_classes", data.get("body_classes").toString() + " horizontal_menu no_menu");
        }

        if (showHomeBanner()) {
            data.put("body_classes", data.get("body_classes").toString() + " has_home_banner");
        }

        data.put("body_inner_before", "<div class=\"page-loader\"><div class=\"preloader pl-xl\" style=\"width:80px\"><svg class=\"pl-circular\" viewBox=\"25 25 50 50\"><circle class=\"plc-path\" cx=\"50\" cy=\"50\" r=\"20\" /></svg></div></div>");
        return UserviewUtil.getTemplate(this, data, "/templates/userview/layout.ftl");
    }

    public String getLoginForm(Map<String, Object> data) {
        data.put("hide_nav", true);
        return super.getLoginForm(data);
    }

    @Override
    public String handlePageNotFound(Map<String, Object> data) {
        if (PROFILE.equals(userview.getParamString("menuId"))) {
            return pageProfile(data);
        } else if (INBOX.equals(userview.getParamString("menuId"))) {
            return pageInbox(data);
        } else {
            return super.handlePageNotFound(data);
        }
    }

    protected String getUserMenu(Map<String, Object> data) {
        if (!getPropertyString("horizontal_menu").isEmpty()) {
            String html = "";
            if ((Boolean) data.get("is_logged_in")) {
                User user = (User) data.get("user");
                String email = user.getEmail();
                if (email == null) {
                    email = "";
                }
                if (email.contains(";")) {
                    email = email.split(";")[0];
                }
                if (email.contains(",")) {
                    email = email.split(",")[0];
                }

                String profileImageTag = "";
                if (getPropertyString("userImage").isEmpty()) {
                    String url = (email != null && !email.isEmpty()) ?
                        new Gravatar()
                            .setSize(20)
                            .setHttps(true)
                            .setRating(Rating.PARENTAL_GUIDANCE_SUGGESTED)
                            .setStandardDefaultImage(DefaultImage.IDENTICON)
                            .getUrl(email)
                        : "//www.gravatar.com/avatar/default?d=identicon";
                    profileImageTag = "<img class=\"gravatar\" alt=\"gravatar\" width=\"30\" height=\"30\" data-lazysrc=\""+url+"\" onError=\"this.onerror = '';this.style.display='none';\"/> ";
                } else if ("hashVariable".equals(getPropertyString("userImage"))) {
                    String url = AppUtil.processHashVariable(getPropertyString("userImageUrlHash"), null, StringUtil.TYPE_HTML, null, AppUtil.getCurrentAppDefinition());
                    if (AppUtil.containsHashVariable(url) || url == null || url.isEmpty()) {
                        url = data.get("context_path") + "/" + getPathName() + "/user.png";
                    }
                    profileImageTag = "<img alt=\"profile\" width=\"30\" height=\"30\" src=\""+url+"\" /> ";
                }

                html += "<li class=\"user-link dropdown\">\n"
                      + "    <a data-toggle=\"dropdown\" class=\"btn dropdown-toggle\">\n"
                      + "	     " + profileImageTag + StringUtil.stripHtmlTag(DirectoryUtil.getUserFullName(user), new String[]{}) + "\n"
                      + "	     <span class=\"caret\"></span>\n"
                      + "    </a>\n";

                html += "<ul class=\"dropdown-menu\">\n";
                if (!"true".equals(getPropertyString("profile")) && !user.getReadonly()) {
                    html += "    <li><a href=\"" + data.get("base_link") + PROFILE +"\"><i class=\"fa fa-user\"></i> " + ResourceBundleUtil.getMessage("theme.universal.profile") + "</a></li>\n";
                }

                Object[] shortcut = (Object[]) getProperty("userMenu");
                if (shortcut != null && shortcut.length > 0) {
                    for (Object o : shortcut) {
                        Map link = (HashMap) o;
                        String href = link.get("href").toString();
                        String label = link.get("label").toString();
                        String target = (link.get("target") == null)?"":link.get("target").toString();

                        if ("divider".equalsIgnoreCase(label)) {
                            html += "<li class=\"divider\"></li>\n";
                        } else if (href.isEmpty()) {
                             html += "<li class=\"dropdown-menu-title\"><span>" + label + "</span></li>\n";
                        } else {
                            if (!href.contains("/")) {
                                href = data.get("base_link") + href;
                            }
                            html += "<li><a href=\"" + href + "\" target=\""+target+"\">" + label + "</a></li>\n";
                        }
                    }
                }

                html += "    <li><a href=\"" + data.get("logout_link") + "\"><i class=\"fa fa-power-off\"></i> " + ResourceBundleUtil.getMessage("theme.universal.logout") + "</a></li>\n"
                      + "</ul>";

            } else {
                html += "<li class=\"user-link\">\n"
                      + "    <a href=\"" + data.get("login_link") + "\" class=\"btn\">\n"
                      + "	     <i class=\"fa fa-user white\"></i> " + ResourceBundleUtil.getMessage("ubuilder.login") + "\n"
                      + "    </a>\n";
            }
            html += "</li>";
            return html;
        } else {
            return "";
        }
    }

    protected String getSidebarUserMenu(Map<String, Object> data) {
        String html = "<ul class=\"user-menu nav nav-tabs nav-stacked main-menu\">";
        if ((Boolean) data.get("is_logged_in")) {
            User user = (User) data.get("user");
            String email = user.getEmail();
            if (email == null) {
                email = "";
            }
            if (email.contains(";")) {
                email = email.split(";")[0];
            }
            if (email.contains(",")) {
                email = email.split(",")[0];
            }

            String profileImageTag = "";
            if (getPropertyString("userImage").isEmpty()) {
                String url = (email != null && !email.isEmpty()) ?
                    new Gravatar()
                        .setSize(20)
                        .setHttps(true)
                        .setRating(Rating.PARENTAL_GUIDANCE_SUGGESTED)
                        .setStandardDefaultImage(DefaultImage.IDENTICON)
                        .getUrl(email)
                    : "//www.gravatar.com/avatar/default?d=identicon";
                profileImageTag = "<img class=\"gravatar\" alt=\"gravatar\" width=\"30\" height=\"30\" data-lazysrc=\""+url+"\" onError=\"this.onerror = '';this.style.display='none';\"/> ";
            } else if ("hashVariable".equals(getPropertyString("userImage"))) {
                String url = AppUtil.processHashVariable(getPropertyString("userImageUrlHash"), null, StringUtil.TYPE_HTML, null, AppUtil.getCurrentAppDefinition());
                if (AppUtil.containsHashVariable(url) || url == null || url.isEmpty()) {
                    url = data.get("context_path") + "/" + getPathName() + "/user.png";
                }
                profileImageTag = "<img alt=\"profile\" width=\"30\" height=\"30\" src=\""+url+"\" /> ";
            }

            html += "<li class=\"mm-profile user-link\">\n"
                  + "    <a class=\"dropdown\">\n"
                  + "        "+profileImageTag+"\n"
                  + "	     <span>" + StringUtil.stripHtmlTag(DirectoryUtil.getUserFullName(user), new String[]{}) + "</span>\n"
                  + "	     <small>" + email + "</small>\n"
                  + "    </a>\n";

            html += "<ul>\n";
            if (!"true".equals(getPropertyString("profile")) && !user.getReadonly()) {
                String activeCss = "";
                if (PROFILE.equals(userview.getParamString("menuId"))) {
                    activeCss = " class=\"active\"";
                }
                html += "    <li "+activeCss+"><a href=\"" + data.get("base_link") + PROFILE +"\"><span><i class=\"fa fa-user\"></i> " + ResourceBundleUtil.getMessage("theme.universal.profile") + "</span></a></li>\n";
            }

            Object[] shortcut = (Object[]) getProperty("userMenu");
            if (shortcut != null && shortcut.length > 0) {
                for (Object o : shortcut) {
                    Map link = (HashMap) o;
                    String href = link.get("href").toString();
                    String label = link.get("label").toString();
                    String target = (link.get("target") == null)?"":link.get("target").toString();

                    if ("divider".equalsIgnoreCase(label)) {
                        html += "<li class=\"divider\"></li>\n";
                    } else if (href.isEmpty()) {
                         html += "<li class=\"dropdown-menu-title\"><span>" + label + "</span></li>\n";
                    } else {
                        if (!href.contains("/")) {
                            href = data.get("base_link") + href;
                        }
                        html += "<li><a href=\"" + href + "\" target=\""+target+"\">" + label + "</a></li>\n";
                    }
                }
            }

            html += "    <li><a href=\"" + data.get("logout_link") + "\"><span><i class=\"fa fa-power-off\"></i> " + ResourceBundleUtil.getMessage("theme.universal.logout") + "</span></a></li>\n";
            html += "</ul>";

        } else {
            String profileImageTag = "";
            if (getPropertyString("userImage").isEmpty() || "hashVariable".equals(getPropertyString("userImage"))) {
                String url = data.get("context_path") + "/" + getPathName() + "/user.png";
                profileImageTag = "<img alt=\"profile\" width=\"30\" height=\"30\" src=\""+url+"\" /> ";
            }

            html += "<li class=\"mm-profile user-link\">\n"
                  + "    <a href=\"" + data.get("login_link") + "\" >\n"
                  + "        "+profileImageTag+"\n"
                  + "	     <span>Visitor</span>\n"
                  + "	     <small class=\"login_link\">" + ResourceBundleUtil.getMessage("ubuilder.login") + "</small>\n"
                  + "    </a>\n";
        }
        html += "</li></ul>";
        return html;
    }

    @Override
    public String getMenus(Map<String, Object> data) {
        if ("true".equals(getPropertyString("displayCategoryLabel"))) {
            data.put("combine_single_menu_category", false);
        } else {
            data.put("combine_single_menu_category", true);
        }
        data.put("categories_container_before", getSidebarUserMenu(data));

        return super.getMenus(data);
    }

    @Override
    public String getFooter(Map<String, Object> data) {
        if (enableResponsiveSwitch()) {
            data.put("footer_inner_after", "<div id=\"responsiveSwitch\"><p><a href=\"#\" class=\"rs-link\" data-link-desktop=\""+ ResourceBundleUtil.getMessage("theme.universal.switchDesktop") +"\" data-link-responsive=\""+ ResourceBundleUtil.getMessage("theme.universal.switchMobile") +"\"></a></p></div>" + getPropertyString("subfooter"));
        } else {
            data.put("footer_inner_after", getPropertyString("subfooter"));
        }
        return super.getFooter(data);
    }

    protected String getHomeLink(Map<String, Object> data) {
        String home_page_link = data.get("context_path").toString() + "/home";
        if (!getPropertyString("homeUrl").isEmpty()) {
            home_page_link = getPropertyString("homeUrl");
        }
        return "<li class=\"\"><a class=\"btn\" href=\"" + home_page_link + "\" title=\"" + ResourceBundleUtil.getMessage("theme.universal.home") + "\"><i class=\"fa fa-home\"></i></a></li>\n";
    }

    protected String getNavbar(Map<String, Object> data) {
        String html = "<div class=\"nav-no-collapse header-nav\"><ul class=\"nav pull-right\">\n";
        html += getHomeLink(data);
        if ((Boolean) data.get("is_logged_in")) {
            html += getInbox(data);
        }
        html += getShortcut(data);
        html += getUserMenu(data);
        html += "</ul></div>\n";
        return html;
    }

    protected String getInbox(Map<String, Object> data) {
        String html = "";

        if (!getPropertyString("inbox").isEmpty()) {
            String url = data.get("context_path") + "/web/json/plugin/" + getClassName() + "/service?_a=getAssignment";
            if ("current".equals(getPropertyString("inbox"))) {
                try {
                    url += "&appId=" + URLEncoder.encode(userview.getParamString("appId"), "UTF-8");
                } catch (UnsupportedEncodingException e){}
            }
            html += "<li class=\"inbox-notification dropdown\" data-url=\"" + url + "\">\n"
                  + "    <a data-toggle=\"dropdown\" class=\"btn dropdown-toggle\">\n"
                  + "	 <i class=\"fa fa-tasks white\"></i><span class=\"badge red\">0</span>\n"
                  + "    </a>\n"
                  + "    <ul class=\"dropdown-menu notifications\">\n"
                  + "        <li class=\"dropdown-menu-title\"><span>" + ResourceBundleUtil.getMessage("theme.universal.inboxTaskMessage") + "</span><a href=\"#\" class=\"refresh\"><i class=\"fa fa-refresh\"></i></a></li>"
                  + "        <li class=\"loading\"><a><span><i class=\"fa fa-spinner fa-spin fa-3x\"></i></span></a></li>\n"
                  + "        <li><a href=\"" + data.get("base_link") + INBOX + "\" class=\"dropdown-menu-sub-footer\">" + ResourceBundleUtil.getMessage("theme.universal.viewAllTask") + "</a></li>\n"
                  + "    </ul>\n"
                  + "<li>";
        }

        return html;
    }

    protected String getShortcut(Map<String, Object> data) {
        String shortcutHtml = "";

        Object[] shortcut = (Object[]) getProperty("shortcut");
        if (shortcut != null && shortcut.length > 0) {
            for (Object o : shortcut) {
                Map link = (HashMap) o;
                String href = link.get("href").toString();
                String label = link.get("label").toString();
                String target = (link.get("target") == null)?"":link.get("target").toString();
                boolean isPublic = "true".equalsIgnoreCase((String) link.get("isPublic"));

                if ((Boolean) data.get("is_logged_in") || (!((Boolean) data.get("is_logged_in")) && isPublic)) {
                    if ("divider".equalsIgnoreCase(label)) {
                        shortcutHtml += "<li class=\"divider\"></li>\n";
                    } else if (href.isEmpty()) {
                         shortcutHtml += "<li class=\"dropdown-menu-title\"><span>" + label + "</span></li>\n";
                    } else {
                        if (!href.contains("/")) {
                            href = data.get("base_link") + href;
                        }
                        shortcutHtml += "<li><a href=\"" + href + "\" target=\""+target+"\">" + label + "</a></li>\n";
                    }
                }
            }
        }

        String html = "";
        if (!shortcutHtml.isEmpty()) {
            html = "<li class=\"shortcut-link dropdown\">\n"
                  + "    <a data-toggle=\"dropdown\" class=\"btn dropdown-toggle\">\n"
                  + "	     <i class=\"fa fa-th-list white\"></i> " + getPropertyString("shortcutLinkLabel") + "\n"
                  + "	     <span class=\"caret\"></span>\n"
                  + "    </a>\n";

            html += "<ul class=\"dropdown-menu\">\n";
            html += shortcutHtml;
            html += "</ul></li>";
        }

        return html;
    }

    protected String getBreadcrumb(Map<String, Object> data) {
        String breadcrumb = "<ul class=\"breadcrumb\"><li><i class=\"fa fa-home\"></i> <a href=\"" + data.get("home_page_link") + "\">" + ResourceBundleUtil.getMessage("theme.universal.home") + "</a> <i class=\"fa fa-angle-right\"></i></li>";
        if ((Boolean) data.get("is_login_page") || (Boolean) data.get("embed")) {
            return "";
        } else if (userview.getCurrent() != null) {
            UserviewCategory category = userview.getCurrentCategory();
            if (!(category.getMenus().size() <= 1 && ((Boolean) data.get("combine_single_menu_category"))) && !"yes".equals(category.getPropertyString("hide"))) {
                breadcrumb += "<li><a href=\"" + getCategoryLink(category, data) + "\">" + StringUtil.stripAllHtmlTag(category.getPropertyString("label")) + "</a> <i class=\"fa fa-angle-right\"></i></li>";
            }
            breadcrumb += "<li><a>" + StringUtil.stripAllHtmlTag(userview.getCurrent().getPropertyString("label")) + "</a></li>";
        } else if (PROFILE.equals(userview.getParamString("menuId"))) {
            breadcrumb += "<li><a>" + ResourceBundleUtil.getMessage("theme.universal.profile") + "</a></li>";
        } else if (INBOX.equals(userview.getParamString("menuId"))) {
            breadcrumb += "<li><a>" + ResourceBundleUtil.getMessage("theme.universal.inbox") + "</a></li>";
        } else if (UserviewPwaTheme.PWA_OFFLINE_MENU_ID.equals(userview.getParamString("menuId")) || UserviewPwaTheme.PAGE_UNAVAILABLE_MENU_ID.equals(userview.getParamString("menuId"))) {
            breadcrumb += "<li><a>" + ResourceBundleUtil.getMessage("pwa.offline.breadcrumbTitle") + "</a></li>";
        } else {
            breadcrumb += "<li><a>" + ResourceBundleUtil.getMessage("ubuilder.pageNotFound") + "</a></li>";
        }
        breadcrumb += "</ul>";

        return breadcrumb;
    }

    protected String getFontSizeController(Map<String, Object> data) {
        String fontController = "<div class=\"adjustfontSize\">\n"
                + "      <div style=\"float:right\">\n"
                + "            <span> "+ ResourceBundleUtil.getMessage("theme.universal.fontSize") +":</span>\n"
                + "            <div class=\"btn-group\" role=\"group\" aria-label=\"Basic example\">\n"
                + "                  <button id=\"smallFont\" type=\"button\" class=\"buttonFontSize\"><i class=\"fas fa-font\" style=\"font-size:13px\"></i></button>\n"
                + "                  <button id=\"mediumFont\" type=\"button\" class=\"buttonFontSize\"><i class=\"fas fa-font\" style=\"font-size:17px\"></i></button>\n"
                + "                  <button id=\"bigFont\" type=\"button\" class=\"buttonFontSize\"><i class=\"fas fa-font\" style=\"font-size:20px\"></i></button>\n"
                + "            </div>\n"
                + "      </div>\n"
                + "      <div style=\"clear:both\"></div>\n"
                + "</div>";
        if ((Boolean) data.get("is_login_page") || (Boolean) data.get("embed")) {
            return "";
        } else {
            return fontController;
        }
    }

    protected String getCategoryLink(UserviewCategory category, Map<String, Object> data) {
        UserviewMenu menu = category.getMenus().iterator().next();
        if (menu.isHomePageSupported()) {
            return menu.getUrl();
        }
        return "";
    }

    protected boolean enableResponsiveSwitch() {
        return MobileUtil.isMobileUserAgent() && "true".equals(getPropertyString("enableResponsiveSwitch"));
    }

    protected String pageProfile(Map<String, Object> data) {
        String html = "";
        try {
            UserProfileMenu profile = new UserProfileMenu();
            userview.setProperty("pageNotFoundMenu", profile);
            Map<String, Object> props = new HashMap<String, Object>();
            props.put("id", PROFILE);
            props.put("customId", PROFILE);
            props.put("menuId", PROFILE);
            props.put("label", ResourceBundleUtil.getMessage("theme.universal.profile"));
            profile.setRequestParameters(userview.getParams());
            profile.setProperties(props);
            profile.setUserview(userview);
            profile.setUrl(data.get("base_link") + PROFILE);
            profile.setKey(userview.getParamString("key"));
            html += UserviewUtil.getUserviewMenuHtml(profile);
        } catch (Exception e) {
            html += handleContentError(e, data);
        }
        return html;
    }

    protected String pageInbox(Map<String, Object> data) {
        String html = "";
        try {
            UserviewMenu menu = null;
            if ("current".equals(getPropertyString("inbox"))) {
                menu = new InboxMenu();
            } else if ("all".equals(getPropertyString("inbox"))) {
                menu = new UniversalInboxMenu();
            }
            if (menu != null) {
                userview.setProperty("pageNotFoundMenu", menu);
                Map<String, Object> props = new HashMap<String, Object>();
                props.put("id", INBOX);
                props.put("customId", INBOX);
                props.put("menuId", INBOX);
                props.put("label", "");

                if ("current".equals(getPropertyString("inbox"))) {
                    props.put(InboxMenu.PROPERTY_FILTER, InboxMenu.PROPERTY_FILTER_ALL);
                }

                menu.setRequestParameters(userview.getParams());
                menu.setProperties(props);
                menu.setUserview(userview);
                menu.setUrl(data.get("base_link") + INBOX);
                menu.setKey(userview.getParamString("key"));
                html += UserviewUtil.getUserviewMenuHtml(menu);
            }
        } catch (Exception e) {
            html += handleContentError(e, data);
        }
        return html;
    }

    public void webService(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String action = request.getParameter("_a");

        if ("getAssignment".equals(action)) {
            try {
                String appId = request.getParameter("appId");
                WorkflowManager wm = (WorkflowManager) AppUtil.getApplicationContext().getBean("workflowManager");
                if (appId != null && appId.isEmpty()) {
                    appId = null;
                }
                int count = wm.getAssignmentSize(appId, null, null);
                Collection<WorkflowAssignment> assignments = wm.getAssignmentListLite(appId, null, null, null, "a.activated", true, 0, 5);

                JSONObject jsonObj = new JSONObject();
                jsonObj.accumulate("count", count);

                String format = AppUtil.getAppDateFormat();
                Collection<Map<String, String>> datas = new ArrayList<Map<String, String>>();
                for (WorkflowAssignment a : assignments) {
                    Map<String, String> data = new HashMap<String, String>();
                    data.put("processId", a.getProcessId());
                    data.put("processDefId", a.getProcessDefId());
                    data.put("processRequesterId", a.getProcessRequesterId());
                    data.put("processName", a.getProcessName());
                    data.put("processVersion", a.getProcessVersion());
                    data.put("activityId", a.getActivityId());
                    data.put("activityDefId", a.getActivityDefId());
                    data.put("activityName", a.getActivityName());
                    data.put("assigneeName", a.getAssigneeName());
                    data.put("dateCreated", TimeZoneUtil.convertToTimeZone(a.getDateCreated(), null, format));
                    datas.add(data);
                }

                jsonObj.put("data", datas);

                jsonObj.write(response.getWriter());
            } catch (Exception ex) {
                LogUtil.error(this.getClass().getName(), ex, "Get assignment error!");
                response.setStatus(HttpServletResponse.SC_NO_CONTENT);
            }
        } else {
            response.setStatus(HttpServletResponse.SC_NO_CONTENT);
        }
    }

    @Override
    public String getFavIconLink(Map<String, Object> data) {
        String fav = getPropertyString("fav_icon");
        if (!fav.isEmpty()) {
            return fav;
        } else {
            return super.getFavIconLink(data);
        }
    }

    @Override
    public boolean isMobileViewDisabled() {
        return true;
    }

    @Override
    public String[] themeDefinedMenusId() {
        return new String[] {PROFILE, INBOX};
    }

    @Override
    public String handlePwaOfflinePage(Map<String, Object> data) {
        return UserviewUtil.getTemplate(this, data, "/templates/userview/pwaOffline.ftl");
    }

    @Override
    public String handlePwaUnavailablePage(Map<String, Object> data) {
        return UserviewUtil.getTemplate(this, data, "/templates/userview/pwaUnavailable.ftl");
    }
}

Output:
【漏洞类型】跨站脚本攻击（XSS）
【严重性】High
【漏洞摘要】这是一个跨站脚本攻击（XSS）漏洞。在`UniversalTheme`类的`getInternalJsCssLib`方法中，`key`参数未经过适当的JavaScript转义处理就直接插入到JavaScript字符串中，导致攻击者可以通过构造恶意的`key`参数来执行恶意脚本。
【关键代码模式】`StringUtil.escapeString(key, StringUtil.TYPE_JAVASCIPT, null)`
【修复方法说明】修复方法通过使用`StringUtil.escapeString`函数对`key`参数进行JavaScript转义处理，解决了XSS漏洞问题。最佳实践是始终对用户输入进行适当的转义处理，以防止注入攻击。