package org.sakaiproject.profile2.logic; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.sakaiproject.profile2.dao.ProfileDao; import org.sakaiproject.profile2.hbm.model.ProfileImageExternal; import org.sakaiproject.profile2.hbm.model.ProfileImageOfficial; import org.sakaiproject.profile2.hbm.model.ProfileImageUploaded; import org.sakaiproject.profile2.model.GalleryImage; import org.sakaiproject.profile2.model.MimeTypeByteArray; import org.sakaiproject.profile2.model.Person; import org.sakaiproject.profile2.model.ProfileImage; import org.sakaiproject.profile2.model.ProfilePreferences; import org.sakaiproject.profile2.model.ProfilePrivacy; import org.sakaiproject.profile2.util.Messages; import org.sakaiproject.profile2.util.ProfileConstants; import org.sakaiproject.profile2.util.ProfileUtils; import org.sakaiproject.user.api.User; /** * Implementation of ProfileImageLogic API * * @author Steve Swinsburg (steve.swinsburg@gmail.com) * */ public class ProfileImageLogicImpl implements ProfileImageLogic { private static final Logger log = Logger.getLogger(ProfileImageLogicImpl.class); /** * {@inheritDoc} */ public ProfileImage getProfileImage(String userUuid, ProfilePreferences prefs, ProfilePrivacy privacy, int size) { return getProfileImage(userUuid, prefs, privacy, size, null); } /** * {@inheritDoc} */ public ProfileImage getProfileImage(String userUuid, ProfilePreferences prefs, ProfilePrivacy privacy, int size, String siteId) { ProfileImage image = new ProfileImage(); boolean allowed = false; boolean isSameUser = false; String officialImageSource; String defaultImageUrl; if (ProfileConstants.PROFILE_IMAGE_THUMBNAIL == size) { defaultImageUrl = getUnavailableImageThumbnailURL(); } else { defaultImageUrl = getUnavailableImageURL(); } //get current user String currentUserUuid = sakaiProxy.getCurrentUserId(); if(StringUtils.equals(userUuid, currentUserUuid)){ isSameUser = true; } //if no current user we are not logged in (could be from entity provider) if(StringUtils.isBlank(currentUserUuid)){ // this is where the logic for handling public profile images will go //right now we throw a security exception. throw new SecurityException("Must be logged in to request a profile image."); } //check prefs supplied was valid, if given if(prefs != null && !StringUtils.equals(userUuid, prefs.getUserUuid())) { log.error("ProfilePreferences data supplied was not for user: " + userUuid); image.setExternalImageUrl(defaultImageUrl); image.setAltText(getAltText(userUuid, isSameUser, false)); return image; } //check privacy supplied was valid, if given if(privacy != null && !StringUtils.equals(userUuid, privacy.getUserUuid())) { log.error("ProfilePrivacy data supplied was not for user: " + userUuid); image.setExternalImageUrl(defaultImageUrl); image.setAltText(getAltText(userUuid, isSameUser, false)); return image; } //check if same user if(isSameUser){ allowed = true; } //if we have a siteId and it's not a my workspace site, check if the current user has permissions to view the image if(StringUtils.isNotBlank(siteId)){ if(!sakaiProxy.isUserMyWorkspace(siteId)) { log.debug("checking if user: " + currentUserUuid + " has permissions in site: " + siteId); allowed = sakaiProxy.isUserAllowedInSite(currentUserUuid, ProfileConstants.ROSTER_VIEW_PHOTO, siteId); } } //if not allowed yet, check we have a privacy record, if not, get one if(!allowed && privacy == null) { privacy = privacyLogic.getPrivacyRecordForUser(userUuid); //if still null, default image if(privacy == null) { log.error("Couldn't retrieve ProfilePrivacy data for user: " + userUuid + ". Using default image."); image.setExternalImageUrl(defaultImageUrl); image.setAltText(getAltText(userUuid, isSameUser, false)); return image; } } //if not allowed, check privacy record if(!allowed) { boolean friend = connectionsLogic.isUserXFriendOfUserY(userUuid, currentUserUuid); allowed = privacyLogic.isUserXProfileImageVisibleByUserY(userUuid, privacy, currentUserUuid, friend) || sakaiProxy.isAdminUser(); } //default if still not allowed if(!allowed){ image.setExternalImageUrl(defaultImageUrl); image.setAltText(getAltText(userUuid, isSameUser, false)); return image; } //lookup global image setting, this will be used if no preferences were supplied. int imageType = sakaiProxy.getProfilePictureType(); //if we have no prefs, try to get one, it won't be considered if it is still null. if(prefs == null){ prefs = preferencesLogic.getPreferencesRecordForUser(userUuid); } //if we have prefs and the conditions are set for a user to be able to make a choice, get the pref. if(prefs != null && sakaiProxy.isUsingOfficialImageButAlternateSelectionEnabled()) { if(prefs.isUseOfficialImage()){ imageType = ProfileConstants.PICTURE_SETTING_OFFICIAL; } } //get the image based on the global type/preference switch (imageType) { case ProfileConstants.PICTURE_SETTING_UPLOAD: MimeTypeByteArray mtba = getUploadedProfileImage(userUuid, size); //if no uploaded image, use the default image url if(mtba == null || mtba.getBytes() == null) { image.setExternalImageUrl(defaultImageUrl); } else { image.setUploadedImage(mtba.getBytes()); image.setMimeType(mtba.getMimeType()); } image.setAltText(getAltText(userUuid, isSameUser, true)); break; case ProfileConstants.PICTURE_SETTING_URL: image.setExternalImageUrl(getExternalProfileImageUrl(userUuid, size)); image.setAltText(getAltText(userUuid, isSameUser, true)); break; case ProfileConstants.PICTURE_SETTING_OFFICIAL: officialImageSource = sakaiProxy.getOfficialImageSource(); //check source and get appropriate value if(StringUtils.equals(officialImageSource, ProfileConstants.OFFICIAL_IMAGE_SETTING_URL)){ image.setOfficialImageUrl(getOfficialImageUrl(userUuid)); } else if(StringUtils.equals(officialImageSource, ProfileConstants.OFFICIAL_IMAGE_SETTING_PROVIDER)){ String data = getOfficialImageEncoded(userUuid); if(StringUtils.isBlank(data)) { image.setExternalImageUrl(defaultImageUrl); } } image.setAltText(getAltText(userUuid, isSameUser, true)); break; default: image.setExternalImageUrl(defaultImageUrl); image.setAltText(getAltText(userUuid, isSameUser, false)); break; } return image; } /** * {@inheritDoc} */ public ProfileImage getProfileImage(Person person, int size) { return getProfileImage(person.getUuid(), person.getPreferences(), person.getPrivacy(), size, null); } /** * {@inheritDoc} */ public ProfileImage getProfileImage(Person person, int size, String siteId) { return getProfileImage(person.getUuid(), person.getPreferences(), person.getPrivacy(), size, siteId); } /** * {@inheritDoc} */ public boolean setUploadedProfileImage(String userUuid, byte[] imageBytes, String mimeType, String fileName) { //check auth and get currentUserUuid String currentUserUuid = sakaiProxy.getCurrentUserId(); if(currentUserUuid == null) { throw new SecurityException("You must be logged in to update a user's profile image."); } //check admin, or the currentUser and given uuid match if(!sakaiProxy.isSuperUser() && !StringUtils.equals(currentUserUuid, userUuid)) { throw new SecurityException("Not allowed to save."); } //check image is actually allowed to be changed if(!sakaiProxy.isProfilePictureChangeEnabled()) { log.warn("Profile image changes are not permitted as per sakai.properties setting 'profile2.picture.change.enabled'."); return false; } /* * MAIN PROFILE IMAGE */ //scale image imageBytes = ProfileUtils.scaleImage(imageBytes, ProfileConstants.MAX_IMAGE_XY); //create resource ID String mainResourceId = sakaiProxy.getProfileImageResourcePath(userUuid, ProfileConstants.PROFILE_IMAGE_MAIN); //save, if error, log and return. if(!sakaiProxy.saveFile(mainResourceId, userUuid, fileName, mimeType, imageBytes)) { log.error("Couldn't add main image to CHS. Aborting."); return false; } /* * THUMBNAIL PROFILE IMAGE */ //scale image imageBytes = ProfileUtils.scaleImage(imageBytes, ProfileConstants.MAX_THUMBNAIL_IMAGE_XY); //create resource ID String thumbnailResourceId = sakaiProxy.getProfileImageResourcePath(userUuid, ProfileConstants.PROFILE_IMAGE_THUMBNAIL); log.debug("Profile.ChangeProfilePicture.onSubmit thumbnailResourceId: " + thumbnailResourceId); //save, if error, warn, erase thumbnail reference, and continue (we really only need the main image) if(!sakaiProxy.saveFile(thumbnailResourceId, userUuid, fileName, mimeType, imageBytes)) { log.warn("Couldn't add thumbnail image to CHS. Main image will be used instead."); thumbnailResourceId = null; } /* * SAVE IMAGE RESOURCE IDS */ //save ProfileImageUploaded profileImage = new ProfileImageUploaded(userUuid, mainResourceId, thumbnailResourceId, true); if(dao.addNewProfileImage(profileImage)){ log.info("Added a new profile image for user: " + userUuid); return true; } return false; } /** * {@inheritDoc} */ public boolean setExternalProfileImage(String userUuid, String url, String thumbnail) { //check auth and get currentUserUuid String currentUserUuid = sakaiProxy.getCurrentUserId(); if(currentUserUuid == null) { throw new SecurityException("You must be logged in to update a user's profile image."); } //check admin, or the currentUser and given uuid match if(!sakaiProxy.isSuperUser() && !StringUtils.equals(currentUserUuid, userUuid)) { throw new SecurityException("Not allowed to save."); } //check image is actually allowed to be changed if(!sakaiProxy.isProfilePictureChangeEnabled()) { log.warn("Profile image changes are not permitted as per sakai.properties setting 'profile2.picture.change.enabled'."); return false; } //save ProfileImageExternal externalImage = new ProfileImageExternal(userUuid, url, thumbnail); if(dao.saveExternalImage(externalImage)) { log.info("Updated external image record for user: " + userUuid); return true; } return false; } /** * {@inheritDoc} */ public boolean saveOfficialImageUrl(final String userUuid, final String url) { ProfileImageOfficial officialImage = new ProfileImageOfficial(userUuid, url); if(dao.saveOfficialImageUrl(officialImage)) { log.info("Updated official image record for user: " + userUuid); return true; } return false; } /** * {@inheritDoc} */ public boolean addGalleryImage(String userUuid, byte[] imageBytes, String mimeType, String fileName) { // check auth and get currentUserUuid String currentUserUuid = sakaiProxy.getCurrentUserId(); if (currentUserUuid == null) { throw new SecurityException("You must be logged in to add a gallery image."); } // check admin, or the currentUser and given uuid match if (!sakaiProxy.isSuperUser() && !StringUtils.equals(currentUserUuid, userUuid)) { throw new SecurityException("You are not allowed to add a gallery image."); } String imageId = sakaiProxy.createUuid(); // create resource ID String mainResourcePath = sakaiProxy.getProfileGalleryImagePath(userUuid, imageId); byte[] scaledImageBytes = ProfileUtils.scaleImage(imageBytes, ProfileConstants.MAX_GALLERY_IMAGE_XY); // save image if (!sakaiProxy.saveFile(mainResourcePath, userUuid, fileName, mimeType,scaledImageBytes)) { log.error("Couldn't add gallery image to CHS. Aborting."); return false; } // create thumbnail byte[] thumbnailBytes = ProfileUtils.scaleImage(imageBytes, ProfileConstants.MAX_GALLERY_THUMBNAIL_IMAGE_XY); String thumbnailResourcePath = sakaiProxy.getProfileGalleryThumbnailPath(userUuid, imageId); sakaiProxy.saveFile(thumbnailResourcePath, userUuid, fileName, mimeType,thumbnailBytes); //save GalleryImage galleryImage = new GalleryImage(userUuid,mainResourcePath, thumbnailResourcePath, fileName); if(dao.addNewGalleryImage(galleryImage)){ log.info("Added new gallery image for user: " + galleryImage.getUserUuid()); return true; } return false; } /** * {@inheritDoc} */ public List<GalleryImage> getGalleryImages(String userUuid) { // check auth and get currentUserUuid String currentUserUuid = sakaiProxy.getCurrentUserId(); if (currentUserUuid == null) { throw new SecurityException("You must be logged in to make a request for a user's gallery images."); } return dao.getGalleryImages(userUuid); } /** * {@inheritDoc} */ public List<GalleryImage> getGalleryImagesRandomized(String userUuid) { List<GalleryImage> images = getGalleryImages(userUuid); Collections.shuffle(images); return images; } /** * {@inheritDoc} */ public boolean removeGalleryImage(String userId, long imageId) { if(userId == null || new Long(imageId) == null){ throw new IllegalArgumentException("Null argument in ProfileLogicImpl.removeGalleryImage()"); } // check auth and get currentUserUuid String currentUserUuid = sakaiProxy.getCurrentUserId(); if (currentUserUuid == null) { throw new SecurityException("You must be logged in to remove a gallery image."); } // check admin, or the currentUser and given uuid match if (!sakaiProxy.isSuperUser() && !StringUtils.equals(currentUserUuid, userId)) { throw new SecurityException("You are not allowed to remove this gallery image."); } GalleryImage galleryImage = dao.getGalleryImageRecord(userId, imageId); if(galleryImage == null){ log.error("GalleryImage record does not exist for userId: " + userId + ", imageId: " + imageId); return false; } //delete main image if (!sakaiProxy.removeResource(galleryImage.getMainResource())) { log.error("Gallery image not removed: " + galleryImage.getMainResource()); } //delete thumbnail if (!sakaiProxy.removeResource(galleryImage.getThumbnailResource())) { log.error("Gallery thumbnail not removed: " + galleryImage.getThumbnailResource()); } if(dao.removeGalleryImage(galleryImage)){ log.info("User: " + userId + " removed gallery image: " + imageId); return true; } return false; } private String getUnavailableImageURL(String imagePath) { StringBuilder path = new StringBuilder(); path.append(sakaiProxy.getServerUrl()); path.append(imagePath); return path.toString(); } /** * {@inheritDoc} */ public String getUnavailableImageURL() { return getUnavailableImageURL(ProfileConstants.UNAVAILABLE_IMAGE_FULL); } /** * {@inheritDoc} */ public String getUnavailableImageThumbnailURL() { return getUnavailableImageURL(ProfileConstants.UNAVAILABLE_IMAGE_THUMBNAIL); } /** * {@inheritDoc} */ public String getProfileImageEntityUrl(String userUuid, int size) { StringBuilder sb = new StringBuilder(); sb.append(sakaiProxy.getServerUrl()); sb.append("/direct/profile/"); sb.append(userUuid); sb.append("/image/"); if(size == ProfileConstants.PROFILE_IMAGE_THUMBNAIL){ sb.append("thumb/"); } return sb.toString(); } /** * {@inheritDoc} */ public int getGalleryImagesCount(final String userUuid) { return dao.getGalleryImagesCount(userUuid); } /** * Get the profile image for the given user, allowing fallback if no thumbnail exists. * * @param userUuid the uuid of the user we are querying * @param size comes from ProfileConstants, main or thumbnail, also maps to a directory in ContentHosting * @return MimeTypeByteArray or null * * <p>Note: if thumbnail is requested and none exists, the main image will be returned instead. It can be scaled in the markup.</p> * */ private MimeTypeByteArray getUploadedProfileImage(String userUuid, int size) { MimeTypeByteArray mtba = new MimeTypeByteArray(); //get record from db ProfileImageUploaded profileImage = dao.getCurrentProfileImageRecord(userUuid); if(profileImage == null) { log.debug("ProfileLogic.getUploadedProfileImage() null for userUuid: " + userUuid); return null; } //get main image if(size == ProfileConstants.PROFILE_IMAGE_MAIN) { mtba = sakaiProxy.getResource(profileImage.getMainResource()); } //or get thumbnail if(size == ProfileConstants.PROFILE_IMAGE_THUMBNAIL) { mtba = sakaiProxy.getResource(profileImage.getThumbnailResource()); if(mtba.getBytes() == null) { mtba = sakaiProxy.getResource(profileImage.getMainResource()); } } return mtba; } /** * Get the URL to an image that a user has specified as their profile image * @param userId uuid of user * @param size comes from ProfileConstants. main or thumbnail. * * <p>Note: if thumbnail is requested and none exists, the main image will be returned instead. It can be scaled in the markup.</p> * * @return url to the image, or a default image if none. */ private String getExternalProfileImageUrl(final String userUuid, final int size) { //get external image record for this user ProfileImageExternal externalImage = dao.getExternalImageRecordForUser(userUuid); //setup default String defaultImageUrl = getUnavailableImageURL(); //if none, return null if(externalImage == null) { return getUnavailableImageURL(); } //else return the url for the type they requested if(size == ProfileConstants.PROFILE_IMAGE_MAIN) { String url = externalImage.getMainUrl(); if(StringUtils.isBlank(url)) { return defaultImageUrl; } return url; } if(size == ProfileConstants.PROFILE_IMAGE_THUMBNAIL) { String url = externalImage.getThumbnailUrl(); if(StringUtils.isBlank(url)) { url = externalImage.getMainUrl(); if(StringUtils.isBlank(url)) { return defaultImageUrl; } } return url; } //no url log.info("ProfileLogic.getExternalProfileImageUrl. No URL for userId: " + userUuid + ", imageType: " + size + ". Returning default."); return defaultImageUrl; } /** * Get the URL to a user's official profile image * @param userUuid uuid of user * * @return url or a default image if none */ private String getOfficialImageUrl(final String userUuid) { //get external image record for this user ProfileImageOfficial official = dao.getOfficialImageRecordForUser(userUuid); //setup default String defaultImageUrl = getUnavailableImageURL(); //if none, return null if(official == null) { return defaultImageUrl; } if(StringUtils.isBlank(official.getUrl())) { log.info("ProfileLogic.getOfficialImageUrl. No URL for userUuid: " + userUuid + ". Returning default."); return defaultImageUrl; } return official.getUrl(); } /** * Get the official image data from the user properties, encoded in BASE64 * @param userUuid uuid of user * @return base64 encoded data, or null. */ private String getOfficialImageEncoded(final String userUuid) { User u = sakaiProxy.getUserById(userUuid); return u.getProperties().getProperty(sakaiProxy.getOfficialImageAttribute()); } /** * Helper to get the altText to be used for the image * @param userUuid * @param isOwner * @param hasImage * @return */ private String getAltText(String userUuid, boolean isOwner, boolean hasImage) { //owner and has an image if(isOwner && hasImage){ return Messages.getString("profile.image.my.alt"); } //owner and doesnt have an image if(isOwner && !hasImage){ return Messages.getString("profile.image.my.none.alt"); } //not owner so get name if(!isOwner) { String displayName = sakaiProxy.getUserDisplayName(userUuid); return Messages.getString("profile.image.other.alt", new Object[] { displayName }); } return null; } private SakaiProxy sakaiProxy; public void setSakaiProxy(SakaiProxy sakaiProxy) { this.sakaiProxy = sakaiProxy; } private ProfilePrivacyLogic privacyLogic; public void setPrivacyLogic(ProfilePrivacyLogic privacyLogic) { this.privacyLogic = privacyLogic; } private ProfileConnectionsLogic connectionsLogic; public void setConnectionsLogic(ProfileConnectionsLogic connectionsLogic) { this.connectionsLogic = connectionsLogic; } private ProfilePreferencesLogic preferencesLogic; public void setPreferencesLogic(ProfilePreferencesLogic preferencesLogic) { this.preferencesLogic = preferencesLogic; } private ProfileDao dao; public void setDao(ProfileDao dao) { this.dao = dao; } }