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