package com.psddev.cms.tool.search; import java.io.IOException; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import com.psddev.cms.db.Content; import com.psddev.cms.db.ImageTag; import com.psddev.cms.db.Localization; import com.psddev.cms.tool.ToolPageContext; import com.psddev.cms.tool.page.SearchResultActions; import com.psddev.dari.util.CollectionUtils; import com.psddev.dari.util.HtmlWriter; import com.psddev.dari.util.ImageEditor; import com.psddev.dari.util.JspUtils; import com.psddev.dari.util.StorageItem; import com.psddev.dari.util.StringUtils; import org.joda.time.DateTime; import com.psddev.cms.db.Directory; import com.psddev.cms.db.Renderer; import com.psddev.cms.db.Site; import com.psddev.cms.db.ToolUi; import com.psddev.cms.db.ToolUser; import com.psddev.cms.tool.CmsTool; import com.psddev.cms.tool.SearchResultField; import com.psddev.dari.db.Database; import com.psddev.dari.db.Metric; import com.psddev.dari.db.MetricInterval; import com.psddev.dari.db.ObjectField; import com.psddev.dari.db.ObjectType; import com.psddev.dari.db.Predicate; import com.psddev.dari.db.Query; import com.psddev.dari.db.Recordable; import com.psddev.dari.db.State; import com.psddev.dari.util.ClassFinder; import com.psddev.dari.util.ObjectUtils; import com.psddev.dari.util.PaginatedResult; import com.psddev.dari.util.TypeDefinition; public class ListSearchResultView extends AbstractSearchResultView { private static final String ATTRIBUTE_PREFIX = ListSearchResultView.class.getName() + "."; private static final String PREVIOUS_DATE_ATTRIBUTE = ATTRIBUTE_PREFIX + "previousDate"; private static final String MAX_SUM_ATTRIBUTE = ATTRIBUTE_PREFIX + ".maximumSum"; protected ObjectField sortField; protected boolean showSiteLabel; protected boolean showTypeLabel; protected PaginatedResult<?> result; @Override public String getIconName() { return "list-ul"; } @Override public String getDisplayName() { return Localization.currentUserText(this, "displayName"); } @Override protected void doWriteHtml() throws IOException { ObjectType selectedType = search.getSelectedType(); sortField = updateSort(); showSiteLabel = Query.from(CmsTool.class).first().isDisplaySiteInSearchResult() && Query.from(Site.class).hasMoreThan(0); if (selectedType != null) { showTypeLabel = selectedType.as(ToolUi.class).findDisplayTypes().size() != 1; if (ObjectType.getInstance(ObjectType.class).equals(selectedType)) { List<ObjectType> types = new ArrayList<ObjectType>(); Predicate predicate = search.toQuery(page.getSite()).getPredicate(); for (ObjectType t : Database.Static.getDefault().getEnvironment().getTypes()) { if (t.is(predicate)) { types.add(t); } } result = new PaginatedResult<ObjectType>(search.getOffset(), search.getLimit(), types); } } else { showTypeLabel = search.findValidTypes().size() != 1; } if (result == null) { result = search.toQuery(page.getSite()).select(search.getOffset(), search.getLimit()); } writePaginationHtml(result); writeQueryRestrictionsHtml(); writeFieldsHtml(); writeSortsHtml(); writeLimitsHtml(result); page.writeStart("div", "class", "searchResult-list"); if (result.hasPages()) { writeItemsHtml(result.getItems()); } else { writeEmptyHtml(); } page.writeEnd(); } protected void writeItemsHtml(Collection<?> items) throws IOException { writeTableHtml(items); } protected void writeImagesHtml(Iterable<?> items) throws IOException { page.writeStart("div", "class", "searchResult-images"); for (Object item : items) { page.writeStart("figure"); itemWriter.writeCheckboxHtml(page, search, item); page.writeElement("img", "src", page.getPreviewThumbnailUrl(item), "alt", (showSiteLabel ? page.getObjectLabel(State.getInstance(item).as(Site.ObjectModification.class).getOwner()) + ": " : "") + (showTypeLabel ? page.getTypeLabel(item) + ": " : "") + page.getObjectLabel(item)); itemWriter.writeBeforeHtml(page, search, item); page.writeStart("figcaption"); if (showSiteLabel) { page.writeObjectLabel(State.getInstance(item).as(Site.ObjectModification.class).getOwner()); page.writeHtml(": "); } if (showTypeLabel) { page.writeTypeLabel(item); page.writeHtml(": "); } page.writeObjectLabel(item); page.writeEnd(); itemWriter.writeAfterHtml(page, search, item); page.writeEnd(); } page.writeEnd(); } protected void writeTableHtml(Iterable<?> items) throws IOException { HttpServletRequest request = page.getRequest(); page.putOverride(Recordable.class, (HtmlWriter writer, Recordable object) -> { ToolPageContext page = (ToolPageContext) writer; page.writeObjectLabel(object); }); page.putOverride(Metric.class, (HtmlWriter writer, Metric object) -> { writer.write(Double.toString(object.getSum())); }); page.putOverride(Content.class, (HtmlWriter writer, Content content) -> { ToolPageContext page = (ToolPageContext) writer; page.writeStart("a", "href", page.objectUrl("/content/edit.jsp", content), "target", "_top"); page.writeObjectLabel(content); page.writeEnd(); }); page.putOverride(StorageItem.class, (HtmlWriter writer, StorageItem item) -> { ToolPageContext page = (ToolPageContext) writer; page.writeElement("img", "height", 100, "src", ImageEditor.Static.getDefault() != null ? new ImageTag.Builder(item).setHeight(100).toUrl() : item.getPublicUrl()); }); String selectAllUrl = page.cmsUrl("/searchResultActions", "search", ObjectUtils.toJson(search.getState().getSimpleValues())); page.writeStart("table", "class", "searchResultTable links table-striped pageThumbnails"); page.writeStart("thead"); page.writeStart("tr"); page.writeStart("th"); page.writeElement("input", "type", "checkbox", "class", "searchResult-checkAll", "value", "", "data-frame-target", search.createFrameName("SearchResultActions"), "data-frame-check", StringUtils.addQueryParameters(selectAllUrl, SearchResultActions.ACTION_PARAMETER, SearchResultActions.ACTION_ADD), "data-frame-uncheck", StringUtils.addQueryParameters(selectAllUrl, SearchResultActions.ACTION_PARAMETER, SearchResultActions.ACTION_REMOVE)); page.writeEnd(); if (sortField != null && ObjectField.DATE_TYPE.equals(sortField.getInternalType())) { page.writeStart("th", "colspan", 2); page.writeHtml(page.localize(sortField, "field." + sortField.getInternalName())); page.writeEnd(); } if (showSiteLabel) { page.writeStart("th"); page.writeHtml(page.localize(ListSearchResultView.class, "label.site")); page.writeEnd(); } if (showTypeLabel) { page.writeStart("th"); page.writeHtml(page.localize(ListSearchResultView.class, "label.type")); page.writeEnd(); } page.writeStart("th"); page.writeHtml(page.localize(ListSearchResultView.class, "label.label")); page.writeEnd(); if (sortField != null && !ObjectField.DATE_TYPE.equals(sortField.getInternalType())) { page.writeStart("th"); page.writeHtml(sortField.getDisplayName()); page.writeEnd(); } ToolUser user = page.getUser(); if (user != null) { ObjectType selectedType = search.getSelectedType(); List<String> fieldNames = user.getSearchResultFieldsByTypeId().get(selectedType != null ? selectedType.getId().toString() : ""); if (fieldNames == null) { for (Class<? extends SearchResultField> c : ClassFinder.Static.findClasses(SearchResultField.class)) { if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) { SearchResultField field = TypeDefinition.getInstance(c).newInstance(); if (field.isDefault(selectedType) && !ObjectUtils.equals(sortField, field)) { field.writeTableHeaderCellHtml(page); } } } if (selectedType != null) { for (ObjectField field : selectedType.getFields()) { if (Boolean.TRUE.equals(field.as(ToolUi.class).getDefaultSearchResult())) { writeHeaderCell(field); } } } } else { for (String fieldName : fieldNames) { Class<?> fieldNameClass = ObjectUtils.getClassByName(fieldName); if (fieldNameClass != null && SearchResultField.class.isAssignableFrom(fieldNameClass)) { @SuppressWarnings("unchecked") SearchResultField field = TypeDefinition.getInstance((Class<? extends SearchResultField>) fieldNameClass).newInstance(); if (field.isSupported(selectedType) && !ObjectUtils.equals(sortField, field)) { field.writeTableHeaderCellHtml(page); } } else { ObjectField field = null; if (selectedType != null) { field = selectedType.getField(fieldName); } if (field == null) { field = Database.Static.getDefault().getEnvironment().getField(fieldName); } if (field != null && !ObjectUtils.equals(sortField, field)) { writeHeaderCell(field); } } } } } boolean showViewers = page.getCmsTool().isEnableViewers(); if (showViewers) { page.writeStart("th"); page.writeHtml(page.localize(ListSearchResultView.class, "label.viewers")); page.writeEnd(); } page.writeEnd(); page.writeEnd(); page.writeStart("tbody"); for (Object item : items) { State itemState = State.getInstance(item); String permalink = itemState.as(Directory.ObjectModification.class).getPermalink(); Integer embedWidth = null; if (ObjectUtils.isBlank(permalink)) { ObjectType type = itemState.getType(); if (type != null) { Renderer.TypeModification rendererData = type.as(Renderer.TypeModification.class); int previewWidth = rendererData.getEmbedPreviewWidth(); if (previewWidth > 0 && !ObjectUtils.isBlank(rendererData.getEmbedPath())) { permalink = JspUtils.getAbsolutePath(page.getRequest(), "/_preview", "_embed", "true", "_cms.db.previewId", itemState.getId()); embedWidth = previewWidth; } } } page.writeStart("tr", "data-preview-url", permalink, "data-preview-embed-width", embedWidth, "class", State.getInstance(item).getId().equals(page.param(UUID.class, "id")) ? "selected" : null); page.writeStart("td"); itemWriter.writeCheckboxHtml(page, search, item); page.writeEnd(); if (sortField != null && ObjectField.DATE_TYPE.equals(sortField.getInternalType())) { DateTime dateTime = page.toUserDateTime(itemState.getByPath(sortField.getInternalName())); if (dateTime == null) { page.writeStart("td", "colspan", 2); page.writeHtml("N/A"); page.writeEnd(); } else { String date = page.formatUserDate(dateTime); page.writeStart("td", "class", "date"); if (!ObjectUtils.equals(date, request.getAttribute(PREVIOUS_DATE_ATTRIBUTE))) { request.setAttribute(PREVIOUS_DATE_ATTRIBUTE, date); page.writeHtml(date); } page.writeEnd(); page.writeStart("td", "class", "time"); page.writeHtml(page.formatUserTime(dateTime)); page.writeEnd(); } } if (showSiteLabel) { page.writeStart("td"); page.writeObjectLabel(itemState.as(Site.ObjectModification.class).getOwner()); page.writeEnd(); } if (showTypeLabel) { page.writeStart("td"); page.writeTypeLabel(item); page.writeEnd(); } page.writeStart("td"); itemWriter.writeBeforeHtml(page, search, item); page.writeObjectLabel(item); itemWriter.writeAfterHtml(page, search, item); page.writeEnd(); if (sortField != null && !ObjectField.DATE_TYPE.equals(sortField.getInternalType())) { String sortFieldName = sortField.getInternalName(); Object value = itemState.getByPath(sortFieldName); page.writeStart("td"); if (value instanceof Metric) { page.writeStart("span", "style", page.cssString("white-space", "nowrap")); Double maxSum = (Double) request.getAttribute(MAX_SUM_ATTRIBUTE); if (maxSum == null) { Object maxObject = search.toQuery(page.getSite()).sortDescending(sortFieldName).first(); maxSum = maxObject != null ? ((Metric) State.getInstance(maxObject).get(sortFieldName)).getSum() : 1.0; request.setAttribute(MAX_SUM_ATTRIBUTE, maxSum); } Metric valueMetric = (Metric) value; Map<DateTime, Double> sumEntries = valueMetric.groupSumByDate( new MetricInterval.Daily(), new DateTime().dayOfMonth().roundFloorCopy().minusDays(7), null); double sum = valueMetric.getSum(); long sumLong = (long) sum; if (sumLong == sum) { page.writeHtml(String.format("%,2d ", sumLong)); } else { page.writeHtml(String.format("%,2.2f ", sum)); } if (!sumEntries.isEmpty()) { long minMillis = Long.MAX_VALUE; long maxMillis = Long.MIN_VALUE; for (Map.Entry<DateTime, Double> sumEntry : sumEntries.entrySet()) { long sumMillis = sumEntry.getKey().getMillis(); if (sumMillis < minMillis) { minMillis = sumMillis; } if (sumMillis > maxMillis) { maxMillis = sumMillis; } } double cumulativeSum = 0.0; StringBuilder path = new StringBuilder(); double xRange = maxMillis - minMillis; int width = 35; int height = 18; for (Map.Entry<DateTime, Double> sumEntry : sumEntries.entrySet()) { cumulativeSum += sumEntry.getValue(); path.append('L'); path.append((sumEntry.getKey().getMillis() - minMillis) / xRange * width); path.append(','); path.append(height - cumulativeSum / maxSum * height); } path.setCharAt(0, 'M'); page.writeStart("svg", "xmlns", "http://www.w3.org/2000/svg", "width", width, "height", height, "style", page.cssString( "display", "inline-block", "vertical-align", "middle")); page.writeStart("path", "fill", "none", "stroke", "#444444", "d", path.toString()); page.writeEnd(); page.writeEnd(); } page.writeEnd(); } else if (value instanceof Recordable) { page.writeHtml(((Recordable) value).getState().getLabel()); } else { for (Iterator<Object> i = CollectionUtils.recursiveIterable(value).iterator(); i.hasNext();) { Object collectionValue = i.next(); page.writeObject(collectionValue); if (i.hasNext()) { page.writeHtml(", "); } } } page.writeEnd(); } if (user != null) { ObjectType selectedType = search.getSelectedType(); List<String> fieldNames = user.getSearchResultFieldsByTypeId().get(selectedType != null ? selectedType.getId().toString() : ""); if (fieldNames == null) { for (Class<? extends SearchResultField> c : ClassFinder.Static.findClasses(SearchResultField.class)) { if (!c.isInterface() && !Modifier.isAbstract(c.getModifiers())) { SearchResultField field = TypeDefinition.getInstance(c).newInstance(); if (field.isDefault(selectedType) && !ObjectUtils.equals(sortField, field)) { field.writeTableDataCellHtml(page, item); } } } if (selectedType != null) { for (ObjectField field : selectedType.getFields()) { if (Boolean.TRUE.equals(field.as(ToolUi.class).getDefaultSearchResult())) { writeDataCell(itemState, field.getInternalName()); } } } } else { for (String fieldName : fieldNames) { Class<?> fieldNameClass = ObjectUtils.getClassByName(fieldName); if (fieldNameClass != null && SearchResultField.class.isAssignableFrom(fieldNameClass)) { @SuppressWarnings("unchecked") SearchResultField field = TypeDefinition.getInstance((Class<? extends SearchResultField>) fieldNameClass).newInstance(); if (field.isSupported(itemState.getType()) && !ObjectUtils.equals(sortField, field)) { field.writeTableDataCellHtml(page, item); } } else { ObjectField field = itemState.getField(fieldName); if (field == null) { field = Database.Static.getDefault().getEnvironment().getField(fieldName); } if (field != null && !ObjectUtils.equals(sortField, field)) { writeDataCell(itemState, fieldName); } } } } } if (showViewers) { page.writeStart("td"); { page.writeStart("div", "class", "EditFieldUpdateViewers", "data-rtc-content-id", itemState.getId().toString()); { page.writeStart("div", "data-rtc-edit-field-update-viewers", ""); page.writeEnd(); } page.writeEnd(); } page.writeEnd(); } page.writeEnd(); } page.writeEnd(); page.writeEnd(); page.writeStart("div", "id", page.createId()); page.writeEnd(); page.writeStart("script", "type", "text/javascript"); page.writeRaw("$('#" + page.getId() + "').siblings('.searchResultTable').find('tr').each(function() {"); page.writeRaw(" if ($(this).find('td a').size() > 1) {"); page.writeRaw(" $(this).closest('.searchResultTable').addClass('multipleLinkedColumns');"); page.writeRaw(" return false;"); page.writeRaw(" }"); page.writeRaw(" });"); page.writeEnd(); } private void writeHeaderCell(ObjectField field) throws IOException { page.writeStart("th"); page.writeHtml(field.getDisplayName()); page.writeEnd(); } private void writeDataCell(State itemState, String fieldName) throws IOException { page.writeStart("td"); for (Iterator<Object> i = CollectionUtils.recursiveIterable(itemState.getByPath(fieldName)).iterator(); i.hasNext();) { Object value = i.next(); page.writeObject(value); if (i.hasNext()) { page.writeHtml(", "); } } page.writeEnd(); } }