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