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();
}
}