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        /**The big icon size. */
069        Big(96, BIG_ICON_SUFFIX),
070        /**The small icon size.*/
071        Small(32, SMALL_ICON_SUFFIX),
072        /**The tiny icon size. */
073        Tiny(23, TINY_ICON_SUFFIX);
074
075        /**Size in pixel.*/
076        private int m_size;
077
078        /**Suffix to append to filename.*/
079        private String m_suffix;
080
081        /**
082         * constructor.<p>
083         *
084         * @param size in pixel
085         * @param suffix for filename
086         */
087        private IconSize(int size, String suffix) {
088            m_size = size;
089            m_suffix = suffix;
090        }
091
092        /**
093         * Gets size in pixel.<p>
094         *
095         * @return icon size in pixel
096         */
097        public int getSize() {
098
099            return m_size;
100        }
101
102        /**
103         * Gets the suffix.<p>
104         *
105         * @return string
106         */
107        public String getSuffix() {
108
109            return m_suffix;
110        }
111
112    }
113
114    /** The color reserved for admin users. */
115    public static final Color ADMIN_COLOR = new Color(0x00, 0x30, 0x82);
116
117    /** The big icon suffix. */
118    public static final String BIG_ICON_SUFFIX = "_big_icon.png";
119
120    /** The target folder name. */
121    public static final String ICON_FOLDER = "user_icons";
122
123    /** The small icon suffix. */
124    public static final String SMALL_ICON_SUFFIX = "_small_icon.png";
125
126    /** The tiny icon suffix. */
127    public static final String TINY_ICON_SUFFIX = "_tiny_icon.png";
128
129    /** The temp folder name. */
130    public static final String TEMP_FOLDER = "temp/";
131
132    /** The user image folder. */
133    public static final String USER_IMAGE_FOLDER = "/system/userimages/";
134
135    /** The user image additional info key. */
136    public static final String USER_IMAGE_INFO = "USER_IMAGE";
137
138    /** Logger instance for this class. */
139    private static final Log LOG = CmsLog.getLog(CmsUserIconHelper.class);
140
141    /** The admin cms context. */
142    private CmsObject m_adminCms;
143
144    /** The image cache. */
145    private CmsVfsNameBasedDiskCache m_cache;
146
147    /** The icon renderer. */
148    private IdentIcon m_renderer;
149
150    /**
151     * Constructor.<p>
152     *
153     * @param adminCms the admin cms context
154     */
155    public CmsUserIconHelper(CmsObject adminCms) {
156        m_adminCms = adminCms;
157        m_renderer = new IdentIcon();
158        m_renderer.setReservedColor(ADMIN_COLOR);
159
160        m_cache = new CmsVfsNameBasedDiskCache(
161            OpenCms.getSystemInfo().getWebApplicationRfsPath() + "/" + CmsWorkplace.RFS_PATH_RESOURCES,
162            ICON_FOLDER);
163    }
164
165    /**
166     * Checks whether the given user has an individual user image.<p>
167     *
168     * @param user the user
169     *
170     * @return <code>true</code> if the given user has an individual user image
171     */
172    public static boolean hasUserImage(CmsUser user) {
173
174        return CmsStringUtil.isNotEmptyOrWhitespaceOnly((String)user.getAdditionalInfo(USER_IMAGE_INFO));
175    }
176
177    /**
178     * Deletes the user image of the current user.<p>
179     *
180     * @param cms the cms context
181     */
182    public void deleteUserImage(CmsObject cms) {
183
184        CmsUser user = cms.getRequestContext().getCurrentUser();
185        String userIconPath = (String)user.getAdditionalInfo(USER_IMAGE_INFO);
186        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(userIconPath)) {
187            try {
188                CmsObject adminCms = OpenCms.initCmsObject(m_adminCms);
189                if (adminCms.existsResource(userIconPath)) {
190
191                    CmsProject tempProject = adminCms.createTempfileProject();
192                    adminCms.getRequestContext().setCurrentProject(tempProject);
193                    adminCms.lockResource(userIconPath);
194                    adminCms.deleteResource(userIconPath, CmsResource.DELETE_REMOVE_SIBLINGS);
195                }
196                user.deleteAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO);
197                adminCms.writeUser(user);
198
199                try {
200                    OpenCms.getPublishManager().publishProject(adminCms);
201                } catch (Exception e) {
202                    LOG.error("Error publishing user image resources.", e);
203                }
204            } catch (CmsException e) {
205                LOG.error("Error deleting previous user image.", e);
206            }
207        }
208    }
209
210    /**
211     * Returns the big ident-icon path for the given user.<p>
212     *
213     * @param cms the cms context
214     * @param user the user
215     *
216     * @return the icon path
217     */
218    public String getBigIconPath(CmsObject cms, CmsUser user) {
219
220        return getIconPath(cms, user, IconSize.Big);
221    }
222
223    /**
224     * Returns the small ident-icon path for the given user.<p>
225     *
226     * @param cms the cms context
227     * @param user the user
228     *
229     * @return the icon path
230     */
231    public String getSmallIconPath(CmsObject cms, CmsUser user) {
232
233        return getIconPath(cms, user, IconSize.Small);
234    }
235
236    /**
237     * Returns the tiny ident-icon path for the given user.<p>
238     *
239     * @param cms the cms context
240     * @param user the user
241     *
242     * @return the icon path
243     */
244    public String getTinyIconPath(CmsObject cms, CmsUser user) {
245
246        return getIconPath(cms, user, IconSize.Tiny);
247    }
248
249    /**
250     * Handles a user image upload.
251     * The uploaded file will be scaled and save as a new file beneath /system/userimages/, the original file will be deleted.<p>
252     *
253     * @param cms the cms context
254     * @param user the user
255     * @param uploadedFile the uploaded file
256     *
257     * @return <code>true</code> in case the image was set successfully
258     */
259    public boolean handleImageUpload(CmsObject cms, CmsUser user, String uploadedFile) {
260
261        boolean result = false;
262        try {
263            setUserImage(cms, user, uploadedFile);
264            result = true;
265        } catch (CmsException e) {
266            LOG.error("Error setting user image.", e);
267        }
268        try {
269            cms.lockResource(uploadedFile);
270            cms.deleteResource(uploadedFile, CmsResource.DELETE_REMOVE_SIBLINGS);
271        } catch (CmsException e) {
272            LOG.error("Error deleting user image temp file.", e);
273        }
274        return result;
275    }
276
277    /**
278     * Handles a user image upload.
279     * The uploaded file will be scaled and save as a new file beneath /system/userimages/, the original file will be deleted.<p>
280     *
281     * @param cms the cms context
282     * @param uploadedFiles the uploaded file paths
283     *
284     * @return <code>true</code> in case the image was set successfully
285     */
286    public boolean handleImageUpload(CmsObject cms, List<String> uploadedFiles) {
287
288        boolean result = false;
289        if (uploadedFiles.size() == 1) {
290            String tempFile = CmsStringUtil.joinPaths(USER_IMAGE_FOLDER, TEMP_FOLDER, uploadedFiles.get(0));
291            result = handleImageUpload(cms, cms.getRequestContext().getCurrentUser(), tempFile);
292        }
293        return result;
294    }
295
296    /**
297     * Sets the user image for the given user.<p>
298     *
299     * @param cms the cms context
300     * @param user the user
301     * @param rootPath the image root path
302     *
303     * @throws CmsException in case anything goes wrong
304     */
305    public void setUserImage(CmsObject cms, CmsUser user, String rootPath) throws CmsException {
306
307        CmsFile tempFile = cms.readFile(cms.getRequestContext().removeSiteRoot(rootPath));
308        CmsImageScaler scaler = new CmsImageScaler(tempFile.getContents(), tempFile.getRootPath());
309
310        if (scaler.isValid()) {
311            scaler.setType(2);
312            scaler.setHeight(192);
313            scaler.setWidth(192);
314            byte[] content = scaler.scaleImage(tempFile);
315            String previousImage = (String)user.getAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO);
316            String newFileName = USER_IMAGE_FOLDER
317                + user.getId().toString()
318                + "_"
319                + System.currentTimeMillis()
320                + getSuffix(tempFile.getName());
321            CmsObject adminCms = OpenCms.initCmsObject(m_adminCms);
322            CmsProject tempProject = adminCms.createTempfileProject();
323            adminCms.getRequestContext().setCurrentProject(tempProject);
324            if (adminCms.existsResource(newFileName)) {
325                // a user image of the given name already exists, just write the new content
326                CmsFile imageFile = adminCms.readFile(newFileName);
327                adminCms.lockResource(imageFile);
328                imageFile.setContents(content);
329                adminCms.writeFile(imageFile);
330                adminCms.writePropertyObject(
331                    newFileName,
332                    new CmsProperty(CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, null, "w:192,h:192"));
333
334            } else {
335                // create a new user image file
336                adminCms.createResource(
337                    newFileName,
338                    OpenCms.getResourceManager().getResourceType(CmsResourceTypeImage.getStaticTypeName()),
339                    content,
340                    Collections.singletonList(
341                        new CmsProperty(CmsPropertyDefinition.PROPERTY_IMAGE_SIZE, null, "w:192,h:192")));
342            }
343            if (newFileName.equals(previousImage)) {
344                previousImage = null;
345            }
346
347            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(previousImage)) {
348                previousImage = (String)user.getAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO);
349                if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(previousImage)
350                    && cms.existsResource(newFileName, CmsResourceFilter.ONLY_VISIBLE_NO_DELETED)) {
351                    try {
352                        adminCms.lockResource(previousImage);
353                        adminCms.deleteResource(previousImage, CmsResource.DELETE_REMOVE_SIBLINGS);
354                    } catch (CmsException e) {
355                        LOG.error("Error deleting previous user image.", e);
356                    }
357                }
358            }
359            user.setAdditionalInfo(CmsUserIconHelper.USER_IMAGE_INFO, newFileName);
360            adminCms.writeUser(user);
361
362            try {
363                OpenCms.getPublishManager().publishProject(adminCms);
364            } catch (Exception e) {
365                LOG.error("Error publishing user image resources.", e);
366            }
367
368        }
369    }
370
371    /**
372     * Returns the ident-icon path for the given user.<p>
373     *
374     * @param cms the cms context
375     * @param user the user
376     * @param size IconSize to get icon for
377     *
378     * @return the icon path
379     */
380    private String getIconPath(CmsObject cms, CmsUser user, IconSize size) {
381
382        String userIconPath = (String)user.getAdditionalInfo(USER_IMAGE_INFO);
383        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(userIconPath)) {
384            userIconPath += size.equals(IconSize.Big)
385            ? ""
386            : "?__scale=h:" + size.getSize() + ",w:" + size.getSize() + ",t:2";
387            return OpenCms.getLinkManager().substituteLinkForRootPath(cms, userIconPath);
388        }
389
390        boolean isAdmin = OpenCms.getRoleManager().hasRole(cms, user.getName(), CmsRole.ADMINISTRATOR);
391        String name = user.getName() + Boolean.toString(isAdmin);
392        String rfsName = toRfsName(name, size);
393        String path = toPath(name, size);
394        if (!m_cache.hasCacheContent(rfsName)) {
395
396            BufferedImage icon = m_renderer.render(name, isAdmin, size.getSize());
397            try {
398                m_cache.saveCacheFile(rfsName, getImageBytes(icon));
399            } catch (Exception e) {
400                LOG.error(e.getLocalizedMessage(), e);
401            }
402        }
403        return path;
404    }
405
406    /**
407     * Returns the image data.
408     * @param image the image
409     *
410     * @return the data
411     *
412     * @throws IOException in case writing to the output stream failed
413     */
414    private byte[] getImageBytes(BufferedImage image) throws IOException {
415
416        return Simapi.getImageBytes(image, Simapi.TYPE_PNG);
417    }
418
419    /**
420     * Returns the file suffix.<p>
421     *
422     * @param fileName the file name
423     *
424     * @return the suffix
425     */
426    private String getSuffix(String fileName) {
427
428        int index = fileName.lastIndexOf(".");
429        if (index > 0) {
430            return fileName.substring(index);
431        } else {
432            return fileName;
433        }
434    }
435
436    /**
437     * Transforms user name and icon size into the image path.
438     *
439     * @param name the user name
440     * @param size IconSize to get icon for
441     *
442     * @return the path
443     */
444    private String toPath(String name, IconSize size) {
445
446        return CmsStringUtil.joinPaths(CmsWorkplace.getSkinUri(), ICON_FOLDER, "" + name.hashCode()) + size.getSuffix();
447    }
448
449    /**
450     * Transforms user name and icon size into the rfs image path.
451     *
452     * @param name the user name
453     * @param size IconSize to get icon for
454     *
455     * @return the path
456     */
457    private String toRfsName(String name, IconSize size) {
458
459        return CmsStringUtil.joinPaths(m_cache.getRepositoryPath(), "" + name.hashCode()) + size.getSuffix();
460    }
461}