package com.psddev.cms.tool; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.ImmutableMap; import com.psddev.cms.db.Content; import com.psddev.cms.db.ContentField; import com.psddev.cms.db.ContentTemplate; import com.psddev.cms.db.ContentTemplateMappings; import com.psddev.cms.db.ContentType; import com.psddev.cms.db.Draft; import com.psddev.cms.db.ElFunctionUtils; import com.psddev.cms.db.History; import com.psddev.cms.db.ImageTag; import com.psddev.cms.db.LayoutTag; import com.psddev.cms.db.Localization; import com.psddev.cms.db.LocalizationContext; import com.psddev.cms.db.Overlay; import com.psddev.cms.db.OverlayProvider; import com.psddev.cms.db.Page; import com.psddev.cms.db.PageFilter; import com.psddev.cms.db.Renderer; import com.psddev.cms.db.ResizeOption; import com.psddev.cms.db.RichTextElement; import com.psddev.cms.db.Schedule; import com.psddev.cms.db.Site; import com.psddev.cms.db.StandardImageSize; import com.psddev.cms.db.Template; import com.psddev.cms.db.ToolFormWriter; import com.psddev.cms.db.ToolUi; import com.psddev.cms.db.ToolUiLayoutElement; import com.psddev.cms.db.ToolUser; import com.psddev.cms.db.Trash; import com.psddev.cms.db.Variation; import com.psddev.cms.db.WorkInProgress; import com.psddev.cms.db.WorkStream; import com.psddev.cms.db.Workflow; import com.psddev.cms.db.WorkflowLog; import com.psddev.cms.db.WorkflowState; import com.psddev.cms.db.WorkflowTransition; import com.psddev.cms.rte.RichTextToolbar; import com.psddev.cms.rte.RichTextToolbarItem; import com.psddev.cms.tool.file.SvgFileType; import com.psddev.cms.tool.page.content.Edit; import com.psddev.cms.tool.page.content.PublishModification; import com.psddev.cms.view.ClassResourceViewTemplateLoader; import com.psddev.cms.view.EmbedEntryView; import com.psddev.cms.view.PageEntryView; import com.psddev.cms.view.PageViewClass; import com.psddev.cms.view.PreviewEntryView; import com.psddev.cms.view.ViewCreator; import com.psddev.cms.view.ViewModel; import com.psddev.cms.view.ViewModelCreator; import com.psddev.cms.view.ViewOutput; import com.psddev.cms.view.ViewRenderer; import com.psddev.cms.view.ViewResponse; import com.psddev.cms.view.servlet.ServletViewModelCreator; import com.psddev.dari.db.Application; import com.psddev.dari.db.CompoundPredicate; import com.psddev.dari.db.Database; import com.psddev.dari.db.Modification; import com.psddev.dari.db.ObjectField; import com.psddev.dari.db.ObjectFieldComparator; import com.psddev.dari.db.ObjectIndex; import com.psddev.dari.db.ObjectStruct; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.Predicate; import com.psddev.dari.db.PredicateParser; import com.psddev.dari.db.Query; import com.psddev.dari.db.Singleton; import com.psddev.dari.db.State; import com.psddev.dari.db.StateStatus; import com.psddev.dari.db.ValidationException; import com.psddev.dari.util.ClassFinder; import com.psddev.dari.util.CodeUtils; import com.psddev.dari.util.CompactMap; import com.psddev.dari.util.DebugFilter; import com.psddev.dari.util.DependencyResolver; import com.psddev.dari.util.ErrorUtils; import com.psddev.dari.util.HtmlGrid; import com.psddev.dari.util.HtmlWriter; import com.psddev.dari.util.ImageEditor; import com.psddev.dari.util.JspUtils; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.RoutingFilter; import com.psddev.dari.util.Settings; import com.psddev.dari.util.StorageItem; import com.psddev.dari.util.StringUtils; import com.psddev.dari.util.TypeDefinition; import com.psddev.dari.util.TypeReference; import com.psddev.dari.util.Utf8Filter; import com.psddev.dari.util.WebPageContext; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import javax.servlet.Servlet; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.PageContext; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringWriter; import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Properties; import java.util.Set; import java.util.UUID; import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; /** * {@link WebPageContext} with extra methods that work well with * pages in {@link Tool}. */ public class ToolPageContext extends WebPageContext { /** * Settings key for tool URL prefix when creating a fully qualified * version of a path. */ public static final String TOOL_URL_PREFIX_SETTING = "brightspot/toolUrlPrefix"; public static final String TYPE_ID_PARAMETER = "typeId"; public static final String OBJECT_ID_PARAMETER = "id"; public static final String DRAFT_ID_PARAMETER = "draftId"; public static final String ORIGINAL_DRAFT_VALUE = "original"; public static final String HISTORY_ID_PARAMETER = "historyId"; public static final String VARIATION_ID_PARAMETER = "variationId"; public static final String RETURN_URL_PARAMETER = "returnUrl"; public static final String WORKFLOW_ACTION_PARAMETER = "action-workflow"; public static final String NEW_DRAFT_ACTION_PARAMETER = "action-newDraft"; public static final String DRAFT_ACTION_PARAMETER = "action-draft"; public static final String MERGE_ACTION_PARAMETER = "action-merge"; public static final String PUBLISH_ACTION_PARAMETER = "action-publish"; public static final String DELETE_ACTION_PARAMETER = "action-delete"; public static final String TRASH_ACTION_PARAMETER = "action-trash"; public static final String RESTORE_ACTION_PARAMETER = "action-restore"; public static final String SAVE_ACTION_PARAMETER = "action-save"; public static final String UNSCHEDULE_ACTION_PARAMETER = "action-unschedule"; private static final String ATTRIBUTE_PREFIX = ToolPageContext.class.getName() + "."; private static final String ERRORS_ATTRIBUTE = ATTRIBUTE_PREFIX + "errors"; private static final String FORM_FIELDS_DISABLED_ATTRIBUTE = ATTRIBUTE_PREFIX + "formFieldsDisabled"; private static final String TOOL_ATTRIBUTE = ATTRIBUTE_PREFIX + "tool"; private static final String TOOL_BY_CLASS_ATTRIBUTE = ATTRIBUTE_PREFIX + "toolByClass"; private static final String TOOL_BY_PATH_ATTRIBUTE = ATTRIBUTE_PREFIX + "toolByPath"; public static final String PARENT_ID_ATTRIBUTE = ATTRIBUTE_PREFIX + "parentId"; public static final String PARENT_TYPE_ID_ATTRIBUTE = ATTRIBUTE_PREFIX + "parentTypeId"; private static final String EXTRA_PREFIX = "cms.tool."; private static final String OVERLAID_DRAFT_EXTRA = EXTRA_PREFIX + "overlaidDraft"; private static final String OVERLAID_HISTORY_EXTRA = EXTRA_PREFIX + "overlaidHistory"; public static final String DEFAULT_OBJECT_LABEL = "Untitled"; private static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+"); /** Creates an instance based on the given {@code pageContext}. */ public ToolPageContext(PageContext pageContext) { super(pageContext); } /** Creates an instance based on the given servlet parameters. */ public ToolPageContext( ServletContext servletContext, HttpServletRequest request, HttpServletResponse response) { super(servletContext, request, response); } /** * Returns the parameter value as an instance of the given * {@code returnClass} associated with the given {@code name}, or if not * found, either the {@linkplain #getPageSetting page setting value} or * the given {@code defaultValue}. */ @SuppressWarnings("unchecked") public <T> T pageParam(Class<T> returnClass, String name, T defaultValue) { Class<?> valueClass = PRIMITIVE_CLASSES.get(returnClass); if (valueClass == null) { valueClass = returnClass; } HttpServletRequest request = getRequest(); String valueString = request.getParameter(name); Object value = ObjectUtils.to(valueClass, valueString); Object userValue = ObjectUtils.to(valueClass, AuthenticationFilter.Static.getPageSetting(request, name)); if (valueString == null) { return ObjectUtils.isBlank(userValue) ? defaultValue : (T) userValue; } else { if (!ObjectUtils.equals(value, userValue)) { AuthenticationFilter.Static.putPageSetting(request, name, value); } return (T) value; } } /** * Returns the parameter value as an instance of the given * {@code returnClass} associated with the given {@code name}, or if not * found, either the {@linkplain #getPageSetting page setting value} or * the given {@code defaultValue}. */ @SuppressWarnings("unchecked") public <T> List<T> pageParams(Class<T> returnClass, String name, List<T> defaultValue) { Class<?> valueClass = PRIMITIVE_CLASSES.get(returnClass); if (valueClass == null) { valueClass = returnClass; } HttpServletRequest request = getRequest(); List<T> value = params(returnClass, name); List<Object> userValue = ObjectUtils.to(new TypeReference<List<Object>>() { }, AuthenticationFilter.Static.getPageSetting(request, name)); if (value == null || value.isEmpty()) { return ObjectUtils.isBlank(userValue) ? defaultValue : (List<T>) userValue; } else { if (!ObjectUtils.equals(value, userValue)) { AuthenticationFilter.Static.putPageSetting(request, name, value); } return (List<T>) value; } } private static final Map<Class<?>, Class<?>> PRIMITIVE_CLASSES; static { Map<Class<?>, Class<?>> m = new HashMap<Class<?>, Class<?>>(); m.put(boolean.class, Boolean.class); m.put(byte.class, Byte.class); m.put(char.class, Character.class); m.put(double.class, Double.class); m.put(float.class, Float.class); m.put(int.class, Integer.class); m.put(long.class, Long.class); m.put(short.class, Short.class); PRIMITIVE_CLASSES = Collections.unmodifiableMap(m); } /** * Returns a label, or the given {@code defaultLabel} if one can't be * found, for the given {@code object}. */ public String getObjectLabelOrDefault(Object object, String defaultLabel) { return Static.getObjectLabelOrDefault(object, defaultLabel); } /** Returns a label for the given {@code object}. */ public String getObjectLabel(Object object) { return Static.getObjectLabel(object); } /** * Returns a label, or the given {@code defaultLabel} if one can't be * found, for the type of the given {@code object}. */ public String getTypeLabelOrDefault(Object object, String defaultLabel) { return Static.getTypeLabelOrDefault(object, defaultLabel); } /** Returns a label for the type of the given {@code object}. */ public String getTypeLabel(Object object) { return Static.getTypeLabel(object); } public String localize(Object context, Map<String, Object> contextOverrides, String key) throws IOException { ToolUser user = getUser(); return Localization.text( user != null ? user.getLocale() : null, new LocalizationContext(context, contextOverrides), key); } public String localize(Object context, String key) throws IOException { return localize(context, null, key); } /** * Returns {@code true} is the given {@code object} is previewable. * * @param object If {@code null}, always returns {@code false}. */ @SuppressWarnings("deprecation") public boolean isPreviewable(Object object) { if (object != null) { if (object instanceof Page && !(object instanceof Template)) { return true; } else if (object instanceof Renderer) { return true; } else { State state = State.getInstance(object); ObjectType type = state.getType(); if (type != null) { if (Template.Static.findUsedTypes(getSite()).contains(type)) { return true; } else { Renderer.TypeModification rendererData = type.as(Renderer.TypeModification.class); if (!ObjectUtils.isBlank(rendererData.getPath()) || !ObjectUtils.isBlank(rendererData.getPaths())) { return true; } } } PageViewClass pageViewClass = object.getClass().getAnnotation(PageViewClass.class); if ((pageViewClass != null && ViewCreator.findCreatorClass(object, pageViewClass.value(), null, null) != null) || ViewCreator.findCreatorClass(object, null, PageFilter.PAGE_VIEW_TYPE, null) != null || ViewCreator.findCreatorClass(object, null, PageFilter.PREVIEW_VIEW_TYPE, null) != null || ViewModel.findViewModelClass(PageFilter.PAGE_VIEW_TYPE, object) != null || ViewModel.findViewModelClass(PageFilter.PREVIEW_VIEW_TYPE, object) != null || ViewModel.findViewModelClass(PageEntryView.class, object) != null || ViewModel.findViewModelClass(PreviewEntryView.class, object) != null) { return true; } } } return false; } /** * Returns {@code true} is the given {@code object} is embeddable. * * @param object If {@code null}, always returns {@code false}. */ public boolean isEmbeddable(Object object) { if (object == null) { return false; } State state = State.getInstance(object); ObjectType type = state.getType(); Renderer.TypeModification rendererData = type.as(Renderer.TypeModification.class); return !ObjectUtils.isBlank(rendererData.getEmbedPath()) || ViewCreator.findCreatorClass(object, null, PageFilter.EMBED_VIEW_TYPE, null) != null || ViewModel.findViewModelClass(PageFilter.EMBED_VIEW_TYPE, object) != null || ViewModel.findViewModelClass(EmbedEntryView.class, object) != null; } /** * Returns the singleton instance of the given {@code toolClass}. * Note that this method caches the result, so it'll return the * exact same object every time within a single request. */ @SuppressWarnings("unchecked") public <T extends Tool> T getToolByClass(Class<T> toolClass) { HttpServletRequest request = getRequest(); Map<Class<?>, Tool> tools = (Map<Class<?>, Tool>) request.getAttribute(TOOL_BY_CLASS_ATTRIBUTE); if (tools == null) { tools = new HashMap<Class<?>, Tool>(); request.setAttribute(TOOL_BY_CLASS_ATTRIBUTE, tools); } Tool tool = tools.get(toolClass); if (!toolClass.isInstance(tool)) { tool = Application.Static.getInstance(toolClass); tools.put(toolClass, tool); } return (T) tool; } /** * Returns the CMS tool. * * @see #getToolByClass */ public CmsTool getCmsTool() { return getToolByClass(CmsTool.class); } /** Returns all embedded tools, keyed by their context paths. */ @SuppressWarnings("unchecked") public Map<String, Tool> getEmbeddedTools() { HttpServletRequest request = getRequest(); Map<String, Tool> tools = (Map<String, Tool>) request.getAttribute(TOOL_BY_PATH_ATTRIBUTE); if (tools == null) { tools = new LinkedHashMap<String, Tool>(); for (Map.Entry<String, Properties> entry : JspUtils.getEmbeddedSettings(getServletContext()).entrySet()) { String toolClassName = entry.getValue().getProperty(Application.MAIN_CLASS_SETTING); Class<?> objectClass = ObjectUtils.getClassByName(toolClassName); if (objectClass != null && Tool.class.isAssignableFrom(objectClass)) { tools.put(entry.getKey(), getToolByClass((Class<Tool>) objectClass)); } } if (!tools.containsKey("")) { Application app = Application.Static.getMain(); if (app instanceof Tool) { tools.put("", (Tool) app); } } request.setAttribute(TOOL_BY_PATH_ATTRIBUTE, tools); } return tools; } /** Returns the tool that's currently in use. */ public Tool getTool() { ServletContext context = getServletContext(); HttpServletRequest request = getRequest(); Tool tool = (Tool) request.getAttribute(TOOL_ATTRIBUTE); if (tool == null) { String contextPath = JspUtils.getEmbeddedContextPath(context, request.getServletPath()); tool = getEmbeddedTools().get(contextPath); request.setAttribute(TOOL_ATTRIBUTE, tool); } return tool; } private class AreaUrl implements Comparable<AreaUrl> { private final Area area; private final String url; public AreaUrl(Area area, String url) { this.area = area; this.url = url; } public Area getArea() { return area; } public String getUrl() { return url; } @Override public int compareTo(AreaUrl other) { return other.url.length() - url.length(); } } /** * Returns the area that's currently in use. * * @return May be {@code null}. */ public Area getArea() { List<AreaUrl> areaUrls = new ArrayList<AreaUrl>(); for (Area area : Tool.Static.getPluginsByClass(Area.class)) { String url = area.getUrl(); if (!ObjectUtils.isBlank(url)) { Tool tool = area.getTool(); if (tool != null) { areaUrls.add(new AreaUrl(area, toolUrl(tool, url))); } } } Collections.sort(areaUrls); String path = getRequest().getServletPath(); if (path.endsWith("/index.jsp")) { path = path.substring(0, path.length() - 9); } for (AreaUrl areaUrl : areaUrls) { if (path.startsWith(areaUrl.getUrl())) { return areaUrl.getArea(); } } return null; } private Object[] pushToArray(Object[] array, Object... newItems) { int os = array.length; int ns = newItems.length; Object[] newArray = new Object[os + ns]; if (os > 0) { System.arraycopy(array, 0, newArray, 0, os); } if (ns > 0) { System.arraycopy(newItems, 0, newArray, os, ns); } return newArray; } public String toolPath(Tool tool, String path, Object... parameters) { String toolPath = null; String appName = tool.getApplicationName(); if (appName != null) { toolPath = RoutingFilter.Static.getApplicationPath(appName); } else { for (Map.Entry<String, Tool> entry : getEmbeddedTools().entrySet()) { if (entry.getValue().equals(tool)) { toolPath = entry.getKey(); break; } } if (toolPath == null) { throw new IllegalStateException(String.format( "Can't find tool path for [%s]", tool.getName())); } } toolPath = toolPath + StringUtils.ensureStart(path, "/"); return StringUtils.addQueryParameters(toolPath, parameters); } public String toolPath(Class<? extends Tool> toolClass, String path, Object... parameters) { return toolPath(getToolByClass(toolClass), path, parameters); } /** * Returns an absolute version of the given {@code path} in context * of the given {@code tool}, modified by the given {@code parameters}. * * @param tool Can't be {@code null}. * @param path May be {@code null}. * @param parameters May be {@code null}. */ @SuppressWarnings("deprecation") public String toolUrl(Tool tool, String path, Object... parameters) { String url = null; String appName = tool.getApplicationName(); if (appName != null) { url = getServletContext().getContextPath() + RoutingFilter.Static.getApplicationPath(appName); } else { for (Map.Entry<String, Tool> entry : getEmbeddedTools().entrySet()) { if (entry.getValue().equals(tool)) { url = entry.getKey(); break; } } if (url == null) { url = tool.getUrl(); if (ObjectUtils.isBlank(url)) { url = getServletContext().getContextPath(); } } else { url = getServletContext().getContextPath() + url; } } url = url + StringUtils.ensureStart(path, "/"); return StringUtils.addQueryParameters(url, parameters); } /** * Returns an absolute version of the given {@code path} in context * of the instance of the given {@code toolClass}, modified by the given * {@code parameters}. * * @param toolClass Can't be {@code null}. * @param path May be {@code null}. * @param parameters May be {@code null}. */ public String toolUrl(Class<? extends Tool> toolClass, String path, Object... parameters) { return toolUrl(getToolByClass(toolClass), path, parameters); } /** * Returns a fully qualified, absolute version of the given {@code path} * in context of the instance of the given {@code toolClass}, modified by * the given {@code parameters}. * * @param toolClass Can't be {@code null}. * @param path May be {@code null}. * @param parameters May be {@code null}. */ public String fullyQualifiedToolUrl(Class<? extends Tool> toolClass, String path, Object... parameters) { String toolUrl = toolUrl(toolClass, path, parameters); String prefix = Settings.get(String.class, TOOL_URL_PREFIX_SETTING); if (!ObjectUtils.isBlank(prefix)) { toolUrl = StringUtils.removeEnd(prefix , "/") + toolUrl; } return toolUrl; } /** * Returns an absolute version of the given {@code path} in context * of the CMS, modified by the given {@code parameters}. * * @param path May be {@code null}. * @param parameters May be {@code null}. */ public String cmsUrl(String path, Object... parameters) { return toolUrl(getCmsTool(), path, parameters); } public String typeUrl(String path, UUID typeId, Object... parameters) { return url(path, pushToArray(parameters, TYPE_ID_PARAMETER, typeId, OBJECT_ID_PARAMETER, null, DRAFT_ID_PARAMETER, null, HISTORY_ID_PARAMETER, null)); } public String typeUrl(String path, Class<?> objectClass, Object... parameters) { UUID typeId = ObjectType.getInstance(objectClass).getId(); return typeUrl(path, typeId, parameters); } public String objectUrl(String path, Object object, Object... parameters) { if (object instanceof Draft) { Draft draft = (Draft) object; parameters = pushToArray(parameters, OBJECT_ID_PARAMETER, draft.getObjectId(), DRAFT_ID_PARAMETER, draft.getId(), HISTORY_ID_PARAMETER, null); } else if (object instanceof History) { History history = (History) object; parameters = pushToArray(parameters, OBJECT_ID_PARAMETER, history.getObjectId(), DRAFT_ID_PARAMETER, null, HISTORY_ID_PARAMETER, history.getId()); } else { State state = State.getInstance(object); ObjectType type = state.getType(); UUID objectId = state.getId(); Draft draft = getOverlaidDraft(object); History history = getOverlaidHistory(object); parameters = pushToArray(parameters, OBJECT_ID_PARAMETER, objectId, TYPE_ID_PARAMETER, type != null ? type.getId() : null, DRAFT_ID_PARAMETER, draft != null ? draft.getId() : null, HISTORY_ID_PARAMETER, history != null ? history.getId() : null); } return url(path, parameters); } public String originalUrl(String path, Object object, Object... parameters) { return url(path, pushToArray(parameters, OBJECT_ID_PARAMETER, State.getInstance(object).getId(), DRAFT_ID_PARAMETER, ORIGINAL_DRAFT_VALUE, HISTORY_ID_PARAMETER, null)); } /** * Returns an URL for returning to the current page from the request * at the given {@code path}, modified by the given {@code parameters}. */ public String returnableUrl(String path, Object... parameters) { HttpServletRequest request = getRequest(); return url(path, pushToArray(parameters, RETURN_URL_PARAMETER, JspUtils.getAbsolutePath(request, "") .substring(JspUtils.getEmbeddedContextPath(getServletContext(), request.getServletPath()).length()))); } /** * Returns an URL to the return to the page specified by a previous * call to {@link #returnableUrl(String, Object...)}, modified by the * given {@code parameters}. */ public String returnUrl(Object... parameters) { String returnUrl = param(String.class, RETURN_URL_PARAMETER); if (ObjectUtils.isBlank(returnUrl)) { throw new IllegalArgumentException(String.format( "The [%s] parameter is required!", RETURN_URL_PARAMETER)); } return url(returnUrl, parameters); } /** Returns a modifiable list of all the errors in this page. */ public List<Throwable> getErrors() { @SuppressWarnings("unchecked") List<Throwable> errors = (List<Throwable>) getRequest().getAttribute(ERRORS_ATTRIBUTE); if (errors == null) { errors = new ArrayList<Throwable>(); getRequest().setAttribute(ERRORS_ATTRIBUTE, errors); } return errors; } /** * Renders the form inputs appropriate for the given {@code field} * using the data from the given {@code object}. */ public void renderField(Object object, ObjectField field) throws IOException { @SuppressWarnings("all") ToolFormWriter writer = new ToolFormWriter(this); writer.inputs(State.getInstance(object), field.getInternalName()); } /** * Processes the form inputs for the given {@code field}, rendered in * {@link #renderField(Object, ObjectField)}, using the data from the * given {@code object}. */ public void processField(Object object, ObjectField field) throws Throwable { @SuppressWarnings("all") ToolFormWriter writer = new ToolFormWriter(this); writer.update(State.getInstance(object), getRequest(), field.getInternalName()); } /** Finds an existing object or reserve one. */ public Object findOrReserve(Collection<ObjectType> validTypes) { UUID objectId = param(UUID.class, OBJECT_ID_PARAMETER); Object object; WorkStream workStream = Query.findById(WorkStream.class, param(UUID.class, "workStreamId")); UUID draftId = param(UUID.class, DRAFT_ID_PARAMETER); if (!isFormPost() && workStream != null) { object = workStream.next(getUser()); if (object instanceof Draft) { objectId = ((Draft) object).getObjectId(); draftId = ((Draft) object).getId(); object = Query.fromAll().where("_id = ?", objectId).resolveInvisible().first(); } } else { object = Query.fromAll().where("_id = ?", objectId).resolveInvisible().first(); } UUID overlayId = param(UUID.class, "overlayId"); Object overlayObject; if (overlayId != null) { overlayObject = Query.fromAll() .where("_id = ?", overlayId) .resolveInvisible() .first(); } else { overlayObject = null; } Overlay overlay = null; if (overlayObject instanceof Overlay) { overlay = (Overlay) overlayObject; } else if (object instanceof Overlay) { overlay = (Overlay) object; } else if (object != null && overlayObject instanceof OverlayProvider) { overlay = ((OverlayProvider) overlayObject).provideOverlay(object); } if (overlay != null) { object = Query.fromAll() .where("_id = ?", overlay.getContentId()) .noCache() .resolveInvisible() .first(); State objectState = State.getInstance(object); objectState.getExtras().put("cms.draft.oldValues", objectState.getSimpleValues()); objectState.getExtras().put("cms.tool.overlay", overlay); objectState.setValues(Draft.mergeDifferences( objectState.getDatabase().getEnvironment(), objectState.getSimpleValues(), overlay.getDifferences())); } if (object == null && !ObjectUtils.isBlank(validTypes)) { UUID typeId = param(UUID.class, TYPE_ID_PARAMETER); ContentTemplate contentTemplate = Query .from(ContentTemplate.class) .where("_id = ?", typeId) .first(); ObjectType selectedType = contentTemplate != null ? contentTemplate.getTemplateType() : ObjectType.getInstance(typeId); if (selectedType == null) { for (ObjectType type : validTypes) { selectedType = type; break; } } if (selectedType != null) { if (selectedType.getSourceDatabase() != null) { object = Query.fromType(selectedType).where("_id = ?", objectId).resolveInvisible().first(); } if (object == null) { if (selectedType.getGroups().contains(Singleton.class.getName())) { object = Query.fromType(selectedType).resolveInvisible().first(); } if (object == null) { if (contentTemplate == null) { ToolUser user = getUser(); if (user != null) { Site site = getSite(); ObjectType finalSelectedType = selectedType; contentTemplate = Stream.of(user, user.getRole(), getCmsTool()) .filter(Objects::nonNull) .flatMap(o -> { ContentTemplateMappings mappings = o.as(ContentTemplateMappings.class); return Stream.concat( mappings.getSiteSpecificDefaults().stream() .filter(m -> m.getSites().contains(site)) .flatMap(m -> m.getContentTemplates().stream()), mappings.getGlobalDefaults().stream()); }) .filter(t -> finalSelectedType.equals(t.getTemplateType())) .findFirst() .orElse(null); } } if (contentTemplate != null) { object = contentTemplate.createObject(); State.getInstance(object).setId(param(UUID.class, "id")); } else { object = selectedType.createObject(objectId); } State.getInstance(object).as(Site.ObjectModification.class).setOwner(getSite()); } } } } if (object == null) { Object draftObject = Query.fromAll().where("_id = ?", draftId).first(); if (draftObject instanceof Draft) { Draft draft = (Draft) draftObject; object = draft.recreate(); State.getInstance(object).getExtras().put(OVERLAID_DRAFT_EXTRA, draft); } } else { State state = State.getInstance(object); History history = Query .from(History.class) .where("id = ?", param(UUID.class, HISTORY_ID_PARAMETER)) .and("objectId = ?", objectId) .first(); if (history != null) { state.getExtras().put(OVERLAID_HISTORY_EXTRA, history); state.getExtras().put("cms.draft.oldValues", state.getSimpleValues()); state.setValues(history.getObjectOriginals()); state.setStatus(StateStatus.SAVED); } else if (objectId != null) { Object draftObject; if (draftId != null) { draftObject = Query .fromAll() .where("id = ?", draftId) .and("com.psddev.cms.db.Draft/objectId = ?", objectId) .first(); } else { draftObject = Query .fromAll() .and("com.psddev.cms.db.Draft/objectId = ?", objectId) .and("com.psddev.cms.db.Draft/newContent = true") .first(); } if (draftObject instanceof Draft) { Draft draft = (Draft) draftObject; state.getExtras().put(OVERLAID_DRAFT_EXTRA, draft); draft.merge(object); } } UUID variationId = param(UUID.class, VARIATION_ID_PARAMETER); if (variationId != null) { @SuppressWarnings("unchecked") Map<String, Object> variationValues = (Map<String, Object>) state.getByPath("variations/" + variationId.toString()); if (variationValues != null) { state.putAll(variationValues); } } } if (object != null) { State.getInstance(object).setResolveInvisible(true); } Template template = Query.from(Template.class).where("_id = ?", param(UUID.class, "templateId")).first(); if (template != null) { if (object == null) { Set<ObjectType> contentTypes = template.getContentTypes(); if (!contentTypes.isEmpty()) { object = contentTypes.iterator().next().createObject(objectId); State.getInstance(object).as(Site.ObjectModification.class).setOwner(getSite()); } } if (object != null) { State.getInstance(object).as(Template.ObjectModification.class).setDefault(template); } } else if (object != null) { State state = State.getInstance(object); if (state.isNew()) { List<Template> templates = Template.Static.findUsable(object); if (!templates.isEmpty()) { state.as(Template.ObjectModification.class).setDefault(templates.iterator().next()); } } } return object; } /** Finds an existing object or reserve one. */ public Object findOrReserve(UUID... validTypeIds) { Set<ObjectType> validTypes = null; if (!ObjectUtils.isBlank(validTypeIds)) { validTypes = new LinkedHashSet<ObjectType>(); for (UUID typeId : validTypeIds) { ObjectType type = ObjectType.getInstance(typeId); if (type != null) { validTypes.add(type); } } } return findOrReserve(validTypes); } /** Finds an existing object or reserve one. */ public Object findOrReserve(Class<?>... validObjectClasses) { Set<ObjectType> validTypes = null; if (!ObjectUtils.isBlank(validObjectClasses)) { validTypes = new LinkedHashSet<ObjectType>(); for (Class<?> validObjectClass : validObjectClasses) { ObjectType type = ObjectType.getInstance(validObjectClass); if (type != null) { validTypes.add(type); } } } return findOrReserve(validTypes); } /** Finds an existing object or reserve one. */ public Object findOrReserve() { UUID selectedTypeId = param(UUID.class, TYPE_ID_PARAMETER); ContentTemplate contentTemplate = Query .from(ContentTemplate.class) .where("_id = ?", selectedTypeId) .first(); if (contentTemplate != null) { selectedTypeId = contentTemplate.getTemplateType().getId(); } return findOrReserve(selectedTypeId != null ? new UUID[] { selectedTypeId } : new UUID[0]); } /** * Returns the draft that was overlaid on top of the given * {@code object}. */ public Draft getOverlaidDraft(Object object) { return (Draft) State.getInstance(object).getExtra(OVERLAID_DRAFT_EXTRA); } /** * Returns the past revision that was overlaid on top of the * {@code object}. */ public History getOverlaidHistory(Object object) { return (History) State.getInstance(object).getExtra(OVERLAID_HISTORY_EXTRA); } public Predicate siteItemsPredicate() { ToolUser user = getUser(); if (user != null) { Site site = user.getCurrentSite(); if (site != null) { return site.itemsPredicate(); } } return null; } public Predicate siteItemsSearchPredicate() { Predicate predicate = siteItemsPredicate(); if (predicate != null) { predicate = CompoundPredicate.combine( PredicateParser.AND_OPERATOR, predicate, PredicateParser.Static.parse("* matches *")); } return predicate; } public Predicate userTypesPredicate() { Set<UUID> denied = new HashSet<>(); Set<UUID> allowed = new HashSet<>(); for (ObjectType type : Database.Static.getDefault().getEnvironment().getTypes()) { UUID typeId = type.getId(); if (hasPermission("type/" + typeId + "/read")) { allowed.add(typeId); } else { denied.add(typeId); } } int deniedSize = denied.size(); if (deniedSize > allowed.size()) { return PredicateParser.Static.parse("_type = ?", allowed); } else if (deniedSize > 0) { return PredicateParser.Static.parse("_type != ?", denied); } else { return null; } } private String cmsResource(String path, Object... parameters) { ServletContext context = getServletContext(); path = cmsUrl(path); long lastModified = 0; try { URL resource = context.getResource(path); if (resource != null) { URLConnection resourceConnection = resource.openConnection(); InputStream resourceInput = resourceConnection.getInputStream(); try { lastModified = resourceConnection.getLastModified(); } finally { resourceInput.close(); } } } catch (IOException error) { throw new IllegalStateException(error); } if (lastModified == 0) { lastModified = (long) (Math.random() * Long.MAX_VALUE); } return StringUtils.addQueryParameters( StringUtils.addQueryParameters(path, parameters), "_", lastModified); } /** * Returns the URL to the preview thumbnail of the given {@code object}. * * @return May be {@code null}. */ public String getPreviewThumbnailUrl(Object object) { if (object != null) { StorageItem preview = object instanceof StorageItem ? (StorageItem) object : State.getInstance(object).getPreview(); if (preview != null) { String contentType = preview.getContentType(); if (ImageEditor.Static.getDefault() != null && (contentType != null && !contentType.equals(SvgFileType.CONTENT_TYPE))) { return new ImageTag.Builder(preview) .setHeight(300) .setResizeOption(ResizeOption.ONLY_SHRINK_LARGER) .toUrl(); } else { return preview.getPublicUrl(); } } } return null; } /** * Creates a visibility label for the given {@code object}. * * @param object May be {@code null}. */ public String createVisibilityLabel(Object object) throws IOException { if (object == null) { return null; } Draft draft; if (object instanceof Draft) { draft = (Draft) object; } else { draft = getOverlaidDraft(object); if (draft != null) { object = draft.recreate(); } } State state = State.getInstance(object); if (draft != null) { if (draft.isNewContent()) { Object original = draft.recreate(); if (original != null) { return State.getInstance(original).getVisibilityLabel(); } } else if (draft.getSchedule() != null) { return localize(State.getInstance(object).getType(), "visibility.scheduledDraft"); } else { return localize(Draft.class, "displayName"); } } return State.getInstance(object).getVisibilityLabel(); } /** * Creates a descriptive HTML label for the given {@code object}. * * @param object May be {@code null}. */ public String createObjectLabelHtml(Object object) throws IOException { StringWriter htmlString = new StringWriter(); HtmlWriter html = new HtmlWriter(htmlString); if (object == null) { html.writeStart("em"); html.writeHtml(localize(null, "label.notAvailable")); html.writeEnd(); } else { String visibilityLabel = createVisibilityLabel(object); if (!ObjectUtils.isBlank(visibilityLabel)) { html.writeStart("span", "class", "visibilityLabel"); html.writeHtml(visibilityLabel); html.writeEnd(); html.writeHtml(" "); } State state = State.getInstance(object); String label = object instanceof ObjectType ? localize(object, "displayName") : state.getLabel(); if (ObjectUtils.to(UUID.class, label) != null) { html.writeStart("em"); html.writeHtml(localize(state.getType(), "label.untitled")); html.writeEnd(); } else { label = Static.notTooShort(label); if (WHITESPACE_PATTERN.splitAsStream(label) .filter(word -> word.length() > 41) .findFirst() .isPresent()) { html.writeStart("span", "class", "breakable"); html.writeHtml(label); html.writeEnd(); } else { html.writeHtml(label); } } } return htmlString.toString(); } /** * Writes a descriptive HTML label for the given {@code object}. * * @param object May be {@code null}. */ public void writeObjectLabel(Object object) throws IOException { write(createObjectLabelHtml(object)); } /** * Writes a descriptive label HTML for the type of the given * {@code object}. * * @param object If it or its type is {@code null}, writes {@code N/A}. */ public void writeTypeLabel(Object object) throws IOException { ObjectType type = null; if (object != null) { if (object instanceof Draft) { type = ((Draft) object).getObjectType(); } else { type = State.getInstance(object).getType(); } } writeObjectLabel(type); } /** * Writes a descriptive label HTML that contains the type information for * the given {@code object}. * * @param object If {@code null}, writes {@code N/A}. */ public void writeTypeObjectLabel(Object object) throws IOException { if (object == null) { writeHtml(localize(null, "label.notAvailable")); } else { State state = State.getInstance(object); ObjectType type = state.getType(); String visibilityLabel = createVisibilityLabel(object); String label = state.getLabel(); if (!ObjectUtils.isBlank(visibilityLabel)) { writeStart("span", "class", "visibilityLabel"); writeHtml(visibilityLabel); writeEnd(); writeHtml(" "); } String typeLabel; if (type == null) { typeLabel = "Unknown Type"; } else { typeLabel = type.getLabel(); if (ObjectUtils.isBlank(typeLabel)) { typeLabel = type.getId().toString(); } } if (ObjectUtils.isBlank(label)) { label = state.getId().toString(); } writeHtml(typeLabel); if (!typeLabel.equals(label)) { writeHtml(": "); writeHtml(getObjectLabelOrDefault(state, DEFAULT_OBJECT_LABEL)); } } } /** * Returns the user's time zone. * * @return Never {@code null}. */ public DateTimeZone getUserDateTimeZone() { DateTimeZone timeZone = null; ToolUser user = getUser(); if (user != null) { String timeZoneId = user.getTimeZone(); if (!ObjectUtils.isBlank(timeZoneId)) { try { timeZone = DateTimeZone.forID(timeZoneId); } catch (IllegalArgumentException error) { // Ignore unparseable time zone IDs. } } } return timeZone == null ? DateTimeZone.getDefault() : timeZone; } /** * Converts the given {@code dateTime} to the user's time zone. * * @param dateTime If {@code null}, returns {@code null}. * @return May be {@code null}. */ public DateTime toUserDateTime(Object dateTime) { return dateTime != null ? new DateTime(dateTime, getUserDateTimeZone()) : null; } /** * Formats the given {@code dateTime} according to the given * {@code format}. * * @param dateTime If {@code null}, returns {@code N/A}. * @return Never {@code null}. */ public String formatUserDateTimeWith(Object dateTime, String format) throws IOException { return Localization.currentUserDate( new DateTime(dateTime).getMillis(), format); } /** * Formats the given {@code dateTime} according to the default format. * * @param dateTime If {@code null}, returns {@code N/A}. * @return Never {@code null}. */ public String formatUserDateTime(Object dateTime) throws IOException { return Localization.currentUserDate( new DateTime(dateTime).getMillis(), Localization.DATE_AND_TIME_SKELETON); } /** * Formats the date part of the given {@code dateTime} according to the * default format. * * @param dateTime If {@code null}, returns {@code N/A}. * @return Never {@code null}. */ public String formatUserDate(Object dateTime) throws IOException { return Localization.currentUserDate( new DateTime(dateTime).getMillis(), Localization.DATE_ONLY_SKELETON); } /** * Formats the time part of the given {@code dateTime} according to the * default format. * * @param dateTime If {@code null}, returns {@code N/A}. * @return Never {@code null}. */ public String formatUserTime(Object dateTime) throws IOException { return Localization.currentUserDate( new DateTime(dateTime).getMillis(), Localization.TIME_ONLY_SKELETON); } /** * Writes the tool header with the given {@code title}. * * @param title If {@code null}, uses the default title. * @param requireToolUser If {@code true}, calls {@link #requireUser}. */ public void writeHeader(String title, boolean requireToolUser) throws IOException { if (requireToolUser && requireUser()) { throw new IllegalStateException(); } if (isAjaxRequest() || param(boolean.class, "_frame")) { return; } CmsTool cms = getCmsTool(); Area area = getArea(); String companyName = cms.getCompanyName(); String environment = cms.getEnvironment(); ToolUser user = getUser(); if (ObjectUtils.isBlank(companyName)) { companyName = "Brightspot"; } Site site = getSite(); StorageItem companyLogo = site != null ? site.getCmsLogo() : null; if (companyLogo == null) { companyLogo = cms.getCompanyLogo(); } HtmlGrid.Static.setRestrictGridPaths(cms.getGridCssPaths(), this.getServletContext()); writeTag("!doctype html"); writeTag("html", "class", site != null ? site.getCmsCssClass() : null, "data-user-id", user != null ? user.getId() : null, "data-user-label", user != null ? user.getLabel() : null, "data-time-zone", getUserDateTimeZone().getID(), "lang", MoreObjects.firstNonNull(user != null ? user.getLocale() : null, Locale.getDefault()).toLanguageTag()); writeStart("head"); writeStart("title"); if (!ObjectUtils.isBlank(title)) { writeHtml(title); writeHtml(" | "); } else if (area != null) { writeObjectLabel(area); writeHtml(" | "); } writeHtml("CMS | "); writeHtml(companyName); writeEnd(); writeElement("meta", "name", "referrer", "content", "never"); writeElement("meta", "name", "robots", "content", "noindex"); writeElement("meta", "name", "viewport", "content", "width=device-width, initial-scale=1"); writeStylesAndScripts(); for (Class<? extends ToolPageHead> headClass : ClassFinder.findConcreteClasses(ToolPageHead.class)) { TypeDefinition.getInstance(headClass).newInstance().writeHtml(this); } writeEnd(); Schedule currentSchedule = getUser() != null ? getUser().getCurrentSchedule() : null; String broadcastMessage = cms.getBroadcastMessage(); Date broadcastExpiration = cms.getBroadcastExpiration(); boolean hasBroadcast = !ObjectUtils.isBlank(broadcastMessage) && (broadcastExpiration == null || broadcastExpiration.after(new Date())); writeTag("body", "class", (currentSchedule != null || hasBroadcast ? "hasToolBroadcast " : "") + (user != null ? "" : "noToolUser ")); if (currentSchedule != null || hasBroadcast) { writeStart("div", "class", "toolBroadcast"); if (currentSchedule != null) { writeHtml("All editorial changes will be scheduled for: "); writeStart("a", "href", cmsUrl("/scheduleEdit", "id", currentSchedule.getId()), "target", "scheduleEdit"); writeHtml(getObjectLabel(currentSchedule)); writeEnd(); writeHtml(" - "); writeStart("form", "method", "post", "style", "display: inline;", "action", cmsUrl("/misc/updateUserSettings", "action", "scheduleSet", "returnUrl", url(""))); writeStart("button", "class", "link icon icon-action-cancel"); writeHtml("Stop Scheduling"); writeEnd(); writeEnd(); } if (hasBroadcast) { writeHtml(" - "); writeHtml(broadcastMessage); } writeEnd(); } writeStart("div", "class", "toolHeader" + (!ObjectUtils.isBlank(environment) ? " toolHeader-hasEnvironment" : "")); writeStart("h1", "class", "toolTitle"); writeStart("a", "href", cmsUrl("/")); if (companyLogo != null) { writeElement("img", "alt", companyName, "src", JspUtils.isSecure(getRequest()) ? companyLogo.getSecurePublicUrl() : companyLogo.getPublicUrl()); } else { writeHtml(companyName); } writeEnd(); writeEnd(); if (!ObjectUtils.isBlank(environment)) { writeStart("div", "class", "toolEnv"); writeHtml(environment); writeEnd(); } if (user != null) { writeStart("div", "class", "toolUserDisplay"); writeStart("span", "class", "toolUserAvatarProfile"); writeStart("a", "href", cmsUrl("/profilePanel"), "target", "profilePanel"); writeRaw(user.createAvatarHtml()); writeEnd(); writeEnd(); writeStart("div", "class", "toolUser"); writeStart("div", "class", "toolUserWelcome"); writeHtml(localize(user, "message.welcome")); writeEnd(); writeStart("div", "class", "toolUserControls"); writeStart("ul", "class", "piped"); if (!user.isDisableWorkInProgress() && !cms.isDisableWorkInProgress()) { writeStart("li"); writeStart("a", "href", cmsUrl("/user/wips"), "target", "wip"); writeHtml(localize(ToolUser.class, "action.wip")); writeEnd(); writeEnd(); } writeStart("li"); writeStart("a", "href", cmsUrl("/profilePanel"), "target", "profilePanel"); writeHtml(localize(ToolUser.class, "action.profile")); writeEnd(); writeEnd(); writeStart("li"); writeStart("a", "href", cmsUrl("/misc/logOut.jsp")); writeHtml(localize(ToolUser.class, "action.logOut")); writeEnd(); writeEnd(); writeEnd(); writeEnd(); writeEnd(); if (Site.Static.findAll().size() > 0) { writeStart("div", "class", "toolUserSite"); writeStart("div", "class", "toolUserSiteDisplay"); writeHtml(localize(Site.class, "displayName")); writeHtml(": "); writeHtml(site != null ? site.getLabel() : localize(Site.class, "global")); writeEnd(); writeStart("div", "class", "toolUserSiteControls"); writeStart("ul", "class", "piped"); if (user.findOtherAccessibleSites().size() > 0 || (user.getCurrentSite() != null && user.hasPermission("site/global"))) { writeStart("li"); writeStart("a", "href", cmsUrl("/siteSwitch", "returnUrl", url("")), "target", "siteSwitch"); writeHtml(localize(Site.class, "action.switch")); writeEnd(); writeEnd(); } writeEnd(); writeEnd(); writeEnd(); } writeEnd(); int nowHour = new DateTime().getHourOfDay(); writeStart("div", "class", "toolProfile"); writeHtml("Good "); writeHtml(nowHour >= 2 && nowHour < 12 ? "Morning" : (nowHour >= 12 && nowHour < 18 ? "Afternoon" : "Evening")); writeHtml(", "); writeHtml(getObjectLabel(user)); writeStart("ul"); if (!Site.Static.findAll().isEmpty()) { Site currentSite = user.getCurrentSite(); writeStart("li"); writeHtml("Site: "); writeStart("a", "href", cmsUrl("/misc/sites.jsp"), "target", "misc"); writeHtml(currentSite != null ? currentSite.getLabel() : "Global"); writeEnd(); writeEnd(); } writeStart("li"); writeStart("a", "class", "icon icon-object-history", "href", cmsUrl("/toolUserHistory"), "target", "toolUserHistory"); writeHtml("History"); writeEnd(); writeEnd(); writeStart("li"); writeStart("a", "class", "icon icon-object-toolUser", "href", cmsUrl("/misc/settings.jsp"), "target", "misc"); writeHtml(localize(ToolUser.class, "action.profile")); writeEnd(); writeEnd(); writeStart("li"); writeStart("a", "class", "action-logOut", "href", cmsUrl("/misc/logOut.jsp")); writeHtml("Log Out"); writeEnd(); writeEnd(); writeEnd(); writeEnd(); } if (hasPermission("area/dashboard")) { writeStart("form", "class", "toolSearch", "method", "get", "action", cmsUrl("/misc/search.jsp"), "target", "miscSearch"); writeElement("input", "type", "hidden", "name", Utf8Filter.CHECK_PARAMETER, "value", Utf8Filter.CHECK_VALUE); writeElement("input", "type", "hidden", "name", Search.NAME_PARAMETER, "value", "global"); writeStart("span", "class", "searchInput"); writeStart("label", "for", createId()).writeHtml(localize(null, "search.label")).writeEnd(); writeElement("input", "type", "text", "id", getId(), "name", "q"); writeStart("button").writeHtml("Go").writeEnd(); writeEnd(); writeEnd(); } if (user != null) { String servletPath = JspUtils.getEmbeddedServletPath(getServletContext(), getRequest().getServletPath()); writeStart("ul", "class", "toolNav"); for (Area top : Tool.Static.getTopAreas()) { if (!hasPermission(top.getPermissionId())) { continue; } String topUrl = top.getUrl(); String topLabel = getObjectLabel(top); writeStart("li", "class", (top.hasChildren() ? " isNested" : "") + (area != null && area.getHierarchy().startsWith(top.getHierarchy()) ? " selected" : "")); writeStart("a", "href", topUrl == null ? "#" : toolUrl(top.getTool(), topUrl)); writeHtml(topLabel); writeEnd(); if (top.hasChildren()) { writeStart("ul"); for (Area child : top.getChildren()) { if (!hasPermission(child.getPermissionId())) { continue; } writeStart("li", "class", area != null && area.getInternalName().equals(child.getInternalName()) ? "selected" : null); writeStart("a", "href", toolUrl(child.getTool(), child.getUrl())); writeHtml(getObjectLabel(child)); writeEnd(); writeEnd(); } writeEnd(); } writeEnd(); } writeEnd(); } writeEnd(); writeTag("div", "class", "toolContent"); StorageItem backgroundImage = cms.getBackgroundImage(); if (backgroundImage != null) { writeStart("div", "class", "toolBackground", "style", cssString( "background-image", "url(" + (JspUtils.isSecure(getRequest()) ? backgroundImage.getSecurePublicUrl() : backgroundImage.getPublicUrl()) + ")")); writeEnd(); } } public void writeStylesAndScripts() throws IOException { List<Tool> tools = new ArrayList<Tool>(); for (ObjectType type : Database.Static.getDefault().getEnvironment().getTypesByGroup(Tool.class.getName())) { if (!type.isConcrete()) { continue; } try { @SuppressWarnings({ "rawtypes", "unchecked" }) Class<? extends Tool> toolClass = (Class) type.getObjectClass(); if (toolClass != null) { tools.add(Application.Static.getInstance(toolClass)); } } catch (ClassCastException error) { // Ignore tool instances without backing Java classes. } } CmsTool cms = getCmsTool(); String extraCss = cms.getExtraCss(); String extraJavaScript = cms.getExtraJavaScript(); String theme = "v3"; if (getCmsTool().isUseNonMinifiedCss()) { writeElement("link", "rel", "stylesheet/less", "type", "text/less", "href", cmsResource("/style/" + theme + ".less")); } else { writeElement("link", "rel", "stylesheet", "type", "text/css", "href", ElFunctionUtils.resource(toolPath(CmsTool.class, "/style/" + theme + ".min.css"))); } for (Tool tool : tools) { tool.writeHeaderAfterStyles(this); } if (getCmsTool().isUseNonMinifiedCss()) { writeStart("script", "type", "text/javascript", "src", cmsResource("/script/less-dev.js")); writeEnd(); writeStart("script", "type", "text/javascript"); writeHtml("window.less.relativeUrls = true;"); writeEnd(); writeStart("script", "type", "text/javascript", "src", cmsResource("/script/less.js")); writeEnd(); } if (!ObjectUtils.isBlank(extraCss)) { writeStart("style", "type", "text/css"); write(extraCss); writeEnd(); } List<Map<String, Object>> cssClassGroups = new ArrayList<Map<String, Object>>(); for (CmsTool.CssClassGroup group : cms.getTextCssClassGroups()) { Map<String, Object> groupDef = new HashMap<String, Object>(); cssClassGroups.add(groupDef); groupDef.put("internalName", group.getInternalName()); groupDef.put("displayName", group.getDisplayName()); groupDef.put("dropDown", group.isDropDown()); List<Map<String, String>> cssClasses = new ArrayList<Map<String, String>>(); groupDef.put("cssClasses", cssClasses); for (CmsTool.CssClass cssClass : group.getCssClasses()) { Map<String, String> cssDef = new HashMap<String, String>(); cssClasses.add(cssDef); cssDef.put("internalName", cssClass.getInternalName()); cssDef.put("displayName", cssClass.getDisplayName()); cssDef.put("tag", cssClass.getTag()); } } List<Map<String, String>> standardImageSizes = new ArrayList<Map<String, String>>(); for (StandardImageSize size : StandardImageSize.findAll()) { Map<String, String> sizeMap = new CompactMap<String, String>(); sizeMap.put("internalName", size.getInternalName()); sizeMap.put("displayName", size.getDisplayName()); standardImageSizes.add(sizeMap); } List<Map<String, Object>> commonTimes = new ArrayList<Map<String, Object>>(); for (CmsTool.CommonTime commonTime : getCmsTool().getCommonTimes()) { Map<String, Object> commonTimeMap = new CompactMap<String, Object>(); commonTimeMap.put("displayName", commonTime.getDisplayName()); commonTimeMap.put("hour", commonTime.getHour()); commonTimeMap.put("minute", commonTime.getMinute()); commonTimes.add(commonTimeMap); } Map<String, List<Map<String, Object>>> richTextToolbars = ClassFinder.findConcreteClasses(RichTextToolbar.class).stream() .collect(Collectors.toMap( Class::getName, c -> { RichTextToolbar toolbar = TypeDefinition.getInstance(c).newInstance(); List<RichTextToolbarItem> items = toolbar.getItems(); if (items != null) { return items.stream() .map(RichTextToolbarItem::toMap) .collect(Collectors.toList()); } else { return Collections.emptyList(); } })); List<Map<String, Object>> richTextElements = new ArrayList<>(); Map<String, Set<String>> contextMap = new HashMap<>(); Map<String, Set<String>> clearContextMap = new HashMap<>(); Map<String, String> tagNameToStyleNameMap = new HashMap<>(); LoadingCache<Class<?>, Set<Class<?>>> concreteClassMap = CacheBuilder.newBuilder() .build(new CacheLoader<Class<?>, Set<Class<?>>>() { @Override public Set<Class<?>> load(Class<?> aClass) throws Exception { Set<Class<?>> classes = new HashSet<Class<?>>(ClassFinder.findConcreteClasses(aClass)); if (!Modifier.isAbstract(aClass.getModifiers()) && !Modifier.isInterface(aClass.getModifiers())) { classes.add(aClass); } return classes; } }); LoadingCache<Class<?>, Set<Class<?>>> exclusiveClassMap = CacheBuilder.newBuilder() .build(new CacheLoader<Class<?>, Set<Class<?>>>() { @Override public Set<Class<?>> load(Class<?> aClass) throws Exception { return ClassFinder.findConcreteClasses(aClass).stream() .collect(Collectors.toSet()); } }); for (Class<? extends RichTextElement> c : ClassFinder.findConcreteClasses(RichTextElement.class)) { RichTextElement.Tag tag = c.getAnnotation(RichTextElement.Tag.class); if (tag != null) { String tagName = tag.value().trim(); if (StringUtils.isEmpty(tagName)) { continue; } Map<String, Object> richTextElement = new CompactMap<>(); ObjectType type = ObjectType.getInstance(c); richTextElement.put("tag", tagName); String initialBody = tag.initialBody().trim(); if (!initialBody.isEmpty()) { richTextElement.put("initialBody", initialBody); } richTextElement.put("line", tag.block()); richTextElement.put("previewable", tag.preview()); richTextElement.put("readOnly", tag.readOnly()); richTextElement.put("position", tag.position()); boolean hasFields = type.getFields().stream() .filter(f -> !f.as(ToolUi.class).isHidden()) .findFirst() .isPresent(); richTextElement.put("popup", hasFields); richTextElement.put("toggle", !hasFields); Set<String> context = contextMap.get(tagName); if (context == null) { context = new HashSet<>(); contextMap.put(tagName, context); } if (tag.root()) { context.add(null); } Stream.of(tag.children()) .map(concreteClassMap::getUnchecked) .flatMap(Collection::stream) .filter(RichTextElement.class::isAssignableFrom) .map(b -> b.getAnnotation(RichTextElement.Tag.class)) .filter(Objects::nonNull) .<String>map(RichTextElement.Tag::value) .map(String::trim) .filter(p -> !ObjectUtils.isBlank(p)) .forEach((String p) -> { if (contextMap.get(p) == null) { contextMap.put(p, new HashSet<>()); } contextMap.get(p).add(tagName); }); Set<String> exclusiveTags = Stream.of(c.getInterfaces()) .filter(i -> i.isAnnotationPresent(RichTextElement.Exclusive.class)) .map(exclusiveClassMap::getUnchecked) .flatMap(Collection::stream) .filter(RichTextElement.class::isAssignableFrom) .map(b -> b.getAnnotation(RichTextElement.Tag.class)) .filter(Objects::nonNull) .map(RichTextElement.Tag::value) .map(String::trim) .filter(p -> !ObjectUtils.isBlank(p)) .collect(Collectors.toSet()); exclusiveTags.remove(tagName); if (!exclusiveTags.isEmpty()) { clearContextMap.put(tagName, exclusiveTags); } String menu = tag.menu().trim(); if (!menu.isEmpty()) { richTextElement.put("submenu", menu); } String styleName = type.getInternalName().replace(".", "-"); tagNameToStyleNameMap.put(tagName, styleName); richTextElement.put("styleName", styleName); richTextElement.put("typeId", type.getId().toString()); richTextElement.put("displayName", type.getDisplayName()); richTextElement.put("tooltipText", tag.tooltip()); if (!ObjectUtils.isBlank(tag.keymaps())) { richTextElement.put("keymap", tag.keymaps()); } richTextElements.add(richTextElement); } } richTextElements.sort( Comparator.comparing((Map<String, Object> r) -> r.get("position"), (r1, r2) -> ObjectUtils.compare(r1, r2, false)) .thenComparing(r -> r.get("styleName"), (r1, r2) -> ObjectUtils.compare(r1, r2, false))); for (Map<String, Object> richTextElement : richTextElements) { String tagName = (String) richTextElement.get("tag"); Set<String> context = contextMap.get(tagName); Set<String> clearContext = clearContextMap.get(tagName); if (!ObjectUtils.isBlank(clearContext)) { Set<String> clearStyles = clearContext.stream() .map(tagNameToStyleNameMap::get) .collect(Collectors.toSet()); richTextElement.put("clear", clearStyles); } if (!ObjectUtils.isBlank(context)) { if (!ObjectUtils.isBlank(clearContext)) { context.addAll(clearContext); } richTextElement.put("context", context); } } String scriptPrefix = getCmsTool().isUseNonMinifiedJavaScript() ? "/script/" : "/script.min/"; writeStart("script", "type", "text/javascript"); write("var ROOT_PATH = '", getRequest().getContextPath(), "';"); write("var CONTEXT_PATH = '", cmsUrl("/"), "';"); write("var UPLOAD_PATH = ", "'" + getRequest().getContextPath() + StringUtils.ensureStart(Settings.getOrDefault(String.class, "dari/upload/path", "/_dari/upload"), "/"), "';"); write("var CSS_CLASS_GROUPS = ", ObjectUtils.toJson(cssClassGroups), ";"); write("var STANDARD_IMAGE_SIZES = ", ObjectUtils.toJson(standardImageSizes), ";"); write("var RTE_LEGACY_HTML = ", getCmsTool().isLegacyHtml(), ';'); write("var RTE_ENABLE_ANNOTATIONS = ", getCmsTool().isEnableAnnotations(), ';'); write("var DISABLE_TOOL_CHECKS = ", getCmsTool().isDisableToolChecks(), ';'); write("var COMMON_TIMES = ", ObjectUtils.toJson(commonTimes), ';'); write("var RICH_TEXT_TOOLBARS = ", ObjectUtils.toJson(richTextToolbars), ';'); write("var RICH_TEXT_ELEMENTS = ", ObjectUtils.toJson(richTextElements), ';'); write("var ENABLE_PADDED_CROPS = ", getCmsTool().isEnablePaddedCrop(), ';'); write("var DISABLE_CODE_MIRROR_RICH_TEXT_EDITOR = ", getCmsTool().isDisableCodeMirrorRichTextEditor() || (getUser() != null && getUser().isDisableCodeMirrorRichTextEditor()), ';'); write("var DISABLE_RTC = ", getCmsTool().isDisableRtc(), ';'); write("var DISABLE_EDIT_FIELD_UPDATE_CACHE = ", getCmsTool().isDisableEditFieldUpdateCache(), ';'); write("var DISABLE_AJAX_SAVES = ", getCmsTool().isDisableAjaxSaves(), ';'); writeEnd(); writeStart("script", "type", "text/javascript", "src", "//www.google.com/jsapi"); writeEnd(); writeStart("script", "type", "text/javascript", "src", ElFunctionUtils.resource(toolPath(CmsTool.class, scriptPrefix + "jquery.js"))); writeEnd(); writeStart("script", "type", "text/javascript", "src", ElFunctionUtils.resource(toolPath(CmsTool.class, scriptPrefix + "jquery.extra.js"))); writeEnd(); writeStart("script", "type", "text/javascript", "src", ElFunctionUtils.resource(toolPath(CmsTool.class, scriptPrefix + "handsontable.full.js"))); writeEnd(); writeStart("script", "type", "text/javascript", "src", ElFunctionUtils.resource(toolPath(CmsTool.class, scriptPrefix + "d3.js"))); writeEnd(); writeStart("script", "type", "text/javascript"); writeRaw("var require = "); writeRaw(ObjectUtils.toJson(ImmutableMap.of( "baseUrl", cmsUrl(scriptPrefix), "urlArgs", "_=" + System.currentTimeMillis()))); writeRaw(";"); writeEnd(); writeStart("script", "type", "text/javascript", "src", ElFunctionUtils.resource(toolPath(CmsTool.class, scriptPrefix + "require.js"))); writeEnd(); writeStart("script", "type", "text/javascript", "src", cms.isUseNonMinifiedJavaScript() ? cmsResource(scriptPrefix + theme + ".js") : ElFunctionUtils.resource(toolPath(CmsTool.class, scriptPrefix + theme + ".js"))); writeEnd(); String dropboxAppKey = getCmsTool().getDropboxApplicationKey(); if (!ObjectUtils.isBlank(dropboxAppKey)) { writeStart("script", "type", "text/javascript", "src", "https://www.dropbox.com/static/api/2/dropins.js", "id", "dropboxjs", "data-app-key", dropboxAppKey); writeEnd(); } for (Tool tool : tools) { tool.writeHeaderAfterScripts(this); } if (!ObjectUtils.isBlank(extraJavaScript)) { writeStart("script", "type", "text/javascript"); write(extraJavaScript); writeEnd(); } } /** * Writes the tool header with the given {@code title}. * * @param title If {@code null}, uses the default title. */ public void writeHeader(String title) throws IOException { writeHeader(title, true); } /** * Writes the tool header with the default title. */ public void writeHeader() throws IOException { writeHeader(null, true); } /** Writes the tool footer. */ public void writeFooter() throws IOException { if (isAjaxRequest() || param(boolean.class, "_frame")) { return; } writeTag("/div"); writeStart("div", "class", "toolFooter"); writeStart("a", "target", "_blank", "href", "http://www.brightspot.com/"); writeElement("img", "src", cmsUrl("/style/brightspot.png"), "alt", "Brightspot", "width", 104, "height", 14); writeEnd(); writeEnd(); if (getCmsTool().isEnableCrossDomainInlineEditing() && !Query.from(Site.class).hasMoreThan(100)) { Set<String> siteUrls = new HashSet<String>(); for (Site s : Query.from(Site.class).selectAll()) { for (String url : s.getUrls()) { try { String siteUrl = new URL(url).toURI().resolve("/").toString(); if (siteUrl.startsWith("http://")) { siteUrls.remove("https://" + siteUrl.substring(7)); } else if (siteUrl.startsWith("https://")) { String insecureSiteUrl = "http://" + siteUrl.substring(8); if (siteUrls.contains(insecureSiteUrl)) { continue; } } siteUrls.add(siteUrl); } catch (MalformedURLException error) { // Ignore invalid site URL. } catch (URISyntaxException error) { // Ignore invalid site URL. } } } ToolUser user = getUser(); String userId = user != null ? user.getId().toString() : UUID.randomUUID().toString(); String token = (String) getRequest().getAttribute(AuthenticationFilter.USER_TOKEN); String signature = StringUtils.hex(StringUtils.hmacSha1(Settings.getSecret(), userId + token)); String cookiePath = StringUtils.addQueryParameters( cmsUrl("/inlineEditorCookie"), "userId", userId, "token", token, "signature", signature) .substring(1); for (String siteUrl : siteUrls) { writeStart("img", "src", siteUrl + cookiePath, "style", cssString( "height", "1px", "width", "1px", "visibility", "hidden")); writeEnd(); } } writeTag("/body"); writeTag("/html"); } /** * Writes a {@code <select>} tag that allows the user to pick multiple * content types. * * @param types Types that the user is allowed to select from. * If {@code null}, all content types will be available. * @param selectedTypes Types that should be initially selected. * @param attributes Attributes for the {@code <select>} tag. */ public void writeMultipleTypeSelect( Iterable<ObjectType> types, Collection<ObjectType> selectedTypes, Object... attributes) throws IOException { writeTypeSelectReally( true, false, types, selectedTypes != null ? selectedTypes : Collections.<ObjectType>emptySet(), null, attributes); } public void writeCreateTypeSelect( Iterable<ObjectType> types, ObjectType selectedType, String allLabel, Object... attributes) throws IOException { writeTypeSelectReally( false, true, types, selectedType != null ? Arrays.asList(selectedType) : Collections.<ObjectType>emptySet(), allLabel, attributes); } /** * Writes a {@code <select>} tag that allows the user to pick a content * type. * * @param types Types that the user is allowed to select from. * If {@code null}, all content types will be available. * @param selectedType Type that should be initially selected. * @param allLabel Label for the option that selects all types. * If {@code null}, the option won't be available. * @param attributes Attributes for the {@code <select>} tag. */ public void writeTypeSelect( Iterable<ObjectType> types, ObjectType selectedType, String allLabel, Object... attributes) throws IOException { writeTypeSelectReally( false, false, types, selectedType != null ? Arrays.asList(selectedType) : Collections.<ObjectType>emptySet(), allLabel, attributes); } /** * Generates a {@code Predicate<ObjectType>} to filter {@link ObjectType}s against CMS display criteria * and optionally check the specified type-level permission against the current * {@link ToolUser}'s permissions. * @param permissions A List of the type-level permissions to be checked. If {@code null}, * type permission will not be checked. * @return a new {@code Predicate<ObjectType>} */ public java.util.function.Predicate<ObjectType> createTypeDisplayPredicate(Collection<String> permissions) { return (ObjectType type) -> { Class<?> c = type.getObjectClass(); return type.isConcrete() && (c == null || !Modification.class.isAssignableFrom(c)) && (ObjectUtils.isBlank(permissions) || permissions.stream().allMatch((String permission) -> hasPermission("type/" + type.getId() + "/" + permission))) && (getCmsTool().isDisplayTypesNotAssociatedWithJavaClasses() || c != null) && !(Draft.class.equals(type.getObjectClass())) && (!type.isDeprecated() || Query.fromType(type).hasMoreThan(0)) && !ObjectUtils.to(boolean.class, Localization.currentUserText(type, "hidden", String.valueOf(false))); }; } private void writeTypeSelectReally( boolean multiple, boolean create, Iterable<ObjectType> types, Collection<ObjectType> selectedTypes, String allLabel, Object... attributes) throws IOException { if (types == null) { types = Database.Static.getDefault().getEnvironment().getTypes(); } List<ObjectType> miscTypes = ObjectUtils.to(new TypeReference<List<ObjectType>>() { }, types); if (!create) { for (ObjectType type : Database.Static.getDefault().getEnvironment().getTypes()) { if (Boolean.FALSE.equals(type.as(ToolUi.class).getHidden()) && !type.isConcrete()) { if (miscTypes.containsAll(type.findConcreteTypes())) { miscTypes.add(type); } } } } Map<String, List<ObjectType>> typeGroups = new LinkedHashMap<String, List<ObjectType>>(); List<ObjectType> mainTypes = Template.Static.findUsedTypes(getSite()); mainTypes.retainAll(miscTypes); miscTypes.removeAll(mainTypes); List<ObjectType> toolUiMainTypes = miscTypes.stream() .filter(t -> t.as(ToolUi.class).isMain()) .collect(Collectors.toList()); mainTypes.addAll(toolUiMainTypes); miscTypes.removeAll(toolUiMainTypes); typeGroups.put(localize(null, "label.mainTypes"), mainTypes); typeGroups.put(localize(null, "label.miscTypes"), miscTypes); for (Iterator<List<ObjectType>> i = typeGroups.values().iterator(); i.hasNext();) { List<ObjectType> typeGroup = i.next(); if (typeGroup.isEmpty()) { i.remove(); } else { Collections.sort(typeGroup); } } writeStart("select", "multiple", multiple ? "multiple" : null, attributes); if (allLabel != null) { writeStart("option", "value", "").writeHtml(allLabel).writeEnd(); } if (typeGroups.size() == 1) { writeTypeSelectGroup(selectedTypes, typeGroups.values().iterator().next(), create); } else { for (Map.Entry<String, List<ObjectType>> entry : typeGroups.entrySet()) { writeStart("optgroup", "label", entry.getKey()); writeTypeSelectGroup(selectedTypes, entry.getValue(), create); writeEnd(); } } writeEnd(); } private void writeTypeSelectGroup(Collection<ObjectType> selectedTypes, List<ObjectType> types, boolean create) throws IOException { String previousLabel = null; for (ObjectTypeOrContentTemplate otct : getObjectTypeOrContentTemplates(types, create)) { ObjectType type = otct.getType(); String label = otct.getLabel(); writeStart("option", "selected", selectedTypes.contains(type) && otct.getTemplate() == null ? "selected" : null, "value", otct.getId()); writeHtml(label); if (label.equals(previousLabel)) { writeHtml(" ("); writeHtml(type.getInternalName()); writeHtml(")"); } writeEnd(); previousLabel = label; } } public List<?> findDropDownItems(ObjectField field, Search dropDownSearch) { List<?> items; if (field.getTypes().contains(ObjectType.getInstance(ObjectType.class))) { List<ObjectType> types = new ArrayList<ObjectType>(); Predicate predicate = dropDownSearch.toQuery(getSite()).getPredicate(); for (ObjectType t : Database.Static.getDefault().getEnvironment().getTypes()) { if (t.is(predicate)) { types.add(t); } } items = new ArrayList<Object>(types); } else { items = dropDownSearch.toQuery(getSite()).selectAll(); } return items; } /** * Writes a {@code <select>} or {@code <input>} tag that allows the user * to pick a content. * * @param field Can't be {@code null}. * @param value Initial value. May be {@code null}. * @param attributes Extra attributes for the HTML tag. */ public void writeObjectSelect(ObjectField field, Object value, Object... attributes) throws IOException { writeObjectSelect(field, value, param(UUID.class, OBJECT_ID_PARAMETER), param(UUID.class, TYPE_ID_PARAMETER), attributes); } /** * Writes a {@code <select>} or {@code <input>} tag that allows the user * to pick a content. * @param field Can't be {@code null}. * @param value Initial value. May be {@code null}. * @param parentId ID of parent object. Will be obtained from query parameter {@code OBJECT_ID_PARAMETER} if {@code null}. * @param parentTypeId ObjectType ID of parent object. Will be obtained from query parameter {@code TYPE_ID_PARAMETER} if {@code null}. * @param attributes Extra attributes for the HTML tag. * @throws IOException */ public void writeObjectSelect(ObjectField field, Object value, UUID parentId, UUID parentTypeId, Object... attributes) throws IOException { ErrorUtils.errorIfNull(field, "field"); ToolUi ui = field.as(ToolUi.class); String placeholder = ObjectUtils.firstNonNull(ui.getPlaceholder(), ""); if (field.isRequired()) { placeholder += " (Required)"; } if (isObjectSelectDropDown(field)) { Search dropDownSearch = new Search(field); dropDownSearch.setParentId(parentId); dropDownSearch.setParentTypeId(parentTypeId); List<?> items = findDropDownItems(field, dropDownSearch); String sortField = field.as(ToolUi.class).getDropDownSortField(); if (StringUtils.isBlank(sortField)) { sortField = "_label"; } Collections.sort(items, new ObjectFieldComparator(sortField, false)); if (field.as(ToolUi.class).isDropDownSortDescending()) { Collections.reverse(items); } writeStart("select", "data-searchable", "true", "data-dynamic-placeholder", ui.getPlaceholderDynamicText(), "data-dynamic-field-name", field.getInternalName(), "placeholder", placeholder, attributes); writeStart("option", "value", ""); writeEnd(); for (Object item : items) { State itemState = State.getInstance(item); writeStart("option", "data-drop-down-html", item instanceof DropDownDisplay ? ((DropDownDisplay) item).createDropDownDisplayHtml() : "", "selected", item.equals(value) ? "selected" : null, "value", itemState.getId()); writeObjectLabel(item); writeEnd(); } writeEnd(); } else { State state = State.getInstance(value); StringBuilder typeIds = new StringBuilder(); for (ObjectType type : field.getTypes()) { typeIds.append(type.getId()); typeIds.append(','); } if (typeIds.length() > 0) { typeIds.setLength(typeIds.length() - 1); } boolean canEdit = true; if (value != null) { ObjectType type = state.getType(); canEdit = hasPermission("type/" + type.getId() + "/write") && !type.as(ToolUi.class).isReadOnly() && ContentEditable.shouldContentBeEditable(state); } String inputSearcherPath = ui.getInputSearcherPath(); String dynamicInputSearcherPath = ui.getDynamicInputSearcherPath(); writeElement("input", "type", "text", "class", "objectId", "data-dynamic-predicate", field.getPredicate(), "data-generic-argument-index", field.getGenericArgumentIndex(), "data-dynamic-placeholder", ui.getPlaceholderDynamicText(), "data-dynamic-field-name", field.getInternalName(), "data-label", value != null ? getObjectLabel(value) : null, "data-label-html", value != null ? createObjectLabelHtml(value) : null, "data-pathed", ToolUi.isOnlyPathed(field), "data-preview", getPreviewThumbnailUrl(value), "data-dynamic-searcher-path", !ObjectUtils.isBlank(dynamicInputSearcherPath) ? dynamicInputSearcherPath : null, "data-searcher-path", !ObjectUtils.isBlank(inputSearcherPath) ? inputSearcherPath : null, "data-suggestions", ui.isEffectivelySuggestions(), "data-typeIds", typeIds, "data-visibility", value != null ? state.getVisibilityLabel() : null, "data-read-only", !canEdit, "value", value != null ? state.getId() : null, "placeholder", placeholder, attributes); } } /** * Writes a {@code <select>} tag that allows the user to pick a * visibility status. * * @param type May be {@code null}. * @param values Initial values. May be {@code null}. * @param attributes May be {@code null}. */ public void writeMultipleVisibilitySelect( ObjectType type, Collection<String> values, Object... attributes) throws IOException { if (values == null) { values = Collections.emptySet(); } Map<String, String> statuses = new HashMap<String, String>(); statuses.put("p", "Published"); statuses.put("d", localize(type, "visibility.draft")); boolean hasWorkflow = false; for (Workflow w : (type == null ? Query.from(Workflow.class) : Query.from(Workflow.class).where("contentTypes = ?", type)).selectAll()) { for (WorkflowState s : w.getStates()) { hasWorkflow = true; statuses.put("w." + s.getName(), s.getDisplayName()); } } if (hasWorkflow) { statuses.put("w", "In Workflow"); } addVisibilityStatuses(statuses, Database.Static.getDefault().getEnvironment()); addVisibilityStatuses(statuses, type); List<Map.Entry<String, String>> sortedStatuses = new ArrayList<Map.Entry<String, String>>(statuses.entrySet()); Collections.sort(sortedStatuses, new Comparator<Map.Entry<String, String>>() { @Override public int compare(Map.Entry<String, String> x, Map.Entry<String, String> y) { return x.getValue().compareTo(y.getValue()); } }); writeStart("select", "multiple", "multiple", "placeholder", "Status (Published)", attributes); for (Map.Entry<String, String> entry : sortedStatuses) { String key = entry.getKey(); writeStart("option", "selected", values.contains(key) ? "selected" : null, "value", key); writeHtml(entry.getValue()); writeEnd(); } writeEnd(); } private void addVisibilityStatuses(Map<String, String> statuses, ObjectStruct struct) { if (struct == null) { return; } for (ObjectIndex index : struct.getIndexes()) { if (index.isVisibility()) { for (String fieldName : index.getFields()) { ObjectField field = struct.getField(fieldName); if (field != null) { String type = field.getInternalItemType(); if (ObjectField.BOOLEAN_TYPE.equals(type)) { String displayName = field.getDisplayName(); if (displayName.endsWith("?")) { displayName = displayName.substring(0, displayName.length() - 1); } statuses.put("b." + field.getUniqueName(), displayName); } else if (ObjectField.TEXT_TYPE.equals(type)) { Set<ObjectField.Value> values = field.getValues(); if (values != null && !values.isEmpty()) { for (ObjectField.Value value : values) { statuses.put("t." + field.getUniqueName() + "=" + value.getValue(), field.getDisplayName() + ": " + value.getLabel()); } } } } } } } } /** * Returns {@code true} if the {@code <select>} tag would be used to allow * the user to pick a content for the given {@code field}. * * @param field Can't be {@code null}. */ public boolean isObjectSelectDropDown(ObjectField field) { ErrorUtils.errorIfNull(field, "field"); if (field.as(ToolUi.class).isDropDown()) { long dropDownMaximum = Settings.getOrDefault(long.class, "cms/tool/dropDownMaximum", 250L); if (field.getTypes().contains(ObjectType.getInstance(ObjectType.class))) { Set<ObjectType> types = Database.Static.getDefault().getEnvironment().getTypes(); if (types.size() <= dropDownMaximum) { return true; } else if (field.getPredicate() != null) { long numFilteredTypes = 0; for (ObjectType type : types) { if (type.is(field.getPredicate())) { if (++numFilteredTypes > dropDownMaximum) { break; } } } return (numFilteredTypes <= dropDownMaximum); } } else { return !new Search(field).toQuery(getSite()).hasMoreThan(dropDownMaximum); } } return false; } /** Writes all grid CSS, or does nothing if it's already written. */ public ToolPageContext writeGridCssOnce() throws IOException { LayoutTag.Static.writeGridCss(this, getServletContext(), getRequest()); return this; } /** * Writes the heading that precedes the form to create or update the * given {@code object}. * * @param attributes Extra attributes for the heading element. */ public void writeFormHeading(Object object, Object... attributes) throws IOException { State state = State.getInstance(object); ObjectType type = state.getType(); String typeLabel = getTypeLabel(object); String iconName = null; if (type != null) { iconName = type.as(ToolUi.class).getIconName(); } if (ObjectUtils.isBlank(iconName)) { iconName = "object"; } writeStart("h1", "class", "icon icon-" + iconName, attributes); if (state.isNew()) { writeHtml("New "); writeHtml(typeLabel); } else { writeHtml("Edit "); writeHtml(typeLabel); } writeEnd(); } /** * Disables all form fields after this call so that they're displayed but * not processed on update. */ public void disableFormFields() { HttpServletRequest request = getRequest(); Integer disabled = (Integer) request.getAttribute(FORM_FIELDS_DISABLED_ATTRIBUTE); request.setAttribute(FORM_FIELDS_DISABLED_ATTRIBUTE, disabled != null ? disabled + 1 : 1); } /** * Enables all form fields after this call so that they're both displayed * and processed on update. */ public void enableFormFields() { HttpServletRequest request = getRequest(); Integer disabled = (Integer) request.getAttribute(FORM_FIELDS_DISABLED_ATTRIBUTE); if (disabled != null) { request.setAttribute(FORM_FIELDS_DISABLED_ATTRIBUTE, disabled - 1); } } /** * Returns {@code true} if the form fields are enabled to be both * displayed and processed on update. */ public boolean isFormFieldsDisabled() { Integer disabled = (Integer) getRequest().getAttribute(FORM_FIELDS_DISABLED_ATTRIBUTE); return disabled != null && disabled > 0; } /** * Writes a contextual message if the given {@code object} is in trash. * * @param object Can't be {@code null}. * @return {@code true} if the message was written. */ public boolean writeTrashMessage(Object object) throws IOException { State state = State.getInstance(object); Content.ObjectModification contentData = state.as(Content.ObjectModification.class); if (!contentData.isTrash()) { return false; } boolean canRestore = hasPermission("type/" + state.getType().getId() + "/restore"); boolean canDelete = hasPermission("type/" + state.getType().getId() + "/delete"); writeStart("div", "class", "message message-warning"); writeStart("p"); writeHtml("Archived "); writeHtml(formatUserDateTime(contentData.getUpdateDate())); writeHtml(" by "); writeObjectLabel(contentData.getUpdateUser()); writeHtml("."); writeEnd(); if (canRestore || canDelete) { writeStart("div", "class", "actions"); if (canRestore) { writeStart("button", "class", "link icon icon-action-restore", "name", "action-restore", "value", "true"); writeHtml("Restore"); writeEnd(); } if (canDelete) { writeStart("button", "class", "link icon icon-action-delete", "name", "action-delete", "value", "true"); writeHtml("Delete Permanently"); writeEnd(); } writeEnd(); } writeEnd(); return true; } private void includeFromCms(String path, Object... attributes) throws IOException, ServletException { JspUtils.include(getRequest(), getResponse(), getWriter(), toolPath(CmsTool.class, path), attributes); } /** * Writes some form fields for the given {@code object}. * * @param object Can't be {@code null}. * @param includeGlobals {@code true} to include global fields. * @param includeFields {@code null} to include all fields. * @param excludeFields {@code null} to exclude no fields. */ public void writeSomeFormFields( Object object, boolean includeGlobals, boolean displayTabContentEditWidgets, Collection<String> includeFields, Collection<String> excludeFields) throws IOException, ServletException { State state = State.getInstance(object); ObjectType type = state.getType(); List<ObjectField> fields = new ArrayList<>(); if (type != null) { fields.addAll(type.getFields()); } if (includeGlobals && !fields.isEmpty()) { writeElement("input", "type", "hidden", "name", state.getId() + "/_includeGlobals", "value", true); for (ObjectField field : state.getDatabase().getEnvironment().getFields()) { if (Boolean.FALSE.equals(field.getState().get("cms.ui.hidden"))) { fields.add(field); } } } HttpServletRequest request = getRequest(); Object oldContainer = request.getAttribute("containerObject"); try { if (oldContainer == null) { request.setAttribute("containerObject", object); } List<ToolUiLayoutElement> layoutPlaceholders = type != null ? type.as(ToolUi.class).getLayoutPlaceholders() : null; String layoutPlaceholdersJson = null; if (!ObjectUtils.isBlank(layoutPlaceholders)) { List<Map<String, Object>> jsons = new ArrayList<Map<String, Object>>(); for (ToolUiLayoutElement element : layoutPlaceholders) { jsons.add(element.toMap()); } layoutPlaceholdersJson = ObjectUtils.toJson(jsons); } StringBuilder cssClass = new StringBuilder("objectInputs"); if ((type != null && type.as(ToolUi.class).isReadOnly()) || !ContentEditable.shouldContentBeEditable(state)) { cssClass.append(" objectInputs-readOnly"); } if (type != null) { String customCssClass = type.as(ToolUi.class).getCssClass(); if (!StringUtils.isBlank(customCssClass)) { cssClass.append(' '); cssClass.append(customCssClass); } } writeStart("div", "class", cssClass, "lang", type != null ? type.as(ToolUi.class).getLanguageTag() : null, "data-type", type != null ? type.getInternalName() : null, "data-id", state.getId(), "data-object-id", state.getId(), "data-layout-placeholders", layoutPlaceholdersJson); if (type != null) { String noteHtml = type.as(ToolUi.class).getEffectiveNoteHtml(object); if (!ObjectUtils.isBlank(noteHtml)) { write("<div class=\"message message-info\">"); write(noteHtml); write("</div>"); } } if (object instanceof Query) { writeStart("div", "class", "queryField"); writeElement("input", "type", "text", "name", state.getId() + "/_query", "value", ObjectUtils.toJson(state.getSimpleValues())); writeEnd(); } else if (!fields.isEmpty()) { ContentType ct = type != null ? Query.from(ContentType.class).where("internalName = ?", type.getInternalName()).first() : null; if (ct != null) { List<ObjectField> firsts = new ArrayList<ObjectField>(); for (ContentField cf : ct.getFields()) { for (Iterator<ObjectField> i = fields.iterator(); i.hasNext();) { ObjectField field = i.next(); if (field.getInternalName().equals(cf.getInternalName())) { firsts.add(field); i.remove(); break; } } } fields.addAll(0, firsts); } else { List<ObjectField> firsts = new ArrayList<ObjectField>(); List<ObjectField> lasts = new ArrayList<ObjectField>(); for (Iterator<ObjectField> i = fields.iterator(); i.hasNext();) { ObjectField field = i.next(); ToolUi ui = field.as(ToolUi.class); if (ui.isDisplayFirst()) { firsts.add(field); i.remove(); } else if (ui.isDisplayLast()) { lasts.add(field); i.remove(); } } fields.addAll(0, firsts); fields.addAll(lasts); } // prevents empty tab from displaying on Singletons fields.removeIf(f -> f.getInternalName().equals("dari.singleton.key")); // Do not display fields with @ToolUi.CollectionItemWeight, @ToolUi.CollectionItemToggle, or @ToolUiCollectionItemProgress fields.removeIf(f -> { ToolUi ui = f.as(ToolUi.class); return ui.isCollectionItemToggle() || ui.isCollectionItemWeight() || ui.isCollectionItemWeightColor() || ui.isCollectionItemWeightMarker() || ui.isCollectionItemProgress(); }); DependencyResolver<ObjectField> resolver = new DependencyResolver<>(); Map<String, ObjectField> fieldByName = fields.stream() .collect(Collectors.toMap(ObjectField::getInternalName, Function.identity())); fields.forEach(field -> { ToolUi ui = field.as(ToolUi.class); toFields(fieldByName, ui.getDisplayAfter()) .forEach(afterField -> resolver.addRequired(field, afterField)); toFields(fieldByName, ui.getDisplayBefore()) .forEach(beforeField -> resolver.addRequired(beforeField, field)); }); List<ObjectField> dependentFields = resolver.resolve(); for (int i = 1, size = dependentFields.size(); i < size; ++ i) { int beforeIndex = fields.indexOf(dependentFields.get(i - 1)); int afterIndex = fields.indexOf(dependentFields.get(i)); if (beforeIndex > afterIndex) { fields.add(afterIndex, fields.remove(beforeIndex)); } } List<ObjectField> orderedFields = toFields(fieldByName, type.as(ToolUi.class).getFieldDisplayOrder()) .collect(Collectors.toList()); fields.removeAll(orderedFields); fields.addAll(0, orderedFields); boolean draftCheck = false; try { if (request.getAttribute("firstDraft") == null) { draftCheck = true; request.setAttribute("firstDraft", state.isNew()); request.setAttribute("finalDraft", !state.isNew() && !state.as(Content.ObjectModification.class).isDraft() && state.as(Workflow.Data.class).getCurrentState() == null && getOverlaidDraft(object) == null); } for (ObjectField field : fields) { String name = field.getInternalName(); if ((includeFields == null || includeFields.contains(name)) && (excludeFields == null || !excludeFields.contains(name))) { renderField(object, field); } } } finally { if (draftCheck) { request.setAttribute("firstDraft", null); request.setAttribute("finalDraft", null); } } for (Class<? extends Tab> t : ClassFinder.findConcreteClasses(Tab.class)) { Tab tab = TypeDefinition.getInstance(t).newInstance(); if (tab.shouldDisplay(object)) { writeStart("div", "class", "Tab", "data-tab", tab.getDisplayName(), "data-tab-class", t.getName()); tab.writeHtml(this, object); writeEnd(); } } if (displayTabContentEditWidgets) { Edit.writeWidgets(this, object, ContentEditWidgetPlacement.TAB); } } writeEnd(); } finally { if (oldContainer == null) { request.setAttribute("containerObject", null); } } } /** * Writes some form fields for the given {@code object}. * * @param object Can't be {@code null}. * @param includeGlobals {@code true} to include global fields. * @param includeFields {@code null} to include all fields. * @param excludeFields {@code null} to exclude no fields. */ public void writeSomeFormFields( Object object, boolean includeGlobals, Collection<String> includeFields, Collection<String> excludeFields) throws IOException, ServletException { writeSomeFormFields(object, includeGlobals, false, includeFields, excludeFields); } private static Stream<ObjectField> toFields(Map<String, ObjectField> fieldByName, Collection<String> fieldNames) { return fieldNames.stream() .map(fieldByName::get) .filter(f -> f != null); } /** * Writes all form fields for the given {@code object}. * * @param object Can't be {@code null}. */ public void writeFormFields(Object object) throws IOException, ServletException { writeSomeFormFields(object, false, null, null); } public void writeStandardForm(Object object, boolean displayTrashAction) throws IOException, ServletException { writeStandardForm(object, displayTrashAction, false); } /** * Writes a standard form for the given {@code object}. * * @param object Can't be {@code null}. * @param displayTrashAction If {@code null}, displays the trash action * instead of the delete action. * @param displayCopyAction If {@code true}, displays the create a copy action */ public void writeStandardForm(Object object, boolean displayTrashAction, boolean displayCopyAction) throws IOException, ServletException { State state = State.getInstance(object); ObjectType type = state.getType(); writeFormHeading(object); writeStart("div", "class", "widgetControls"); includeFromCms("/WEB-INF/objectVariation.jsp", "object", object); writeEnd(); if (displayCopyAction && !State.getInstance(object).isNew() && !(object instanceof com.psddev.dari.db.Singleton) && !State.getInstance(object).getType().as(ToolUi.class).isReadOnly()) { writeStart("div", "class", "widget-contentCreate"); writeStart("div", "class", "action action-create"); writeHtml(h(localize("com.psddev.cms.tool.page.content.Edit", "action.new"))); writeEnd(); writeStart("ul"); writeStart("li"); writeStart("a", "class", "action action-create", "href", typeUrl(null, type.getId())); writeHtml(h(localize(state.getType(), "action.newType"))); writeEnd(); writeEnd(); writeStart("li"); writeStart("a", "class", "action action-copy", "href", typeUrl(null, type.getId(), "copyId", state.getId()), "target", "_top"); writeHtml(h(localize(state.getType(), "action.copy"))); writeEnd(); writeEnd(); writeEnd(); writeEnd(); } includeFromCms("/WEB-INF/objectMessage.jsp", "object", object); writeStart("form", "class", "standardForm", "method", "post", "enctype", "multipart/form-data", "action", url("", "typeId", state.getTypeId(), "id", state.getId()), "autocomplete", "off", "data-type", type != null ? type.getInternalName() : null); boolean trash = writeTrashMessage(object); writeFormFields(object); if (!trash) { writeStart("div", "class", "actions"); writeStart("button", "class", "icon icon-action-save", "name", "action-save", "value", "true"); writeHtml("Save"); writeEnd(); if (!state.isNew() && (type == null || (!type.getGroups().contains(Singleton.class.getName()) && !type.getGroups().contains(Tool.class.getName())))) { if (displayTrashAction) { writeStart("button", "class", "icon icon-action-trash action-pullRight link", "name", "action-trash", "value", "true"); writeHtml("Archive"); writeEnd(); } else { writeStart("button", "class", "icon icon-action-delete action-pullRight link", "name", "action-delete", "value", "true"); writeHtml("Delete"); writeEnd(); } } writeEnd(); } writeEnd(); } /** * Given an object of type {@code T} write the HTML for the given {@code viewType}. * * @param object to render using the given {@code viewType}. * @param viewType type from {@link com.psddev.cms.view.ViewBinding} to render the object. * * @throws IOException */ public void writeViewHtml(Object object, String viewType) throws IOException { Preconditions.checkNotNull(object); Class<? extends ViewModel> viewModelClass = ViewModel.findViewModelClass(viewType, object); Preconditions.checkNotNull(viewModelClass, String.format( "Could not find view model for object of type [%s] and view of type [%s]", object.getClass().getName(), viewType)); writeViewHtml(object, viewModelClass); } /** * Writes the HTML for the view of the object using the given {@code viewModelClass} * * @param object * Can't be {@code null. * * @param viewModelClass * Can't be {@code null}. * * @throws IOException */ public void writeViewHtml(Object object, Class<? extends ViewModel> viewModelClass) throws IOException { Preconditions.checkNotNull(object); ViewResponse viewResponse = new ViewResponse(); ViewModelCreator viewModelCreator = new ServletViewModelCreator(getRequest()); ViewModel<?> viewModel = null; try { viewModel = viewModelCreator.createViewModel(viewModelClass, object, viewResponse); } catch (RuntimeException e) { ViewResponse vr = ViewResponse.findInExceptionChain(e); if (vr != null) { viewResponse = vr; } else { throw e; } } Preconditions.checkNotNull(viewModel, String.format( "Failed to create a view model of type [%s] for object of type [%s] and view of class [%s]!", viewModelClass.getName(), object.getClass().getName(), viewModelClass.getClass().getName())); ViewRenderer renderer = Preconditions.checkNotNull(ViewRenderer.createRenderer(viewModel), String.format( "Could not create view renderer for view of type [%s]", viewModel.getClass().getName())); String output = null; try { ViewOutput result = renderer.render(viewModel, new ClassResourceViewTemplateLoader(object.getClass())); output = result.get(); } catch (RuntimeException e) { ViewResponse vr = ViewResponse.findInExceptionChain(e); if (vr != null) { viewResponse = vr; } else { throw e; } } ToolPageContext.updateViewResponse(getRequest(), (HttpServletResponse) JspUtils.getHeaderResponse(getRequest(), getResponse()), viewResponse); if (output != null) { write(output); } } // Duplicated from PageFilter, should probably live somewhere reusable // Copies the information on the ViewResponse to the actual http servlet response. private static void updateViewResponse(HttpServletRequest request, HttpServletResponse response, ViewResponse viewResponse) { Integer status = viewResponse.getStatus(); if (status != null) { response.setStatus(status); } for (Map.Entry<String, List<String>> entry : viewResponse.getHeaders().entrySet()) { String name = entry.getKey(); List<String> values = entry.getValue(); for (String value : values) { response.addHeader(name, value); } } viewResponse.getCookies().forEach(response::addCookie); viewResponse.getSignedCookies().forEach(cookie -> JspUtils.setSignedCookie(response, cookie)); String redirectUrl = viewResponse.getRedirectUri(); if (redirectUrl != null) { try { JspUtils.redirect(request, response, redirectUrl); } catch (IOException e) { // ignore } } } /** * Writes a standard form for the given {@code object} with the trash * action. * * @param object Can't be {@code null}. * @see #writeStandardForm(Object, boolean) */ public void writeStandardForm(Object object) throws IOException, ServletException { writeStandardForm(object, true); } /** * Writes a link that points to either the Javadoc or the source for the * given {@code objectClass}. * * @param objectClass Can't be {@code null}. */ public void writeJavaClassLink(Class<?> objectClass) throws IOException { String objectClassName = objectClass.getName(); String javadocUrlPrefix; if (objectClassName.startsWith("com.psddev.cms.db.")) { javadocUrlPrefix = "http://public.psddev.com/javadoc/brightspot-cms/"; } else if (objectClassName.startsWith("com.psddev.dari.db.")) { javadocUrlPrefix = "http://public.psddev.com/javadoc/dari/"; } else { javadocUrlPrefix = null; } if (ObjectUtils.isBlank(javadocUrlPrefix)) { File source = CodeUtils.getSource(objectClassName); if (source != null) { writeStart("a", "target", "_blank", "href", DebugFilter.Static.getServletPath(getRequest(), "code", "file", source)); writeStart("code"); writeHtml(objectClassName); writeEnd(); writeEnd(); } else { writeStart("code"); writeHtml(objectClassName); writeEnd(); } } else { writeStart("a", "target", "_blank", "href", javadocUrlPrefix + objectClassName.replace('.', '/').replace('$', '.') + ".html"); writeStart("code"); writeHtml(objectClassName); writeEnd(); writeEnd(); } } public void writeQueryRestrictionForm(Class<? extends QueryRestriction> queryRestrictionClass) throws IOException { QueryRestriction qr = TypeDefinition.getInstance(queryRestrictionClass).newInstance(); writeStart("form", "class", "queryRestrictions", "data-bsp-autosubmit", "", "method", "post", "action", url("", Search.OFFSET_PARAMETER, null)); qr.writeHtml(this); writeEnd(); } /** * Updates the given {@code object} using all request parameters. * * @param object Can't be {@code null}. */ public void updateUsingParameters(Object object) throws IOException, ServletException { includeFromCms("/WEB-INF/objectPost.jsp", "object", object); } /** * Updates the given {@code object} using all widgets with the data from * the current request. * * @param object Nonnull. * @deprecated Use {@link Edit#updateUsingWidgets(ToolPageContext, Object)} instead. */ @SuppressWarnings("deprecation") public void updateUsingAllWidgets(Object object) throws Exception { Edit.updateUsingWidgets(this, object); } private void redirectOnWorkflow(String url, Object... parameters) throws IOException { if (!param(boolean.class, "_frame") && getUser().isReturnToDashboardOnWorkflow()) { getResponse().sendRedirect(cmsUrl("/")); } else { redirectOnSave(url, parameters); } } private void redirectOnSave(String url, Object... parameters) throws IOException { if (param(String.class, "action-draftAndReturn") != null || param(String.class, "action-newDraftAndReturn") != null) { getResponse().sendRedirect(cmsUrl("/")); return; } boolean frame = param(boolean.class, "_frame"); if (!frame && getUser().isReturnToDashboardOnSave()) { getResponse().sendRedirect(cmsUrl("/")); } else { getResponse().sendRedirect(StringUtils.addQueryParameters( url(url, parameters), "_frame", frame ? Boolean.TRUE : null, "editAnyway", null)); } } /** * Tries to delete the given {@code object} if the user has asked for it * in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the delete is tried. */ public boolean tryDelete(Object object) { if (!isFormPost() || param(String.class, DELETE_ACTION_PARAMETER) == null) { return false; } State state = State.getInstance(object); if (!hasPermission("type/" + state.getTypeId() + "/delete")) { throw new IllegalStateException(String.format( "No permission to delete [%s]!", state.getType().getLabel())); } try { Overlay overlay = Edit.getOverlay(object); if (overlay != null) { overlay.getState().delete(); redirectOnSave(""); } else if (param(UUID.class, "draftId") != null) { Draft draft = getOverlaidDraft(object); if (draft != null) { draft.delete(); Schedule schedule = draft.getSchedule(); if (schedule != null && ObjectUtils.isBlank(schedule.getName())) { schedule.delete(); if (!draft.isNewContent()) { state.putAtomically("cms.content.scheduleDate", null); state.save(); } } if (draft.isNewContent()) { state.delete(); } } redirectOnSave(""); } else { state.delete(); Query.from(Draft.class) .where("objectId = ?", state.getId()) .deleteAll(); if (param(boolean.class, "_frame")) { writeStart("div", "id", createId()); writeEnd(); writeStart("script", "type", "text/javascript"); writeRaw("$('#").writeRaw(getId()).writeRaw("').popup('close');"); writeRaw("$('.search-reset').click();"); writeEnd(); return true; } getResponse().sendRedirect(cmsUrl("/")); } return true; } catch (Exception error) { getErrors().add(error); return false; } } public boolean tryUnschedule(Object object) { if (!isFormPost() || param(String.class, UNSCHEDULE_ACTION_PARAMETER) == null) { return false; } State state = State.getInstance(object); if (!hasPermission("type/" + state.getTypeId() + "/delete")) { throw new IllegalStateException(String.format( "No permission to delete [%s]!", state.getType().getLabel())); } try { Draft draft = getOverlaidDraft(object); if (draft != null) { Map<String, Object> diffs = draft.getDifferences().get(state.getId().toString()); if (diffs != null) { diffs.remove("cms.content.scheduleDate"); } Schedule schedule = draft.getSchedule(); if (schedule != null && ObjectUtils.isBlank(schedule.getName())) { if (draft.isNewContent()) { draft.delete(); } else { draft.save(); } schedule.delete(); } else { draft.setSchedule(null); draft.save(); } state.putAtomically("cms.content.scheduleDate", null); state.save(); } redirectOnSave(""); return true; } catch (Exception error) { getErrors().add(error); return false; } } /** * Returns the publish date from the content form. * * @return May be {@code null}. */ public Date getContentFormPublishDate() { Date publishDate = param(Date.class, "publishDate"); if (publishDate != null && publishDate.before(new Date(Database.Static.getDefault().now()))) { publishDate = null; } return publishDate; } /** * Sets the publish date from the content form as the schedule date * on the given {@code object}. * * @param object Can't be {@code null}. */ public void setContentFormScheduleDate(Object object) { State state = State.getInstance(object); Content.ObjectModification contentData = state.as(Content.ObjectModification.class); Date publishDate = getContentFormPublishDate(); if (publishDate != null) { contentData.setPublishDate(publishDate); } contentData.setScheduleDate(publishDate); } @SuppressWarnings("unchecked") private Map<String, Object> findOldValuesInForm(State state) { return (Map<String, Object>) ObjectUtils.fromJson(param(String.class, state.getId() + "/oldValues")); } private void updateCurrentWorkflowLog(State state) throws IOException, ServletException { UUID workflowLogId = param(UUID.class, "workflowLogId"); if (workflowLogId != null) { WorkflowLog log = new WorkflowLog(); log.getState().setId(workflowLogId); updateUsingParameters(log); state.as(Workflow.Data.class).setCurrentLog(log); } } /** * Tries to save the given {@code object} as a draft if the user has * asked for it in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the save is tried. */ public boolean tryDraft(Object object) { if (!isFormPost() || (param(String.class, DRAFT_ACTION_PARAMETER) == null && param(String.class, "action-draftAndReturn") == null)) { return false; } setContentFormScheduleDate(object); State state = State.getInstance(object); Draft draft = getOverlaidDraft(object); Site site = getSite(); try { updateUsingParameters(object); Edit.updateUsingWidgets(this, object); if (state.isNew() && site != null && site.getDefaultVariation() != null) { state.as(Variation.Data.class).setInitialVariation(site.getDefaultVariation()); } if (draft == null && (state.isNew() || state.as(Content.ObjectModification.class).isDraft())) { state.as(Content.ObjectModification.class).setDraft(true); } updateCurrentWorkflowLog(state); Map<String, Map<String, Object>> differences = Draft.findDifferences( state.getDatabase().getEnvironment(), findOldValuesInForm(state), state.getSimpleValues()); if (draft == null) { if (state.isNew() || state.as(Content.ObjectModification.class).isDraft()) { publishDifferences(object, differences); redirectOnSave("", "id", state.getId(), "copyId", null); return true; } else if (state.as(Workflow.Data.class).getCurrentState() != null) { publishDifferences(object, differences); redirectOnSave(""); return true; } draft = new Draft(); draft.setOwner(getUser()); } else if (draft.isNewContent()) { publishDifferences(object, differences); redirectOnSave(""); return true; } draft.update(findOldValuesInForm(state), object); publish(draft); deleteWorksInProgress(object); if (param(String.class, "action-draftAndReturn") != null) { getResponse().sendRedirect(cmsUrl("/")); } else { getResponse().sendRedirect(url("", "editAnyway", null, ToolPageContext.DRAFT_ID_PARAMETER, draft.getId(), ToolPageContext.HISTORY_ID_PARAMETER, null)); } return true; } catch (Exception error) { getErrors().add(error); return false; } } /** * Tries to create a new draft from the given {@code object} if the user * has asked for it in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the create is tried. */ public boolean tryNewDraft(Object object) { if (!isFormPost() || (param(String.class, NEW_DRAFT_ACTION_PARAMETER) == null && param(String.class, "action-newDraftAndReturn") == null)) { return false; } setContentFormScheduleDate(object); State state = State.getInstance(object); Site site = getSite(); boolean wasDraft = state.as(Content.ObjectModification.class).isDraft(); try { updateUsingParameters(object); Edit.updateUsingWidgets(this, object); if (state.isNew() && site != null && site.getDefaultVariation() != null) { state.as(Variation.Data.class).setInitialVariation(site.getDefaultVariation()); } updateCurrentWorkflowLog(state); if (state.isNew()) { state.as(Content.ObjectModification.class).setDraft(true); publish(state); redirectOnSave("", "id", state.getId(), "copyId", null); } else { Draft draft = new Draft(); draft.setOwner(getUser()); draft.update(findOldValuesInForm(state), object); publish(draft); if (param(String.class, "action-newDraftAndReturn") != null) { getResponse().sendRedirect(cmsUrl("/")); } else { getResponse().sendRedirect(url("", "editAnyway", null, ToolPageContext.DRAFT_ID_PARAMETER, draft.getId(), ToolPageContext.HISTORY_ID_PARAMETER, null, "_frame", param(boolean.class, "_frame") ? Boolean.TRUE : null)); } } return true; } catch (Exception error) { if (!wasDraft) { state.as(Content.ObjectModification.class).setDraft(false); } getErrors().add(error); return false; } } /** * Tries to publish or schedule the given {@code object} if the user has * asked for it in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the restore is tried. */ public boolean tryPublish(Object object) { if (!isFormPost() || param(String.class, PUBLISH_ACTION_PARAMETER) == null) { return false; } State state = State.getInstance(object); boolean newContent = state.isNew() || !state.isVisible(); Content.ObjectModification contentData = state.as(Content.ObjectModification.class); Draft draft = getOverlaidDraft(object); ToolUser user = getUser(); if (state.isNew() || object instanceof Draft || contentData.isDraft() || state.as(Workflow.Data.class).getCurrentState() != null) { if (getContentFormPublishDate() != null) { setContentFormScheduleDate(object); } else if (draft == null) { contentData.setPublishDate(new Date()); contentData.setPublishUser(user); } } UUID variationId = param(UUID.class, "variationId"); Site site = getSite(); try { state.beginWrites(); if (variationId == null || (site != null && ((state.isNew() && site.getDefaultVariation() != null) || ObjectUtils.equals(site.getDefaultVariation(), state.as(Variation.Data.class).getInitialVariation())))) { if (state.isNew() && site != null && site.getDefaultVariation() != null) { state.as(Variation.Data.class).setInitialVariation(site.getDefaultVariation()); } getRequest().setAttribute("original", object); includeFromCms("/WEB-INF/objectPost.jsp", "object", object, "original", object); Edit.updateUsingWidgets(this, object); if (variationId != null && variationId.equals(state.as(Variation.Data.class).getInitialVariation())) { state.putByPath("variations/" + variationId.toString(), null); } } else { Object original = Query .from(Object.class) .where("_id = ?", state.getId()) .noCache() .first(); Map<String, Object> oldStateValues = State.getInstance(original).getSimpleValues(); getRequest().setAttribute("original", original); includeFromCms("/WEB-INF/objectPost.jsp", "object", object, "original", original); Edit.updateUsingWidgets(this, object); Map<String, Object> newStateValues = state.getSimpleValues(); Set<String> stateKeys = new LinkedHashSet<String>(); Map<String, Object> stateValues = new LinkedHashMap<String, Object>(); stateKeys.addAll(oldStateValues.keySet()); stateKeys.addAll(newStateValues.keySet()); for (String key : stateKeys) { Object value = newStateValues.get(key); if (!ObjectUtils.equals(oldStateValues.get(key), value)) { stateValues.put(key, value); } } State.getInstance(original).putByPath("variations/" + variationId.toString(), stateValues); State.getInstance(original).getExtras().put("cms.variedObject", object); object = original; state = State.getInstance(object); } Workflow.Data workflowData = state.as(Workflow.Data.class); workflowData.changeState(null, user, (WorkflowLog) null); workflowData.setCurrentLog(null); Schedule schedule = user.getCurrentSchedule(); Date publishDate = null; if (schedule == null) { publishDate = getContentFormPublishDate(); } else if (draft == null) { draft = Query .from(Draft.class) .where("schedule = ?", schedule) .and("objectId = ?", object) .first(); } if (schedule != null || publishDate != null) { if (!state.validate()) { throw new ValidationException(Arrays.asList(state)); } boolean newSchedule = param(boolean.class, "newSchedule"); Map<String, Object> oldValues = findOldValuesInForm(state); if (draft != null && newSchedule) { oldValues = Draft.mergeDifferences( state.getDatabase().getEnvironment(), oldValues, draft.getDifferences()); } if (draft == null || newSchedule) { draft = new Draft(); draft.setOwner(user); if (newContent) { draft.setNewContent(true); } } draft.update(oldValues, object); if (state.isNew()) { contentData.setDraft(true); } if (draft.isNewContent()) { contentData.setDraft(true); publish(state); draft.setDifferences(null); } if (schedule == null) { schedule = draft.getSchedule(); } if (schedule == null) { schedule = new Schedule(); schedule.setTriggerSite(site); schedule.setTriggerUser(user); } if (publishDate != null) { schedule.setTriggerDate(publishDate); schedule.save(); } draft.setSchedule(schedule); publish(draft); state.commitWrites(); redirectOnSave("", ToolPageContext.DRAFT_ID_PARAMETER, draft.getId()); } else { if (draft != null) { draft.delete(); } if (draft != null || contentData.isDraft()) { contentData.setDraft(false); } if (!state.isVisible()) { contentData.setPublishDate(null); contentData.setPublishUser(null); } Overlay overlay = Edit.getOverlay(object); if (overlay != null) { state.putAtomically("cms.content.overlaid", Boolean.TRUE); state.save(); deleteWorksInProgress(object); } Map<String, Map<String, Object>> differences; if (draft != null) { draft.update(findOldValuesInForm(state), object); differences = draft.getDifferences(); Map<String, Object> newValues = differences.get(state.getId().toString()); if (newValues != null) { newValues.remove("cms.workflow.currentState"); } } else { differences = Draft.findDifferences( state.getDatabase().getEnvironment(), findOldValuesInForm(state), state.getSimpleValues()); } if (overlay != null) { overlay.setDifferences(differences); publish(overlay); } else { publishDifferences(object, differences); } state.commitWrites(); redirectOnSave("", "typeId", state.getTypeId(), "id", state.getId(), "historyId", null, "copyId", null, "ab", null, "published", System.currentTimeMillis()); } return true; } catch (Exception error) { getErrors().add(error); return false; } finally { state.endWrites(); } } /** * Tries to restore the given {@code object} if the user has asked for it * in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the restore is tried. */ public boolean tryRestore(Object object) { if (!isFormPost() || param(String.class, RESTORE_ACTION_PARAMETER) == null) { return false; } State objectState = State.getInstance(object); if (!hasPermission("type/" + objectState.getTypeId() + "/restore")) { throw new IllegalStateException(String.format( "No permission to restore [%s]!", objectState.getType().getLabel())); } try { Draft draft = getOverlaidDraft(object); State state = State.getInstance(draft != null ? draft : object); state.as(Content.ObjectModification.class).setTrash(false); publish(state); redirectOnSave(""); return true; } catch (Exception error) { getErrors().add(error); return false; } } /** * Tries to save the given {@code object} if the user has asked for it * in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the trash is tried. */ public boolean trySave(Object object) { if (!isFormPost() || param(String.class, SAVE_ACTION_PARAMETER) == null) { return false; } State state = State.getInstance(object); try { updateUsingParameters(object); state.save(); redirectOnSave("", "id", state.getId(), "copyId", null); return true; } catch (Exception error) { getErrors().add(error); return false; } } /** * Tries to apply a standard set of updates to the given {@code object} * if the user has asked for any in the current request. * * <p>This method calls the following methods in order:</p> * * <ul> * <li>{@link #tryDelete}</li> * <li>{@link #tryRestore}</li> * <li>{@link #trySave}</li> * <li>{@link #tryTrash}</li> * </ul> * * @param object Can't be {@code null}. * @return {@code true} if the trash is tried. */ public boolean tryStandardUpdate(Object object) { return tryDelete(object) || tryRestore(object) || trySave(object) || tryTrash(object); } /** * Tries to trash the given {@code object} if the user has asked for it * in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the trash is tried. */ public boolean tryTrash(Object object) { if (!isFormPost() || param(String.class, TRASH_ACTION_PARAMETER) == null) { return false; } State state = State.getInstance(object); if (!hasPermission("type/" + state.getTypeId() + "/archive")) { throw new IllegalStateException(String.format( "No permission to archive [%s]!", state.getType().getLabel())); } try { Draft draft = getOverlaidDraft(object); trash(draft != null ? draft : object); redirectOnSave(""); return true; } catch (Exception error) { getErrors().add(error); return false; } } public boolean tryMerge(Object object) { if (!isFormPost()) { return false; } String action = param(String.class, MERGE_ACTION_PARAMETER); if (ObjectUtils.isBlank(action)) { return false; } setContentFormScheduleDate(object); State state = State.getInstance(object); Draft draft = getOverlaidDraft(object); if (draft == null) { return false; } try { state.beginWrites(); updateUsingParameters(object); Edit.updateUsingWidgets(this, object); State oldState = State.getInstance(Query .fromAll() .where("_id = ?", state.getId()) .noCache() .first()); if (oldState != null) { state.as(Workflow.Data.class).getState().put("cms.workflow.currentState", oldState.as(Workflow.Data.class).getCurrentState()); } publish(object); draft.delete(); state.commitWrites(); redirectOnSave("", "id", state.getId()); return true; } catch (Exception error) { getErrors().add(error); return false; } finally { state.endWrites(); } } /** * Tries to apply a workflow action to the given {@code object} if the * user has asked for it in the current request. * * @param object Can't be {@code null}. * @return {@code true} if the application of a workflow action is tried. */ public boolean tryWorkflow(Object object) { if (!isFormPost()) { return false; } String action = param(String.class, WORKFLOW_ACTION_PARAMETER); if (ObjectUtils.isBlank(action)) { return false; } setContentFormScheduleDate(object); State state = State.getInstance(object); Draft draft = getOverlaidDraft(object); Workflow.Data workflowData = state.as(Workflow.Data.class); String oldWorkflowState = workflowData.getCurrentState(); Content.ObjectModification contentData = state.as(Content.ObjectModification.class); boolean oldContentDraft = contentData.isDraft(); try { state.beginWrites(); Workflow workflow = Workflow.findWorkflow(getSite(), state); if (workflow != null) { WorkflowTransition transition = workflow.getTransitions().get(action); if (transition != null) { if (!hasPermission("type/" + state.getTypeId() + "/" + transition.getName())) { throw new IllegalAccessException("You do not have permission to " + transition.getDisplayName() + " " + state.getType().getDisplayName()); } WorkflowLog log = new WorkflowLog(); updateUsingParameters(object); Edit.updateUsingWidgets(this, object); contentData.setDraft(false); log.getState().setId(param(UUID.class, "workflowLogId")); updateUsingParameters(log); workflowData.changeState(transition, getUser(), log); if (draft == null) { publish(object); } else { draft.as(Workflow.Data.class).changeState(transition, getUser(), log); draft.update(findOldValuesInForm(state), object); publish(draft); } state.commitWrites(); } } redirectOnWorkflow("", "id", state.getId()); return true; } catch (Exception error) { if (draft != null) { draft.as(Workflow.Data.class).revertState(oldWorkflowState); } workflowData.revertState(oldWorkflowState); contentData.setDraft(oldContentDraft); getErrors().add(error); return false; } finally { state.endWrites(); } } public List<ObjectTypeOrContentTemplate> getObjectTypeOrContentTemplates(Collection<ObjectType> types, boolean includeContentTemplates) { List<ObjectTypeOrContentTemplate> otcts = new ArrayList<>(); types.stream() .map(ObjectTypeOrContentTemplate::new) .forEach(otcts::add); if (includeContentTemplates) { ToolUser user = getUser(); if (user != null) { Site site = getSite(); Stream.of(user, user.getRole(), getCmsTool()) .filter(Objects::nonNull) .flatMap(o -> { ContentTemplateMappings mappings = o.as(ContentTemplateMappings.class); return Stream.concat( mappings.getSiteSpecificExtras().stream() .filter(m -> m.getSites().contains(site)) .flatMap(m -> m.getContentTemplates().stream()), mappings.getGlobalExtras().stream()); }) .filter(t -> types.contains(t.getTemplateType())) .distinct() .forEach(t -> otcts.add(new ObjectTypeOrContentTemplate(t))); Collections.sort(otcts); } } return otcts; } // --- AuthenticationFilter bridge --- /** @see AuthenticationFilter.Static#requireUser */ public boolean requireUser() throws IOException { return AuthenticationFilter.Static.requireUser(getServletContext(), getRequest(), getResponse()); } /** * Returns the current user accessing the tool. * * @see AuthenticationFilter.Static#getUser */ public ToolUser getUser() { return AuthenticationFilter.Static.getUser(getRequest()); } /** * Returns the current tool user setting value associated with the given * {@code key}. * * @see AuthenticationFilter.Static#getUserSetting */ public Object getUserSetting(String key) { return AuthenticationFilter.Static.getUserSetting(getRequest(), key); } /** * Puts the given setting {@code value} at the given {@code key} for * the current tool user. * * @see AuthenticationFilter.Static#putUserSetting */ public void putUserSetting(String key, Object value) { AuthenticationFilter.Static.putUserSetting(getRequest(), key, value); } /** * Returns the page setting value associated with the given {@code key}. * * @see AuthenticationFilter.Static#getPageSetting */ public Object getPageSetting(String key) { return AuthenticationFilter.Static.getPageSetting(getRequest(), key); } /** * Puts the page setting {@code value} at the given {@code key}. * * @see AuthenticationFilter.Static#putPageSetting */ public void putPageSetting(String key, Object value) { AuthenticationFilter.Static.putPageSetting(getRequest(), key, value); } /** * Returns the site that the {@linkplain #getUser current user} * is accessing. */ public Site getSite() { ToolUser user = getUser(); return user != null ? user.getCurrentSite() : null; } /** * Returns {@code true} if the {@linkplain #getUser current user} * is allowed access to the resources identified by the given * {@code permissionId}. * * @param permissionId If {@code null}, returns {@code true}. */ public boolean hasPermission(String permissionId) { ToolPermissionProvider provider = getToolPermissionProvider(); if (provider != null) { return provider.hasPermission(this, permissionId); } ToolUser user = getUser(); return user != null && (permissionId == null || user.hasPermission(permissionId)); } public boolean requirePermission(String permissionId) throws IOException { if (requireUser()) { return true; } else { if (hasPermission(permissionId)) { return false; } else { getResponse().sendError(Settings.isProduction() ? HttpServletResponse.SC_NOT_FOUND : HttpServletResponse.SC_FORBIDDEN); return true; } } } private transient boolean checkedPermissionProvider; private transient ToolPermissionProvider permissionProvider; /** * Returns the {@link ToolPermissionProvider} if configured. */ private ToolPermissionProvider getToolPermissionProvider() { if (!checkedPermissionProvider) { permissionProvider = ToolPermissionProvider.getDefault(); checkedPermissionProvider = true; } return permissionProvider; } // --- Content.Static bridge --- /** * @see Content.Static#deleteSoftly * @deprecated Use {@link #trash} instead. */ @Deprecated public Trash deleteSoftly(Object object) { return Content.Static.deleteSoftly(object, getSite(), getUser()); } private History updateLockIgnored(History history) { if (history != null && param(boolean.class, "editAnyway")) { history.setLockIgnored(true); history.save(); } return history; } private void deleteWorksInProgress(Object object) { UUID contentId = object instanceof Draft ? ((Draft) object).getObjectId() : State.getInstance(object).getId(); Query.from(WorkInProgress.class) .where("owner = ?", getUser()) .and("contentId = ?", contentId) .deleteAll(); } /** * @see Content.Static#publish(Object, Site, ToolUser) */ public History publish(Object object) { PublishModification.setBroadcast(object, true); deleteWorksInProgress(object); ToolUser user = getUser(); History history = updateLockIgnored(Content.Static.publish(object, getSite(), user)); return history; } /** * @see Content.Static#publishDifferences(Object, Map, Site, ToolUser) */ public History publishDifferences(Object object, Map<String, Map<String, Object>> differences) { PublishModification.setBroadcast(object, true); deleteWorksInProgress(object); ToolUser user = getUser(); History history = updateLockIgnored(Content.Static.publishDifferences(object, differences, getSite(), user)); return history; } /** * @see {@link com.psddev.cms.db.Content.Static#trash(Object, com.psddev.cms.db.Site, com.psddev.cms.db.ToolUser)} */ public void trash(Object object) { deleteWorksInProgress(object); Content.Static.trash(object, getSite(), getUser()); } /** * @see {@link com.psddev.cms.db.Content.Static#restore(Object, com.psddev.cms.db.Site, com.psddev.cms.db.ToolUser)} */ public void restore(Object object) { Content.Static.restore(object, getSite(), getUser()); } /** @see Content.Static#purge */ public void purge(Object object) { deleteWorksInProgress(object); Content.Static.purge(object, getSite(), getUser()); } // --- WebPageContext support --- @Deprecated private PageWriter pageWriter; @Deprecated @Override public PageWriter getWriter() throws IOException { if (pageWriter == null) { pageWriter = new PageWriter(super.getWriter()); } return pageWriter; } /** {@link ToolPageContext} utility methods. */ public static final class Static { private Static() { } private static String notTooShort(String word) { char[] letters = word.toCharArray(); StringBuilder not = new StringBuilder(); int index = 0; int length = letters.length; for (; index < 5 && index < length; ++ index) { char letter = letters[index]; if (Character.isWhitespace(letter)) { not.append('\u00a0'); } else { not.append(letter); } } if (index < length) { not.append(letters, index, length - index); } return not.toString(); } /** * Returns a label, or the given {@code defaultLabel} if one can't be * found, for the given {@code object}. */ public static String getObjectLabelOrDefault(Object object, String defaultLabel) { State state = State.getInstance(object); if (state != null) { String label = state.getLabel(); if (ObjectUtils.to(UUID.class, label) == null) { return notTooShort(label); } } return notTooShort(defaultLabel); } /** Returns a label for the given {@code object}. */ public static String getObjectLabel(Object object) { if (object instanceof ObjectType) { return Localization.currentUserText(object, "displayName"); } State state = State.getInstance(object); String label = null; if (state != null) { label = state.getLabel(); } if (ObjectUtils.isBlank(label)) { label = "Not Available"; } return notTooShort(label); } /** * Returns a label, or the given {@code defaultLabel} if one can't be * found, for the type of the given {@code object}. */ public static String getTypeLabelOrDefault(Object object, String defaultLabel) { State state = State.getInstance(object); if (state != null) { ObjectType type = state.getType(); if (type != null) { return getObjectLabel(type); } } return notTooShort(defaultLabel); } /** Returns a label for the type of the given {@code object}. */ public static String getTypeLabel(Object object) { return getTypeLabelOrDefault(object, "Unknown Type"); } } // --- Deprecated --- /** @deprecated Use {@link #ToolPageContext(ServletContext, HttpServletRequest, HttpServletResponse)} instead. */ @Deprecated public ToolPageContext( Servlet servlet, HttpServletRequest request, HttpServletResponse response) { super(servlet, request, response); } /** @deprecated Use {@link Database.Static#getDefault} instead. */ @Deprecated public Database getDatabase() { return Database.Static.getDefault(); } /** @deprecated Use {@link Query#from} instead. */ @Deprecated public <T> Query<T> queryFrom(Class<T> objectClass) { Query<T> query = Query.from(objectClass); query.setDatabase(getDatabase()); return query; } /** * Returns an HTML-escaped label, or the given {@code defaultLabel} if * one can't be found, for the given {@code object}. * * @deprecated Use {@link #getObjectLabelOrDefault} and {@link #h} instead. */ @Deprecated public String objectLabel(Object object, String defaultLabel) { return h(getObjectLabelOrDefault(object, defaultLabel)); } /** * Returns an HTML-escaped label for the given {@code object}. * * @deprecated Use {@link #getObjectLabel} and {@link #h} instead. */ @Deprecated public String objectLabel(Object object) { return h(getObjectLabel(object)); } /** * Returns an HTML-escaped label, or the given {@code defaultLabel} if * one can't be found, for the type of the given {@code object}. * * @deprecated Use {@link #getTypeLabelOrDefault} and {@link #h} instead. */ @Deprecated public String typeLabel(Object object, String defaultLabel) { return h(getTypeLabelOrDefault(object, defaultLabel)); } /** * Returns an HTML-escaped label for the type of the given * {@code object}. * * @deprecated Use {@link #getTypeLabel} and {@link #h} instead. */ @Deprecated public String typeLabel(Object object) { return h(getTypeLabel(object)); } /** @deprecated Use {@link #writeTypeSelect} instead. */ @Deprecated public void typeSelect( Iterable<ObjectType> types, ObjectType selectedType, String allLabel, Object... attributes) throws IOException { writeTypeSelect(types, selectedType, allLabel, attributes); } /** @deprecated Use {@link #writeObjectSelect} instead. */ @Deprecated public void objectSelect(ObjectField field, Object value, Object... attributes) throws IOException { writeObjectSelect(field, value, attributes); } }