package com.psddev.cms.tool.page; import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; 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.Iterator; import java.util.List; import java.util.Map; import javax.servlet.ServletException; import com.psddev.cms.tool.CmsTool; import com.psddev.dari.db.Application; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.format.DateTimeFormat; import com.psddev.cms.db.Content; import com.psddev.cms.db.ContentLock; import com.psddev.cms.db.Directory; import com.psddev.cms.db.Draft; import com.psddev.cms.db.History; import com.psddev.cms.db.Renderer; import com.psddev.cms.db.Site; import com.psddev.cms.db.ToolUser; import com.psddev.cms.tool.PageServlet; import com.psddev.cms.tool.ToolPageContext; import com.psddev.dari.db.ObjectField; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.State; import com.psddev.dari.util.ClassFinder; import com.psddev.dari.util.DebugFilter; import com.psddev.dari.util.JspUtils; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.RoutingFilter; import com.psddev.dari.util.StringUtils; @RoutingFilter.Path(application = "cms", value = "contentTools") @SuppressWarnings("serial") public class ContentTools extends PageServlet { @Override protected String getPermissionId() { return null; } @Override protected void doService(ToolPageContext page) throws IOException, ServletException { ToolUser user = page.getUser(); Collection<String> includeFields = Arrays.asList("returnToDashboardOnSave"); Object object = page.findOrReserve(); State state = State.getInstance(object); ContentLock contentLock = null; if (object != null) { contentLock = ContentLock.Static.lock(object, null, user); } if (page.isFormPost()) { if (page.param(String.class, "action-compare") != null) { Draft draft = page.getOverlaidDraft(object); if (draft != null) { user.setCompareId(draft.getId()); user.save(); } else { History history = page.getOverlaidHistory(object); if (history != null) { user.setCompareId(history.getId()); user.save(); } else if (state != null) { user.setCompareId(state.getId()); user.save(); } } } else if (page.param(String.class, "action-edits") != null) { if (state != null) { Date newPublishDate = page.param(Date.class, "publishDate"); if (newPublishDate != null) { Content.ObjectModification contentData = state.as(Content.ObjectModification.class); DateTimeZone timeZone = page.getUserDateTimeZone(); newPublishDate = new Date(DateTimeFormat .forPattern("yyyy-MM-dd HH:mm:ss") .withZone(timeZone) .parseMillis(new DateTime(newPublishDate).toString("yyyy-MM-dd HH:mm:ss"))); contentData.setPublishUser(page.getUser()); contentData.setPublishDate(newPublishDate); state.save(); } } } else if (page.param(String.class, "action-unlock") != null) { contentLock.delete(); page.writeStart("script", "type", "text/javascript"); page.writeRaw("window.location.reload();"); page.writeEnd(); } else if (page.param(String.class, "action-settings") != null) { try { page.include("/WEB-INF/objectPost.jsp", "object", user, "includeFields", includeFields); user.save(); } catch (Exception error) { page.getErrors().add(error); } } } String returnUrl = page.param(String.class, "returnUrl"); page.writeHeader(); page.writeStart("style", "type", "text/css"); page.writeCss(".cms-contentTools th", "width", "25%;"); page.writeEnd(); page.writeStart("div", "class", "widget cms-contentTools"); page.writeStart("h1", "class", "icon icon-wrench"); page.writeHtml(page.localize(ContentTools.class, "title")); page.writeEnd(); page.writeStart("div", "class", "tabbed"); page.writeStart("div", "class", "fixedScrollable", "data-tab", page.localize(ContentTools.class, "label.forEditors")); if (object != null) { Content.ObjectModification contentData = state.as(Content.ObjectModification.class); Date publishDate = contentData.getPublishDate(); ToolUser publishUser = contentData.getPublishUser(); Date updateDate = contentData.getUpdateDate(); ToolUser updateUser = contentData.getUpdateUser(); page.writeStart("table", "class", "table-striped"); page.writeStart("tbody"); if (publishDate != null || publishUser != null) { page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ContentTools.class, "label.published")); page.writeEnd(); page.writeStart("td"); if (publishDate != null) { page.writeHtml(page.formatUserDateTime(publishDate)); } page.writeEnd(); page.writeStart("td"); if (publishUser != null) { page.writeObjectLabel(publishUser); } page.writeEnd(); page.writeEnd(); } if (updateDate != null || updateUser != null) { page.writeStart("tr"); page.writeStart("th"); page.writeHtml(page.localize(ContentTools.class, "label.lastUpdated")); page.writeEnd(); page.writeStart("td"); if (updateDate != null) { page.writeHtml(page.formatUserDateTime(updateDate)); } page.writeEnd(); page.writeStart("td"); if (updateUser != null) { page.writeObjectLabel(updateUser); } page.writeEnd(); page.writeEnd(); } page.writeEnd(); page.writeEnd(); page.writeStart("h2"); page.writeHtml("Compare"); page.writeEnd(); page.writeStart("form", "method", "post", "action", page.url("")); Object compareObject = user.createCompareObject(); if (compareObject != null) { page.writeStart("div", "class", "message message-info"); page.writeStart("a", "target", "_top", "href", page.cmsUrl("/content/edit.jsp?", "id", state.getId(), "compare", true)); // TODO: LOCALIZE page.writeHtml("Compare With "); page.writeTypeObjectLabel(compareObject); page.writeEnd(); page.writeEnd(); } page.writeStart("div", "class", "actions"); page.writeStart("button", "name", "action-compare", "value", true); page.writeHtml(page.localize(ContentTools.class, "action.compare")); page.writeEnd(); page.writeEnd(); page.writeEnd(); page.writeStart("h2"); page.writeHtml(page.localize(ContentTools.class, "subtitle.advancedEdits")); page.writeEnd(); if (page.isFormPost() && page.param(String.class, "action-edits") != null) { if (page.getErrors().isEmpty()) { page.writeStart("div", "class", "message message-success"); page.writeHtml(page.localize(ContentTools.class, "message.editsSaved")); page.writeEnd(); } else { page.include("/WEB-INF/errors.jsp"); } } page.writeStart("form", "method", "post", "action", page.url("")); page.writeStart("div", "class", "inputContainer"); page.writeStart("div", "class", "inputLabel"); page.writeStart("label", "for", page.createId()); page.writeHtml(page.localize(ContentTools.class, "label.newPublishDate")); page.writeEnd(); page.writeEnd(); page.writeStart("div", "class", "inputSmall"); page.writeElement("input", "type", "text", "class", "date", "name", "publishDate", "value", publishDate != null ? publishDate.getTime() : null); page.writeEnd(); page.writeEnd(); page.writeStart("div", "class", "actions"); page.writeStart("button", "class", "icon icon-action-save", "name", "action-edits", "value", true); page.writeHtml(page.localize(ContentTools.class, "action.save")); page.writeEnd(); page.writeEnd(); page.writeEnd(); if (page.hasPermission("ui/contentLock") && !user.equals(contentLock.getOwner())) { page.writeStart("h2"); page.writeHtml(page.localize(ContentTools.class, "subtitle.contentLock")); page.writeEnd(); page.writeStart("div", "class", "message message-warning"); page.writeStart("p"); //TODO: LOCALIZE page.writeHtml("Locked by "); page.writeObjectLabel(contentLock.getOwner()); page.writeHtml(" since "); page.writeHtml(page.formatUserDateTime(contentLock.getCreateDate())); page.writeHtml("."); page.writeEnd(); page.writeEnd(); page.writeStart("form", "method", "post", "action", page.url("")); page.writeStart("div", "class", "actions"); page.writeStart("button", "class", "icon icon-unlock", "name", "action-unlock", "value", true); page.writeHtml(page.localize(ContentTools.class, "action.unlock")); page.writeEnd(); page.writeEnd(); page.writeEnd(); } } page.writeStart("h2"); page.writeHtml(page.localize(ContentTools.class, "subtitle.settings")); page.writeEnd(); if (page.isFormPost() && page.param(String.class, "action-settings") != null) { if (page.getErrors().isEmpty()) { page.writeStart("div", "class", "message message-success"); page.writeHtml(page.localize(ContentTools.class, "message.settingsSaved")); page.writeEnd(); } else { page.include("/WEB-INF/errors.jsp"); } } page.writeStart("form", "method", "post", "action", page.url(""), "style", "margin-bottom:0;"); page.include("/WEB-INF/objectForm.jsp", "object", user, "includeFields", includeFields); page.writeStart("div", "class", "actions"); page.writeStart("button", "class", "icon icon-action-save", "name", "action-settings", "value", true); page.writeHtml(page.localize(ContentTools.class, "action.save")); page.writeEnd(); page.writeEnd(); page.writeEnd(); page.writeEnd(); page.writeStart("div", "class", "fixedScrollable", "data-tab", page.localize(ContentTools.class, "label.forDevelopers")); page.writeStart("ul"); if (object != null) { page.writeStart("li"); page.writeStart("a", "target", "_blank", "href", page.objectUrl("/contentRaw", ObjectUtils.firstNonNull(page.getOverlaidDraft(object), page.getOverlaidHistory(object), object))); page.writeHtml(page.localize(ContentTools.class, "action.viewRaw")); page.writeEnd(); page.writeEnd(); } if (!ObjectUtils.isBlank(returnUrl)) { page.writeStart("li"); if (ObjectUtils.to(boolean.class, StringUtils.getQueryParameterValue(returnUrl, "deprecated"))) { page.writeStart("a", "target", "_top", "href", StringUtils.addQueryParameters(returnUrl, "deprecated", null)); page.writeHtml(page.localize(ContentTools.class, "action.hideDeprecated")); page.writeEnd(); } else { page.writeStart("a", "target", "_top", "href", StringUtils.addQueryParameters(returnUrl, "deprecated", true)); page.writeHtml(page.localize(ContentTools.class, "action.showDeprecated")); page.writeEnd(); } page.writeEnd(); } page.writeEnd(); if (object != null) { ObjectType type = state.getType(); page.writeStart("table", "class", "table-striped"); page.writeStart("tbody"); if (type != null) { Class<?> objectClass = type.getObjectClass(); if (objectClass != null) { page.writeStart("tr"); page.writeStart("th"); page.writeStart("label", "for", page.createId()); page.writeHtml(page.localize(ContentTools.class, "label.class")); page.writeEnd(); page.writeEnd(); page.writeStart("td"); page.writeJavaClassLink(objectClass); page.writeEnd(); page.writeEnd(); } } page.writeStart("tr"); page.writeStart("th"); page.writeStart("label", "for", page.createId()); page.writeHtml("ID"); page.writeEnd(); page.writeEnd(); page.writeStart("td"); page.writeElement("input", "type", "text", "id", page.getId(), "class", "code", "value", state.getId(), "readonly", "readonly", "style", "width:100%;", "onclick", "this.select();"); page.writeEnd(); page.writeEnd(); page.writeStart("tr"); page.writeStart("th"); page.writeStart("label", "for", page.createId()); page.writeHtml("URL"); page.writeEnd(); page.writeEnd(); page.writeStart("td"); page.writeElement("input", "type", "text", "id", page.getId(), "value", JspUtils.getHostUrl(page.getRequest()) + page.cmsUrl("/content/edit.jsp", "id", state.getId()), "readonly", "readonly", "style", "width:100%;", "onclick", "this.select();"); page.writeEnd(); page.writeEnd(); page.writeEnd(); page.writeEnd(); } if (object != null) { ObjectType type = state.getType(); if (type != null) { if (page.isEmbeddable(object)) { Site site = page.getSite(); String permalink; if (site != null) { permalink = state.as(Directory.ObjectModification.class).getSitePermalink(site); } else { permalink = state.as(Directory.ObjectModification.class).getSitePermalink(null); if (!ObjectUtils.isBlank(permalink)) { String siteUrl = Application.Static.getInstance(CmsTool.class).getDefaultSiteUrl(); if (!ObjectUtils.isBlank(siteUrl)) { permalink = StringUtils.removeEnd(siteUrl, "/") + permalink; } } } if (!ObjectUtils.isBlank(permalink)) { StringBuilder embedCode = new StringBuilder(); embedCode.append("<script type=\"text/javascript\" src=\""); embedCode.append(StringUtils.addQueryParameters( permalink, "_embed", true, "_format", "js")); embedCode.append("\"></script>"); page.writeHtml("Embed Code:"); page.writeElement("br"); page.writeStart("textarea", "class", "code", "data-expandable-class", "code", "readonly", "readonly", "onclick", "this.select();"); page.writeHtml(embedCode); page.writeEnd(); } } String defaultPath = type.as(Renderer.TypeModification.class).getPath(); Map<String, String> paths = type.as(Renderer.TypeModification.class).getPaths(); if (!ObjectUtils.isBlank(defaultPath) || !ObjectUtils.isBlank(paths)) { page.writeStart("h2"); page.writeHtml(page.localize(ContentTools.class, "subtitle.renderers")); page.writeEnd(); page.writeStart("table", "class", "table-striped"); page.writeStart("tbody"); if (!ObjectUtils.isBlank(defaultPath)) { page.writeStart("tr"); page.writeStart("th"); page.writeStart("code"); page.writeHtml(page.localize(ContentTools.class, "label.default")); page.writeEnd(); page.writeEnd(); page.writeStart("td"); page.writeStart("code"); page.writeStart("a", "target", "_blank", "href", DebugFilter.Static.getServletPath(page.getRequest(), "code", "action", "edit", "type", "JSP", "servletPath", defaultPath)); page.writeHtml(defaultPath); page.writeEnd(); page.writeEnd(); page.writeEnd(); page.writeEnd(); } for (Map.Entry<String, String> entry : paths.entrySet()) { page.writeStart("tr"); page.writeStart("th"); page.writeStart("code"); page.writeHtml(entry.getKey()); page.writeEnd(); page.writeEnd(); page.writeStart("td"); page.writeStart("code"); page.writeStart("a", "target", "_blank", "href", DebugFilter.Static.getServletPath(page.getRequest(), "code", "action", "edit", "type", "JSP", "servletPath", entry.getValue())); page.writeHtml(entry.getValue()); page.writeEnd(); page.writeEnd(); page.writeEnd(); page.writeEnd(); } page.writeEnd(); page.writeEnd(); } Class<?> objectClass = type.getObjectClass(); if (objectClass != null) { Static.writeJavaAnnotationDescriptions(page, objectClass); } } } page.writeEnd(); page.writeEnd(); page.writeEnd(); page.writeFooter(); } /** * {@link ContentTools} utility methods. */ public static class Static { public static void writeJavaAnnotationDescriptions( ToolPageContext page, AnnotatedElement annotated) throws IOException { List<Annotation> presentAnnotations = Arrays.asList(annotated.getAnnotations()); List<Class<? extends Annotation>> possibleAnnotationClasses = new ArrayList<Class<? extends Annotation>>(); for (Class<? extends Annotation> ac : ClassFinder.Static.findClasses(Annotation.class)) { if (!ac.isAnnotationPresent(Deprecated.class) && ac.isAnnotationPresent(annotated instanceof Field ? ObjectField.AnnotationProcessorClass.class : ObjectType.AnnotationProcessorClass.class)) { possibleAnnotationClasses.add(ac); continue; } } Collections.sort(possibleAnnotationClasses, new Comparator<Class<? extends Annotation>>() { @Override public int compare(Class<? extends Annotation> x, Class<? extends Annotation> y) { return x.getName().compareTo(y.getName()); } }); if (!presentAnnotations.isEmpty()) { page.writeStart("h2"); page.writeHtml(page.localize(ContentTools.class, "subtitle.presentAnnotations")); page.writeEnd(); page.writeStart("ul"); for (Annotation a : presentAnnotations) { page.writeStart("li"); writeJavaAnnotationDescription(page, a); page.writeEnd(); } page.writeEnd(); } page.writeStart("h2"); page.writeHtml(page.localize(ContentTools.class, "subtitle.possibleAnnotations")); page.writeEnd(); page.writeStart("ul"); for (Class<? extends Annotation> ac : possibleAnnotationClasses) { page.writeStart("li"); page.writeJavaClassLink(ac); page.writeEnd(); } page.writeEnd(); } private static void writeJavaAnnotationDescription( ToolPageContext page, Annotation annotation) throws IOException { Class<? extends Annotation> aClass = annotation.annotationType(); page.writeJavaClassLink(aClass); List<Method> aMethods = new ArrayList<Method>(Arrays.asList(aClass.getMethods())); for (Iterator<Method> i = aMethods.iterator(); i.hasNext();) { if (!i.next().getDeclaringClass().equals(aClass)) { i.remove(); } } if (!aMethods.isEmpty()) { page.writeStart("table", "class", "table-striped"); page.writeStart("tbody"); for (Method m : aMethods) { if (m.getDeclaringClass().equals(aClass)) { page.writeStart("tr"); page.writeStart("th"); page.writeStart("code"); page.writeHtml(m.getName()); page.writeEnd(); page.writeEnd(); page.writeStart("td"); try { writeJavaAnnotationValue(page, m.invoke(annotation)); } catch (IllegalAccessException error) { // Ignore reflection look-up errors. } catch (InvocationTargetException error) { // Ignore reflection look-up errors. } page.writeEnd(); page.writeEnd(); } } page.writeEnd(); page.writeEnd(); } } public static void writeJavaAnnotationValue( ToolPageContext page, Object value) throws IOException { if (value instanceof String) { page.writeStart("code"); page.writeHtml('"'); page.writeHtml(value); page.writeHtml('"'); page.writeEnd(); } else if (value instanceof Class) { page.writeJavaClassLink((Class<?>) value); } else if (value instanceof Annotation) { writeJavaAnnotationDescription(page, (Annotation) value); } else if (value.getClass().isArray()) { int length = Array.getLength(value); if (length > 0) { page.writeStart("ol"); for (int i = 0; i < length; ++ i) { page.writeStart("li"); writeJavaAnnotationValue(page, Array.get(value, i)); page.writeEnd(); } page.writeEnd(); } } else { page.writeStart("code"); page.writeHtml(value); page.writeEnd(); } } } }