package com.psddev.cms.tool.page.content.field;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TreeMap;
import java.util.UUID;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.psddev.cms.db.ImageCrop;
import com.psddev.cms.db.ImageTag;
import com.psddev.cms.db.ImageTextOverlay;
import com.psddev.cms.db.StandardImageSize;
import com.psddev.cms.db.ToolUi;
import com.psddev.cms.tool.FileContentType;
import com.psddev.cms.tool.PageServlet;
import com.psddev.cms.tool.ToolPageContext;
import com.psddev.cms.tool.file.ContentTypeValidator;
import com.psddev.cms.tool.file.MetadataAfterSave;
import com.psddev.cms.tool.file.MetadataBeforeSave;
import com.psddev.dari.db.ObjectField;
import com.psddev.dari.db.ObjectType;
import com.psddev.dari.db.ReferentialText;
import com.psddev.dari.db.State;
import com.psddev.dari.util.ClassFinder;
import com.psddev.dari.util.IoUtils;
import com.psddev.dari.util.JspUtils;
import com.psddev.dari.util.ObjectUtils;
import com.psddev.dari.util.RandomUuidStorageItemPathGenerator;
import com.psddev.dari.util.RoutingFilter;
import com.psddev.dari.util.Settings;
import com.psddev.dari.util.StorageItem;
import com.psddev.dari.util.StorageItemFilter;
import com.psddev.dari.util.StorageItemUploadPart;
import com.psddev.dari.util.StringUtils;
import com.psddev.dari.util.TypeReference;
@RoutingFilter.Path(application = "cms", value = "/content/field/file")
public class FileField extends PageServlet {
private static final Logger LOGGER = LoggerFactory.getLogger(FileField.class);
public static void processField(ToolPageContext page) throws IOException, ServletException {
HttpServletRequest request = page.getRequest();
State state = State.getInstance(request.getAttribute("object"));
ObjectField field = (ObjectField) request.getAttribute("field");
String inputName = ObjectUtils.firstNonBlank((String) request.getAttribute("inputName"), page.param(String.class, "inputName"));
String actionName = inputName + ".action";
String fileParamName = inputName + ".file";
String fileJsonParamName = fileParamName + ".json";
String urlName = inputName + ".url";
String dropboxName = inputName + ".dropbox";
String cropsName = inputName + ".crops.";
String brightnessName = inputName + ".brightness";
String contrastName = inputName + ".contrast";
String flipHName = inputName + ".flipH";
String flipVName = inputName + ".flipV";
String grayscaleName = inputName + ".grayscale";
String invertName = inputName + ".invert";
String rotateName = inputName + ".rotate";
String sepiaName = inputName + ".sepia";
String sharpenName = inputName + ".sharpen";
String blurName = inputName + ".blur";
String focusXName = inputName + ".focusX";
String focusYName = inputName + ".focusY";
String fieldName = field != null ? field.getInternalName() : page.param(String.class, "fieldName");
StorageItem fieldValue = null;
if (state != null) {
fieldValue = (StorageItem) state.getValue(fieldName);
} else if (page.isAjaxRequest()) {
// Handles requests from front end upload
UUID typeId = page.param(UUID.class, "typeId");
ObjectType type = ObjectType.getInstance(typeId);
field = type.getField(fieldName);
state = State.getInstance(type.createObject(null));
fieldValue = StorageItemFilter.getParameter(request, fileJsonParamName, getStorageSetting(Optional.of(field)));
request.setAttribute("object", state);
request.setAttribute("field", field);
}
String metadataFieldName = fieldName + ".metadata";
String cropsFieldName = fieldName + ".crops";
String action = page.param(actionName);
Map<String, Object> fieldValueMetadata = null;
boolean isFormPost = request.getAttribute("isFormPost") != null ? (Boolean) request.getAttribute("isFormPost") : false;
if (fieldValue != null && (!isFormPost || "keep".equals(action))) {
fieldValueMetadata = fieldValue.getMetadata();
}
if (fieldValueMetadata == null) {
fieldValueMetadata = new LinkedHashMap<String, Object>();
}
Map<String, Object> edits = (Map<String, Object>) fieldValueMetadata.get("cms.edits");
if (edits == null) {
edits = new HashMap<String, Object>();
fieldValueMetadata.put("cms.edits", edits);
}
double brightness = ObjectUtils.to(double.class, edits.get("brightness"));
double contrast = ObjectUtils.to(double.class, edits.get("contrast"));
boolean flipH = ObjectUtils.to(boolean.class, edits.get("flipH"));
boolean flipV = ObjectUtils.to(boolean.class, edits.get("flipV"));
boolean grayscale = ObjectUtils.to(boolean.class, edits.get("grayscale"));
boolean invert = ObjectUtils.to(boolean.class, edits.get("invert"));
int rotate = ObjectUtils.to(int.class, edits.get("rotate"));
boolean sepia = ObjectUtils.to(boolean.class, edits.get("sepia"));
int sharpen = ObjectUtils.to(int.class, edits.get("sharpen"));
List<String> blurs = new ArrayList<String>();
if (!ObjectUtils.isBlank(edits.get("blur"))) {
Object blur = edits.get("blur");
if (blur instanceof String && ObjectUtils.to(String.class, blur).matches("(\\d+x){3}\\d+")) {
blurs.add(ObjectUtils.to(String.class, blur));
} else if (blur instanceof List) {
for (Object blurItem : (List) blur) {
String blurValue = ObjectUtils.to(String.class, blurItem);
if (blurValue.matches("(\\d+x){3}\\d+")) {
blurs.add(blurValue);
}
}
}
}
Map<String, ImageCrop> crops = ImageCrop.createCrops(fieldValueMetadata.get("cms.crops"));
if (crops == null) {
// for backward compatibility
crops = ImageCrop.createCrops(state.getValue(cropsFieldName));
}
crops = new TreeMap<String, ImageCrop>(crops);
Map<String, StandardImageSize> sizes = new HashMap<String, StandardImageSize>();
for (StandardImageSize size : StandardImageSize.findAll()) {
String sizeId = size.getId().toString();
sizes.put(sizeId, size);
if (crops.get(sizeId) == null) {
crops.put(sizeId, new ImageCrop());
}
}
Map<String, Double> focusPoint = ObjectUtils.to(new TypeReference<Map<String, Double>>() {
}, fieldValueMetadata.get("cms.focus"));
if (focusPoint == null) {
focusPoint = new HashMap<String, Double>();
}
Class hotSpotClass = ObjectUtils.getClassByName(ImageTag.HOTSPOT_CLASS);
boolean projectUsingBrightSpotImage = hotSpotClass != null && !ObjectUtils.isBlank(ClassFinder.Static.findClasses(hotSpotClass));
if (isFormPost) {
StorageItem newItem = null;
brightness = page.param(double.class, brightnessName);
contrast = page.param(double.class, contrastName);
flipH = page.param(boolean.class, flipHName);
flipV = page.param(boolean.class, flipVName);
grayscale = page.param(boolean.class, grayscaleName);
invert = page.param(boolean.class, invertName);
rotate = page.param(int.class, rotateName);
sepia = page.param(boolean.class, sepiaName);
sharpen = page.param(int.class, sharpenName);
Double focusX = page.paramOrDefault(Double.class, focusXName, null);
Double focusY = page.paramOrDefault(Double.class, focusYName, null);
edits = new HashMap<String, Object>();
if (brightness != 0.0) {
edits.put("brightness", brightness);
}
if (contrast != 0.0) {
edits.put("contrast", contrast);
}
if (flipH) {
edits.put("flipH", flipH);
}
if (flipV) {
edits.put("flipV", flipV);
}
if (invert) {
edits.put("invert", invert);
}
if (rotate != 0) {
edits.put("rotate", rotate);
}
if (grayscale) {
edits.put("grayscale", grayscale);
}
if (sepia) {
edits.put("sepia", sepia);
}
if (sharpen != 0) {
edits.put("sharpen", sharpen);
}
if (!ObjectUtils.isBlank(page.params(String.class, blurName))) {
blurs = new ArrayList<String>();
for (String blur : page.params(String.class, blurName)) {
if (!blurs.contains(blur)) {
blurs.add(blur);
}
}
if (blurs.size() == 1) {
edits.put("blur", blurs.get(0));
} else {
edits.put("blur", blurs);
}
}
if ("keep".equals(action)) {
newItem = StorageItemFilter.getParameter(request, fileJsonParamName, getStorageSetting(Optional.of(field)));
} else if ("newUpload".equals(action)) {
newItem = StorageItemFilter.getParameter(request, fileParamName, getStorageSetting(Optional.of(field)));
} else if ("dropbox".equals(action)) {
Map<String, Object> fileData = (Map<String, Object>) ObjectUtils.fromJson(page.param(String.class, dropboxName));
if (fileData != null) {
File file = null;
try {
file = File.createTempFile("cms.", ".tmp");
String name = ObjectUtils.to(String.class, fileData.get("name"));
String fileContentType = ObjectUtils.getContentType(name);
long fileSize = ObjectUtils.to(long.class, fileData.get("bytes"));
try (InputStream fileInput = new URL(ObjectUtils.to(String.class, fileData.get("link"))).openStream();
FileOutputStream fileOutput = new FileOutputStream(file)) {
IoUtils.copy(fileInput, fileOutput);
}
StorageItemUploadPart part = new StorageItemUploadPart();
part.setName(name);
part.setFile(file);
part.setContentType(fileContentType);
if (name != null
&& fileContentType != null) {
new ContentTypeValidator().beforeSave(null, part);
}
if (fileSize > 0) {
newItem = StorageItem.Static.createIn(getStorageSetting(Optional.of(field)));
newItem.setPath(new RandomUuidStorageItemPathGenerator().createPath(name));
newItem.setContentType(fileContentType);
newItem.setData(new FileInputStream(file));
new MetadataBeforeSave().beforeSave(newItem, part);
newItem.save();
new MetadataAfterSave().afterSave(newItem);
}
} finally {
if (file != null && file.exists()) {
file.delete();
}
}
}
} else if ("newUrl".equals(action)) {
newItem = StorageItem.Static.createUrl(page.param(urlName));
new MetadataAfterSave().afterSave(newItem);
}
if (newItem != null) {
fieldValueMetadata.putAll(newItem.getMetadata());
}
fieldValueMetadata.put("cms.edits", edits);
// Standard sizes.
for (Iterator<Map.Entry<String, ImageCrop>> i = crops.entrySet().iterator(); i.hasNext();) {
Map.Entry<String, ImageCrop> e = i.next();
String cropId = e.getKey();
double x = page.doubleParam(cropsName + cropId + ".x");
double y = page.doubleParam(cropsName + cropId + ".y");
double width = page.doubleParam(cropsName + cropId + ".width");
double height = page.doubleParam(cropsName + cropId + ".height");
String texts = page.param(cropsName + cropId + ".texts");
String textSizes = page.param(cropsName + cropId + ".textSizes");
String textXs = page.param(cropsName + cropId + ".textXs");
String textYs = page.param(cropsName + cropId + ".textYs");
String textWidths = page.param(cropsName + cropId + ".textWidths");
if (x != 0.0 || y != 0.0 || width != 0.0 || height != 0.0 || !ObjectUtils.isBlank(texts)) {
ImageCrop crop = e.getValue();
crop.setX(x);
crop.setY(y);
crop.setWidth(width);
crop.setHeight(height);
crop.setTexts(texts);
crop.setTextSizes(textSizes);
crop.setTextXs(textXs);
crop.setTextYs(textYs);
crop.setTextWidths(textWidths);
for (Iterator<ImageTextOverlay> j = crop.getTextOverlays().iterator(); j.hasNext();) {
ImageTextOverlay textOverlay = j.next();
String text = textOverlay.getText();
if (text != null) {
StringBuilder cleaned = new StringBuilder();
for (Object item : new ReferentialText(text, true)) {
if (item instanceof String) {
cleaned.append((String) item);
}
}
text = cleaned.toString();
if (ObjectUtils.isBlank(text.replaceAll("<[^>]*>", ""))) {
j.remove();
} else {
textOverlay.setText(text);
}
}
}
} else {
i.remove();
}
}
fieldValueMetadata.put("cms.crops", crops);
// Removes legacy cropping information
if (state.getValue(cropsFieldName) != null) {
state.remove(cropsFieldName);
}
// Set focus point
if (focusX != null && focusY != null) {
focusPoint.put("x", focusX);
focusPoint.put("y", focusY);
}
fieldValueMetadata.put("cms.focus", focusPoint);
// Transfers legacy metadata over to it's new location within the StorageItem object
Map<String, Object> legacyMetadata = ObjectUtils.to(new TypeReference<Map<String, Object>>() {
}, state.getValue(metadataFieldName));
if (legacyMetadata != null && !legacyMetadata.isEmpty()) {
for (Map.Entry<String, Object> entry : legacyMetadata.entrySet()) {
if (!fieldValueMetadata.containsKey(entry.getKey())) {
fieldValueMetadata.put(entry.getKey(), entry.getValue());
}
}
state.remove(metadataFieldName);
}
if (newItem != null) {
newItem.setMetadata(fieldValueMetadata);
}
state.putValue(fieldName, newItem);
if (projectUsingBrightSpotImage) {
page.include("/WEB-INF/field/set/hotSpot.jsp");
}
return;
}
// --- Presentation ---
page.writeStart("div", "class", "inputSmall");
page.writeStart("div", "class", "fileSelector");
page.writeStart("select",
"class", "toggleable",
"data-root", ".inputContainer",
"name", page.h(actionName));
if (fieldValue != null) {
page.writeStart("option",
"data-hide", ".fileSelectorItem",
"data-show", ".fileSelectorExisting",
"value", "keep");
page.writeHtml(page.localize(FileField.class, "option.keep"));
page.writeEnd();
}
if (!field.isRequired()) {
page.writeStart("option",
"data-hide", ".fileSelectorItem",
"value", "none");
page.writeHtml(page.localize(FileField.class, "option.none"));
page.writeEnd();
}
page.writeStart("option",
"data-hide", ".fileSelectorItem",
"data-show", ".fileSelectorNewUpload",
"value", "newUpload",
fieldValue == null && field.isRequired() ? " selected" : "");
page.writeHtml(page.localize(FileField.class, "option.newUpload"));
page.writeEnd();
page.writeStart("option",
"data-hide", ".fileSelectorItem",
"data-show", ".fileSelectorNewUrl",
"value", "newUrl");
page.writeHtml(page.localize(FileField.class, "option.newUrl"));
page.writeEnd();
if (!ObjectUtils.isBlank(page.getCmsTool().getDropboxApplicationKey())) {
page.writeStart("option",
"data-hide", ".fileSelectorItem",
"data-show", ".fileSelectorDropbox",
"value", "dropbox");
page.write("Dropbox");
page.writeEnd();
}
page.writeEnd();
page.writeStart("span", "class", "fileSelectorItem fileSelectorNewUpload");
page.writeElement("input",
"type", "file",
page.getCmsTool().isEnableFrontEndUploader() ? "data-bsp-uploader" : "", "",
"name", page.h(fileParamName),
"data-input-name", inputName,
"data-type-id", state.getTypeId());
page.writeEnd();
page.writeTag("input",
"class", "fileSelectorItem fileSelectorNewUrl",
"type", "text",
"name", page.h(urlName));
if (fieldValue != null) {
page.writeTag("input",
"type", "hidden",
"name", fileJsonParamName,
"value", ObjectUtils.toJson(fieldValue));
}
if (!ObjectUtils.isBlank(page.getCmsTool().getDropboxApplicationKey())) {
page.writeStart("span", "class", "fileSelectorItem fileSelectorDropbox");
page.writeElement("input",
"class", "DropboxChooserInput",
"type", "text",
"name", page.h(dropboxName));
page.writeEnd();
}
page.writeEnd();
page.writeEnd();
if (fieldValue != null) {
page.writeStart("div",
"class", "inputLarge fileSelectorItem fileSelectorExisting filePreview");
if (field.as(ToolUi.class).getStoragePreviewProcessorApplication() != null) {
ToolUi ui = field.as(ToolUi.class);
String processorPath = ui.getStoragePreviewProcessorPath();
if (processorPath != null) {
JspUtils.include(request, page.getResponse(), page.getWriter(),
RoutingFilter.Static.getApplicationPath(ui.getStoragePreviewProcessorApplication())
+ StringUtils.ensureStart(processorPath, "/"));
}
} else {
FileContentType.writeFilePreview(page, state, fieldValue);
}
page.writeEnd();
}
if (projectUsingBrightSpotImage) {
page.include("/WEB-INF/field/set/hotSpot.jsp");
}
}
/**
* Gets storageSetting for current field,
* if non exists, get {@code StorageItem.DEFAULT_STORAGE_SETTING}
*
* @param field to check for storage setting
*/
public static String getStorageSetting(Optional<ObjectField> field) {
String storageSetting = null;
if (field.isPresent()) {
String fieldStorageSetting = field.get().as(ToolUi.class).getStorageSetting();
if (!StringUtils.isBlank(fieldStorageSetting)) {
storageSetting = Settings.get(String.class, fieldStorageSetting);
}
}
if (StringUtils.isBlank(storageSetting)) {
storageSetting = Settings.get(String.class, StorageItem.DEFAULT_STORAGE_SETTING);
}
return storageSetting;
}
@Override
protected String getPermissionId() {
return null;
}
@Override
protected void doService(ToolPageContext page) throws IOException, ServletException {
processField(page);
}
}