001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ui;
029
030import com.alkacon.simapi.IdentIcon;
031import com.alkacon.simapi.Simapi;
032
033import org.opencms.cache.CmsVfsNameBasedDiskCache;
034import org.opencms.file.CmsFile;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProject;
037import org.opencms.file.CmsProperty;
038import org.opencms.file.CmsPropertyDefinition;
039import org.opencms.file.CmsResource;
040import org.opencms.file.CmsResourceFilter;
041import org.opencms.file.CmsUser;
042import org.opencms.file.types.CmsResourceTypeImage;
043import org.opencms.loader.CmsImageScaler;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.OpenCms;
047import org.opencms.security.CmsRole;
048import org.opencms.util.CmsStringUtil;
049import org.opencms.workplace.CmsWorkplace;
050
051import java.awt.Color;
052import java.awt.image.BufferedImage;
053import java.io.IOException;
054import java.util.Collections;
055import java.util.List;
056
057import org.apache.commons.logging.Log;
058
059/**
060 * Generates user ident-icons.<p>
061 */
062public class CmsUserIconHelper {
063
064    /**
065     * Available icon sizes.<p>
066     */
067    private enum IconSize {
068
069        /**The big icon size. */
070        Big(96, BIG_ICON_SUFFIX),
071        /**The small icon size.*/
072        Small(32, SMALL_ICON_SUFFIX),
073        /**The tiny icon size. */
074        Tiny(23, TINY_ICON_SUFFIX);
075
076        /**Size in pixel.*/
077        private int m_size;
078
079        /**Suffix to append to filename.*/
080        private String m_suffix;
081
082        /**
083         * constructor.<p>
084         *
085         * @param size in pixel
086         * @param suffix for filename
087         */
088        private IconSize(int size, String suffix) {
089
090            m_size = size;
091            m_suffix = suffix;
092        }
093
094        /**
095         * Gets size in pixel.<p>
096         *
097         * @return icon size in pixel
098         */
099        public int getSize() {
100
101            return m_size;
102        }
103
104        /**
105         * Gets the suffix.<p>
106         *
107         * @return string
108         */
109        public String getSuffix() {
110
111            return m_suffix;
112        }
113
114    }
115
116    /** The color reserved for admin users. */
117    public static final Color ADMIN_COLOR = new Color(0x00, 0x30, 0x82);
118
119    /** The big icon suffix. */
120    public static final String BIG_ICON_SUFFIX = "_big_icon.png";
121
122    /** The target folder name. */
123    public static final String ICON_FOLDER = "user_icons";
124
125    /** The small icon suffix. */
126    public static final String SMALL_ICON_SUFFIX = "_small_icon.png";
127
128    /** The temp folder name. */
129    public static final String TEMP_FOLDER = "temp/";
130
131    /** The tiny icon suffix. */
132    public static final String TINY_ICON_SUFFIX = "_tiny_icon.png";
133
134    /** The user image folder. */
135    public static final String USER_IMAGE_FOLDER = "/system/userimages/";
136
137    /** The user image additional info key. */
138    public static final String USER_IMAGE_INFO = "USER_IMAGE";
139
140    /** Logger instance for this class. */
141    private static final Log LOG = CmsLog.getLog(CmsUserIconHelper.class);
142
143    /** The admin cms context. */
144    private CmsObject m_adminCms;
145
146    /** The image cache. */
147    private CmsVfsNameBasedDiskCache m_cache;
148
149    /** The icon renderer. */
150    private IdentIcon m_renderer;
151
152    /**
153     * Constructor.<p>
154     *
155     * @param adminCms the admin cms context
156     */
157    public CmsUserIconHelper(CmsObject adminCms) {
158
159        m_adminCms = adminCms;
160        m_renderer = new IdentIcon();
161        m_renderer.setReservedColor(ADMIN_COLOR);
162
163        m_cache = new CmsVfsNameBasedDiskCache(
164            OpenCms.getSystemInfo().getWebApplicationRfsPath() + "/" + CmsWorkplace.RFS_PATH_RESOURCES,
165            ICON_FOLDER);
166    }
167
168    /**
169     * Checks whether the given user has an individual user image.<p>
170     *
171     * @param user the user
172     *
173     * @return <code>true</code> if the given user has an individual user image
174     */
175    public static boolean hasUserImage(CmsUser user) {
176
177        return CmsStringUtil.isNotEmptyOrWhitespaceOnly((String)user.getAdditionalInfo(USER_IMAGE_INFO));
178    }
179
180    /**
181     * Deletes the user image of the current user.<p>
182     *
183     * @param cms the cms context
184     */
185    public void deleteUserImage(CmsObject cms) {
186
187        CmsUser user = cms.getRequestContext().getCurrentUser();
188        String userIconPath = (String)user.getAdditionalInfo(USER_IMAGE_INFO);
189        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(userIconPath)) {
190            try {
191                CmsObject adminCms = OpenCms.initCmsObject(m_adminCms);
192                if (adminCms.existsResource(userIconPath)) {
193
194                    CmsProject tempProject = adminCms.createTempfileProject();
195                    adminCms.getRequestContext().setCurrentProject(tempProject);
196                    adminCms.lockResource(userIconPath);
197                    adminCms.deleteResource(userIconPath, CmsResource.DELETE_REMOVE_SIBLINGS);
198                }
199                user.deleteAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO);
200                adminCms.writeUser(user);
201
202                try {
203                    OpenCms.getPublishManager().publishProject(adminCms);
204                } catch (Exception e) {
205                    LOG.error("Error publishing user image resources.", e);
206                }
207            } catch (CmsException e) {
208                LOG.error("Error deleting previous user image.", e);
209            }
210        }
211    }
212
213    /**
214     * Returns the big ident-icon path for the given user.<p>
215     *
216     * @param cms the cms context
217     * @param user the user
218     *
219     * @return the icon path
220     */
221    public String getBigIconPath(CmsObject cms, CmsUser user) {
222
223        return getIconPath(cms, user, IconSize.Big);
224    }
225
226    /**
227     * Returns the small ident-icon path for the given user.<p>
228     *
229     * @param cms the cms context
230     * @param user the user
231     *
232     * @return the icon path
233     */
234    public String getSmallIconPath(CmsObject cms, CmsUser user) {
235
236        return getIconPath(cms, user, IconSize.Small);
237    }
238
239    /**
240     * Returns the tiny ident-icon path for the given user.<p>
241     *
242     * @param cms the cms context
243     * @param user the user
244     *
245     * @return the icon path
246     */
247    public String getTinyIconPath(CmsObject cms, CmsUser user) {
248
249        return getIconPath(cms, user, IconSize.Tiny);
250    }
251
252    /**
253     * Handles a user image upload.
254     * The uploaded file will be scaled and save as a new file beneath /system/userimages/, the original file will be deleted.<p>
255     *
256     * @param cms the cms context
257     * @param user the user
258     * @param uploadedFile the uploaded file
259     *
260     * @return <code>true</code> in case the image was set successfully
261     */
262    public boolean handleImageUpload(CmsObject cms, CmsUser user, String uploadedFile) {
263
264        boolean result = false;
265        try {
266            setUserImage(cms, user, uploadedFile);
267            result = true;
268        } catch (CmsException e) {
269            LOG.error("Error setting user image.", e);
270        }
271        try {
272            cms.lockResource(uploadedFile);
273            cms.deleteResource(uploadedFile, CmsResource.DELETE_REMOVE_SIBLINGS);
274        } catch (CmsException e) {
275            LOG.error("Error deleting user image temp file.", e);
276        }
277        return result;
278    }
279
280    /**
281     * Handles a user image upload.
282     * The uploaded file will be scaled and save as a new file beneath /system/userimages/, the original file will be deleted.<p>
283     *
284     * @param cms the cms context
285     * @param uploadedFiles the uploaded file paths
286     *
287     * @return <code>true</code> in case the image was set successfully
288     */
289    public boolean handleImageUpload(CmsObject cms, List<String> uploadedFiles) {
290
291        boolean result = false;
292        if (uploadedFiles.size() == 1) {
293            String tempFile = CmsStringUtil.joinPaths(USER_IMAGE_FOLDER, TEMP_FOLDER, uploadedFiles.get(0));
294            result = handleImageUpload(cms, cms.getRequestContext().getCurrentUser(), tempFile);
295        }
296        return result;
297    }
298
299    /**
300     * Sets the user image for the given user.<p>
301     *
302     * @param cms the cms context
303     * @param user the user
304     * @param rootPath the image root path
305     *
306     * @throws CmsException in case anything goes wrong
307     */
308    public void setUserImage(CmsObject cms, CmsUser user, String rootPath) throws CmsException {
309
310        CmsFile tempFile = cms.readFile(cms.getRequestContext().removeSiteRoot(rootPath));
311        CmsImageScaler scaler = new CmsImageScaler(tempFile.getContents(), tempFile.getRootPath());
312
313        if (scaler.isValid()) {
314            scaler.setType(2);
315            scaler.setHeight(192);
316            scaler.setWidth(192);
317            byte[] content = scaler.scaleImage(tempFile);
318            String previousImage = (String)user.getAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO);
319            String newFileName = USER_IMAGE_FOLDER
320                + user.getId().toString()
321                + "_"
322                + System.currentTimeMillis()
323                + getSuffix(tempFile.getName());
324            CmsObject adminCms = OpenCms.initCmsObject(m_adminCms);
325            CmsProject tempProject = adminCms.createTempfileProject();
326            adminCms.getRequestContext().setCurrentProject(tempProject);
327            if (adminCms.existsResource(newFileName)) {
328                // a user image of the given name already exists, just write the new content
329                CmsFile imageFile = adminCms.readFile(newFileName);
330                adminCms.lockResource(imageFile);
331                imageFile.setContents(content);
332                adminCms.writeFile(imageFile);
333                adminCms.writePropertyObject(
334                    newFileName,
335                    new CmsProperty(CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, null, "w:192,h:192"));
336
337            } else {
338                // create a new user image file
339                adminCms.createResource(
340                    newFileName,
341                    OpenCms.getResourceManager().getResourceType(CmsResourceTypeImage.getStaticTypeName()),
342                    content,
343                    Collections.singletonList(
344                        new CmsProperty(CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, null, "w:192,h:192")));
345            }
346            if (newFileName.equals(previousImage)) {
347                previousImage = null;
348            }
349
350            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(previousImage)) {
351                previousImage = (String)user.getAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO);
352                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(previousImage)
353                    && cms.existsResource(newFileName, CmsResourceFilter.ONLY_VISIBLE_NO_DELETED)) {
354                    try {
355                        adminCms.lockResource(previousImage);
356                        adminCms.deleteResource(previousImage, CmsResource.DELETE_REMOVE_SIBLINGS);
357                    } catch (CmsException e) {
358                        LOG.error("Error deleting previous user image.", e);
359                    }
360                }
361            }
362            user.setAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO, newFileName);
363            adminCms.writeUser(user);
364
365            try {
366                OpenCms.getPublishManager().publishProject(adminCms);
367            } catch (Exception e) {
368                LOG.error("Error publishing user image resources.", e);
369            }
370
371        }
372    }
373
374    /**
375     * Returns the ident-icon path for the given user.<p>
376     *
377     * @param cms the cms context
378     * @param user the user
379     * @param size IconSize to get icon for
380     *
381     * @return the icon path
382     */
383    private String getIconPath(CmsObject cms, CmsUser user, IconSize size) {
384
385        String userIconPath = (String)user.getAdditionalInfo(USER_IMAGE_INFO);
386        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(userIconPath)) {
387            userIconPath += size.equals(IconSize.Big)
388            ? ""
389            : "?__scale=h:" + size.getSize() + ",w:" + size.getSize() + ",t:2";
390            return OpenCms.getLinkManager().substituteLinkForRootPath(cms, userIconPath);
391        }
392
393        boolean isAdmin = OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ADMINISTRATOR);
394        String name = user.getName() + Boolean.toString(isAdmin);
395        String rfsName = toRfsName(name, size);
396        String path = toPath(name, size);
397        if (!m_cache.hasCacheContent(rfsName)) {
398
399            BufferedImage icon = m_renderer.render(name, isAdmin, size.getSize());
400            try {
401                m_cache.saveCacheFile(rfsName, getImageBytes(icon));
402            } catch (Exception e) {
403                LOG.error(e.getLocalizedMessage(), e);
404            }
405        }
406        return path;
407    }
408
409    /**
410     * Returns the image data.
411     * @param image the image
412     *
413     * @return the data
414     *
415     * @throws IOException in case writing to the output stream failed
416     */
417    private byte[] getImageBytes(BufferedImage image) throws IOException {
418
419        return Simapi.getImageBytes(image, Simapi.TYPE_PNG);
420    }
421
422    /**
423     * Returns the file suffix.<p>
424     *
425     * @param fileName the file name
426     *
427     * @return the suffix
428     */
429    private String getSuffix(String fileName) {
430
431        int index = fileName.lastIndexOf(".");
432        if (index > 0) {
433            return fileName.substring(index);
434        } else {
435            return fileName;
436        }
437    }
438
439    /**
440     * Transforms user name and icon size into the image path.
441     *
442     * @param name the user name
443     * @param size IconSize to get icon for
444     *
445     * @return the path
446     */
447    private String toPath(String name, IconSize size) {
448
449        return CmsStringUtil.joinPaths(CmsWorkplace.getSkinUri(), ICON_FOLDER, "" + name.hashCode()) + size.getSuffix();
450    }
451
452    /**
453     * Transforms user name and icon size into the rfs image path.
454     *
455     * @param name the user name
456     * @param size IconSize to get icon for
457     *
458     * @return the path
459     */
460    private String toRfsName(String name, IconSize size) {
461
462        return CmsStringUtil.joinPaths(m_cache.getRepositoryPath(), "" + name.hashCode()) + size.getSuffix();
463    }
464}