/* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.riotfamily.media.riot.form; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.riotfamily.common.io.IOUtils; import org.riotfamily.common.util.TagWriter; import org.riotfamily.common.web.support.ServletUtils; import org.riotfamily.forms.AbstractElement; import org.riotfamily.forms.ContentElement; import org.riotfamily.forms.DHTMLElement; import org.riotfamily.forms.Element; import org.riotfamily.forms.ErrorUtils; import org.riotfamily.forms.TemplateUtils; import org.riotfamily.forms.resource.FormResource; import org.riotfamily.forms.resource.ResourceElement; import org.riotfamily.forms.resource.Resources; import org.riotfamily.forms.resource.ScriptResource; import org.riotfamily.forms.resource.StylesheetResource; import org.riotfamily.forms.ui.Dimension; import org.riotfamily.media.model.CroppedRiotImage; import org.riotfamily.media.model.RiotFile; import org.riotfamily.media.model.RiotImage; import org.riotfamily.media.processing.ImageCropper; import org.springframework.web.bind.ServletRequestUtils; import org.springframework.web.multipart.MultipartFile; /** * Specialized FileUpload element for image uploads. */ public class ImageUpload extends FileUpload { public enum Alpha { ALLOWED, REQUIRED, FORBIDDEN } private static final FormResource PREVIEW_RESOURCE = new ScriptResource( "riot/image-cropper.js", "Cropper", Resources.SCRIPTACULOUS_SLIDER, new StylesheetResource("style/cropper.css")); private int[] widths; private int[] heights; private int minWidth; private int maxWidth; private int minHeight; private int maxHeight; private int previewWidth = 263; private int previewHeight = 100; private String validFormats = "GIF,JPEG,PNG"; private Alpha alpha; private ImageCropper cropper; private boolean crop = true; public ImageUpload(ImageCropper cropper) { this.cropper = cropper; } @Override protected Element createPreviewElement() { return new PreviewElement(); } public void setCrop(boolean crop) { this.crop = crop; } public void setWidths(int[] widths) { this.widths = widths; if (widths != null) { int min = Integer.MAX_VALUE; int max = 0; for (int i = 0; i < widths.length; i++) { min = Math.min(min, widths[i]); max = Math.max(max, widths[i]); } setMinWidth(min); setMaxWidth(max); } } public void setWidth(int width) { setWidths(new int[] { width }); } public void setHeights(int[] heights) { this.heights = heights; if (heights != null) { int min = Integer.MAX_VALUE; int max = 0; for (int i = 0; i < heights.length; i++) { min = Math.min(min, heights[i]); max = Math.max(max, heights[i]); } setMinHeight(min); setMaxHeight(max); } } public void setHeight(int height) { setHeights(new int[] {height}); } public void setMinWidth(int minWidth) { this.minWidth = minWidth; } public void setMaxWidth(int maxWidth) { this.maxWidth = maxWidth; } public void setMinHeight(int minHeight) { this.minHeight = minHeight; } public void setMaxHeight(int maxHeight) { this.maxHeight = maxHeight; } public void setPreviewWidth(int previewWidth) { this.previewWidth = previewWidth; } public void setPreviewHeight(int previewHeight) { this.previewHeight = previewHeight; } public void setValidFormats(String validFormats) { this.validFormats = validFormats; } public void setAlpha(String alpha) { this.alpha = alpha != null ? Alpha.valueOf(alpha.toUpperCase()) : null; } @Override protected RiotFile createRiotFile(MultipartFile multipartFile, String bucket) throws IOException { RiotImage file = new RiotImage(bucket); file.setMultipartFile(multipartFile); return file; } @Override protected void validateFile(RiotFile file) { RiotImage image = (RiotImage) file; if (validFormats != null) { if (validFormats.indexOf(image.getFormat()) == -1) { ErrorUtils.reject(this, "image.invalidFormat", new Object[] { validFormats, image.getFormat() }); } } if (alpha != null) { if (alpha == Alpha.REQUIRED && !image.isAlpha()) { ErrorUtils.reject(this, "image.alphaRequired"); } else if (alpha == Alpha.FORBIDDEN && image.isAlpha()) { ErrorUtils.reject(this, "image.alphaForbidden"); } } int imageHeight = image.getHeight(); int imageWidth = image.getWidth(); if (widths != null) { boolean match = false; for (int i = 0; i < widths.length; i++) { if (imageWidth == widths[i]) { match = true; break; } } if (!match) { ErrorUtils.reject(this, "image.size.mismatch"); return; } } else if (imageWidth < minWidth || (maxWidth > 0 && imageWidth > maxWidth)) { ErrorUtils.reject(this, "image.size.mismatch"); return; } if (heights != null) { boolean match = false; for (int i = 0; i < heights.length; i++) { if (imageHeight == heights[i]) { match = true; break; } } if (!match) { ErrorUtils.reject(this, "image.size.mismatch"); } } else if (imageHeight < minHeight || (maxHeight > 0 && imageHeight > maxHeight)) { ErrorUtils.reject(this, "image.size.mismatch"); } } protected void cropImage(int width, int height, int x, int y, int scaledWidth) throws IOException { RiotImage original = (RiotImage) getPreviewFile(); setNewFile(new CroppedRiotImage(original, cropper, width, height, x, y, scaledWidth, getBucket())); } protected void undoCrop() { CroppedRiotImage croppedImage = (CroppedRiotImage) getPreviewFile(); setNewFile(croppedImage.getOriginal()); } public class PreviewElement extends AbstractElement implements ContentElement, DHTMLElement, ResourceElement { public PreviewElement() { setWrap(false); } public FormResource getResource() { return PREVIEW_RESOURCE; } @Override protected void renderInternal(PrintWriter writer) { Dimension d = getDimension(); new TagWriter(writer).start("div") .attribute("id", getId()) .attribute("style", d.getCss()) .end(); } public Dimension getDimension() { int w = crop && maxWidth > 0 ? maxWidth : previewWidth; int h = (maxHeight > 0 ? maxHeight : previewHeight) + 65; return new Dimension(w, h); } private int getIntParameter(HttpServletRequest request, String name) { return ServletRequestUtils.getIntParameter(request, name, 0); } public void handleContentRequest(HttpServletRequest request, HttpServletResponse response) throws IOException { RiotFile file = getPreviewFile(); if (file != null) { if ("crop".equals(request.getParameter("action"))) { cropImage( getIntParameter(request, "width"), getIntParameter(request, "height"), getIntParameter(request, "x"), getIntParameter(request, "y"), getIntParameter(request, "scaledWidth")); response.getWriter().print(getCroppedImageUrl()); } else if ("undo".equals(request.getParameter("action"))) { undoCrop(); } else { ServletUtils.setNoCacheHeaders(response); try { response.setHeader("Content-Type", file.getContentType()); response.setContentLength((int) file.getSize()); IOUtils.serve(file.getInputStream(), response.getOutputStream()); } catch (FileNotFoundException e) { log.error(e.getMessage()); } } } else { response.sendError(HttpServletResponse.SC_NO_CONTENT); } } public String getImageUrl() { if (getPreviewFile() != null) { return getFormContext().getContentUrl(this) + "&time=" + System.currentTimeMillis(); } return null; } public String getCropUrl() { if (isEnabled() && cropper != null && crop) { return getFormContext().getContentUrl(this) + "&action=crop"; } return null; } public String getUndoUrl() { if (cropper != null && crop) { return getFormContext().getContentUrl(this) + "&action=undo"; } return null; } public String getCroppedImageUrl() { return getFormContext().getContentUrl(this) + "&cropped=true&time=" + System.currentTimeMillis(); } public String getParentId() { return ImageUpload.this.getId(); } public String getInitScript() { return TemplateUtils.getInitScript(this); } public int getMinWidth() { return minWidth; } public int getMaxWidth() { return maxWidth; } public int getMinHeight() { return minHeight; } public int getMaxHeight() { return maxHeight; } public int[] getWidths() { return widths; } public int[] getHeights() { return heights; } public int getPreviewWidth() { return previewWidth; } public int getPreviewHeight() { return previewHeight; } } }