001/* 002 * Copyright 2011 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.skinfactory.parameters; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.nio.file.Files; 023import java.nio.file.Path; 024import java.nio.file.attribute.FileTime; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028import java.util.stream.Collectors; 029import java.util.stream.Stream; 030 031import org.apache.cocoon.xml.AttributesImpl; 032import org.apache.cocoon.xml.XMLUtils; 033import org.apache.commons.io.IOUtils; 034import org.xml.sax.ContentHandler; 035import org.xml.sax.SAXException; 036 037import org.ametys.runtime.i18n.I18nizableText; 038import org.ametys.web.skin.SkinModel; 039 040/** 041 * Implementation of {@link AbstractSkinParameter} for an image 042 */ 043public class ImageParameter extends AbstractSkinParameter 044{ 045 private String _iconGlyph; 046 private String _iconSmall; 047 private String _iconLarge; 048 private boolean _localUploadEnabled; 049 private String _imagePath; 050 051 /** 052 * Constructor 053 * @param relPath the relative path of the target image 054 * @param label the label 055 * @param description the description 056 */ 057 public ImageParameter(String relPath, I18nizableText label, I18nizableText description) 058 { 059 super(relPath.replaceAll("\\\\", "/"), label, description); 060 _imagePath = relPath.replaceAll("\\\\", "/"); 061 _localUploadEnabled = false; 062 } 063 064 /** 065 * Constructor 066 * @param relPath the relative path of the target image 067 * @param label the label 068 * @param description the description 069 * @param iconGlyph The CSS classe for icon, to use instead of small and large icon 070 * @param iconSmall The small icon 071 * @param iconLarge The large icon 072 */ 073 public ImageParameter(String relPath, I18nizableText label, I18nizableText description, String iconGlyph, String iconSmall, String iconLarge) 074 { 075 super(relPath.replaceAll("\\\\", "/"), label, description); 076 _imagePath = relPath.replaceAll("\\\\", "/"); 077 _localUploadEnabled = false; 078 _iconGlyph = iconGlyph; 079 _iconLarge = iconLarge; 080 _iconSmall = iconSmall; 081 } 082 083 @Override 084 public SkinParameterType getType() 085 { 086 return SkinParameterType.IMAGE; 087 } 088 089 /** 090 * Determines if local upload is enabled 091 * @return true if local upload is enabled 092 */ 093 public boolean isLocalUploadEnabled () 094 { 095 return _localUploadEnabled; 096 } 097 098 /** 099 * Get relative path of images 100 * @return the relative path of images 101 */ 102 public String getLibraryPath () 103 { 104 return this._imagePath; 105 } 106 107 /** 108 * Set the CSS icon 109 * @param iconGlyph the CSS icon 110 */ 111 public void setIconGlyph(String iconGlyph) 112 { 113 _iconGlyph = iconGlyph; 114 } 115 116 /** 117 * Get the CSS icon 118 * @return The CSS icon 119 */ 120 public String getIconGlyph () 121 { 122 return _iconGlyph; 123 } 124 125 /** 126 * Set the small icon relative path 127 * @param iconSmall the relative path of the small icon 128 */ 129 public void setIconSmall(String iconSmall) 130 { 131 _iconSmall = iconSmall; 132 } 133 134 /** 135 * Get the small icon 136 * @return The small icon 137 */ 138 public String getIconSmall () 139 { 140 return _iconSmall; 141 } 142 143 /** 144 * Set the large icon relative path 145 * @param iconLarge the relative path of the large icon 146 */ 147 public void setIconLarge(String iconLarge) 148 { 149 _iconLarge = iconLarge; 150 } 151 152 /** 153 * Get the large icon 154 * @return The large icon 155 */ 156 public String getIconLarge () 157 { 158 return _iconLarge; 159 } 160 161 @Override 162 public void apply(Path tempDir, Path modelDir, Object value, String lang) 163 { 164 Path targetFile = tempDir.resolve("resources/img/" + this._imagePath); 165 166 boolean uploaded = value instanceof FileValue ? ((FileValue) value).isUploaded() : false; 167 Path libraryFile = _getLibraryFile(tempDir, modelDir, uploaded); 168 169 String filePath = value instanceof FileValue ? ((FileValue) value).getPath() : (String) value; 170 Path srcFile = libraryFile.resolve(filePath); 171 172 _copyFile(srcFile, targetFile); 173 174 Path resourcesFile = tempDir.resolve("resources"); 175 try 176 { 177 // Update css file last modified date to avoid cache issue in case of background image 178 for (Path file : _listCSSFiles(resourcesFile)) 179 { 180 Files.setLastModifiedTime(file, FileTime.fromMillis(System.currentTimeMillis())); 181 } 182 } 183 catch (IOException e) 184 { 185 throw new RuntimeException("An error occurred while retrieving CSS files in " + resourcesFile.toString()); 186 } 187 } 188 189 private List<Path> _listCSSFiles(Path file) throws IOException 190 { 191 return Files.walk(file) 192 .filter(Files::isRegularFile) 193 .filter(f -> f.getFileName().toString().endsWith(".css")) 194 .collect(Collectors.toList()); 195 } 196 private Path _getLibraryFile(Path tempDir, Path modelDir, boolean uploaded) 197 { 198 if (uploaded) 199 { 200 return tempDir.resolve("model/_uploads/" + this._imagePath); 201 } 202 else 203 { 204 return modelDir.resolve("model/images/" + this._imagePath); 205 } 206 } 207 208 private void _copyFile(Path srcFile, Path targetFile) 209 { 210 if (!Files.exists(srcFile)) 211 { 212 return; 213 } 214 215 try 216 { 217 Files.createDirectories(targetFile.getParent()); 218 219 try (InputStream is = Files.newInputStream(srcFile); OutputStream os = Files.newOutputStream(targetFile)) 220 { 221 IOUtils.copy(is, os); 222 223 Files.setLastModifiedTime(targetFile, FileTime.fromMillis(System.currentTimeMillis())); 224 } 225 } 226 catch (IOException e) 227 { 228 throw new SkinParameterException ("Unable to apply image parameter '" + getId() + "'", e); 229 } 230 } 231 232 @Override 233 public void toSAX(ContentHandler contentHandler, String modelName) throws SAXException 234 { 235 AttributesImpl attrs = new AttributesImpl(); 236 attrs.addCDATAAttribute("id", getId()); 237 attrs.addCDATAAttribute("type", SkinParameterType.IMAGE.name().toLowerCase()); 238 239 XMLUtils.startElement(contentHandler, "parameter", attrs); 240 241 getLabel().toSAX(contentHandler, "label"); 242 getDescription().toSAX(contentHandler, "description"); 243 XMLUtils.createElement(contentHandler, "path", this._imagePath); 244 245 if (getIconGlyph() != null) 246 { 247 XMLUtils.createElement(contentHandler, "iconGlyph", getIconGlyph()); 248 } 249 250 if (getIconSmall() != null) 251 { 252 XMLUtils.createElement(contentHandler, "iconSmall", "/plugins/skinfactory/" + modelName + "/_thumbnail/16/16/model/images/" + this._imagePath + "/" + getIconSmall()); 253 } 254 else 255 { 256 XMLUtils.createElement(contentHandler, "iconSmall", "/plugins/skinfactory/resources/img/button/image_16.png"); 257 } 258 259 if (getIconLarge() != null) 260 { 261 XMLUtils.createElement(contentHandler, "iconLarge", "/plugins/skinfactory/" + modelName + "/_thumbnail/32/32/model/images/" + this._imagePath + "/" + getIconLarge()); 262 } 263 else 264 { 265 XMLUtils.createElement(contentHandler, "iconLarge", "/plugins/skinfactory/resources/img/button/image_32.png"); 266 } 267 268 XMLUtils.endElement(contentHandler, "parameter"); 269 } 270 271 @Override 272 public Map<String, Object> toJson(String modelName) 273 { 274 Map<String, Object> jsonObject = new HashMap<>(); 275 276 jsonObject.put("id", getId()); 277 jsonObject.put("type", SkinParameterType.IMAGE.name().toLowerCase()); 278 279 jsonObject.put("label", getLabel()); 280 jsonObject.put("description", getDescription()); 281 jsonObject.put("path", this._imagePath); 282 283 if (getIconGlyph() != null) 284 { 285 jsonObject.put("iconGlyph", getIconGlyph()); 286 } 287 288 if (getIconSmall() != null) 289 { 290 jsonObject.put("iconSmall", "/plugins/skinfactory/" + modelName + "/_thumbnail/16/16/model/images/" + this._imagePath + "/" + getIconSmall()); 291 } 292 else 293 { 294 jsonObject.put("iconSmall", "/plugins/skinfactory/resources/img/button/image_16.png"); 295 } 296 297 if (getIconLarge() != null) 298 { 299 jsonObject.put("iconLarge", "/plugins/skinfactory/" + modelName + "/_thumbnail/32/32/model/images/" + this._imagePath + "/" + getIconLarge()); 300 } 301 else 302 { 303 jsonObject.put("iconLarge", "/plugins/skinfactory/resources/img/button/image_32.png"); 304 } 305 306 return jsonObject; 307 } 308 309 310 @Override 311 public FileValue getDefaultValue(SkinModel model) 312 { 313 Path libraryFile = model.getPath().resolve("model/images/" + this._imagePath); 314 315 try (Stream<Path> s = Files.list(libraryFile)) 316 { 317 List<Path> files = s.collect(Collectors.toList()); 318 for (Path file : files) 319 { 320 if (Files.isDirectory(file)) 321 { 322 try (Stream<Path> s2 = Files.list(file)) 323 { 324 List<Path> subfiles = s2.collect(Collectors.toList()); 325 for (Path child : subfiles) 326 { 327 if (_isImage(child)) 328 { 329 String defaultPath = libraryFile.relativize(child).toString(); 330 defaultPath.replaceAll(File.separator.equals("\\") ? File.separator + File.separator : File.separator, "."); 331 332 return new FileValue(defaultPath, false); 333 } 334 } 335 } 336 337 } 338 else if (_isImage(file)) 339 { 340 String defaultPath = libraryFile.relativize(file).toString(); 341 defaultPath.replaceAll(File.separator.equals("\\") ? File.separator + File.separator : File.separator, "."); 342 343 return new FileValue(defaultPath, false); 344 } 345 } 346 } 347 catch (IOException e) 348 { 349 throw new RuntimeException("Cannot get default value for model " + model.getId(), e); 350 } 351 352 return null; 353 } 354 355 private boolean _isImage(Path file) 356 { 357 if (Files.isDirectory(file)) 358 { 359 return false; 360 } 361 362 String name = file.getFileName().toString().toLowerCase(); 363 int index = name.lastIndexOf("."); 364 String ext = name.substring(index + 1); 365 366 if (name.equals("thumbnail_16.png") || name.equals("thumbnail_32.png") || name.equals("thumbnail_48.png")) 367 { 368 return false; 369 } 370 371 return "png".equals(ext) || "gif".equals(ext) || "jpg".equals(ext) || "jpeg".equals(ext); 372 } 373 374 @Override 375 public FileValue getDefaultValue(SkinModel model, String lang) 376 { 377 return getDefaultValue(model); 378 } 379 380 /** 381 * Class representing a file value 382 * 383 */ 384 public static class FileValue 385 { 386 private String _path; 387 private boolean _uploaded; 388 389 /** 390 * Constructor 391 * @param path The relative file path 392 * @param uploaded <code>true</code> if the file was uploaded 393 */ 394 public FileValue(String path, boolean uploaded) 395 { 396 _uploaded = uploaded; 397 _path = path; 398 } 399 400 /** 401 * Determines if the file was uploaded 402 * @return <code>true</code> if the file was uploaded 403 */ 404 public boolean isUploaded () 405 { 406 return _uploaded; 407 } 408 409 /** 410 * Get the relative file path 411 * @return the relative file path 412 */ 413 public String getPath () 414 { 415 return _path; 416 } 417 } 418 419}