001/* 002 * Copyright 2020 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.plugins.workspaces; 017 018import java.io.IOException; 019import java.util.ArrayList; 020import java.util.Arrays; 021import java.util.List; 022import java.util.Map; 023import java.util.Objects; 024import java.util.Optional; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.cocoon.Constants; 035import org.apache.cocoon.components.ContextHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.commons.lang3.tuple.Pair; 039 040import org.ametys.cms.content.indexing.solr.SolrResourceGroupedMimeTypes; 041import org.ametys.core.right.RightManager; 042import org.ametys.core.right.RightManager.RightResult; 043import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder; 044import org.ametys.core.user.CurrentUserProvider; 045import org.ametys.core.user.UserIdentity; 046import org.ametys.core.user.UserManager; 047import org.ametys.core.util.I18nUtils; 048import org.ametys.core.util.URIUtils; 049import org.ametys.core.util.language.UserLanguagesManager; 050import org.ametys.core.util.mail.SendMailHelper; 051import org.ametys.core.util.mail.SendMailHelper.MailBuilder; 052import org.ametys.plugins.explorer.resources.Resource; 053import org.ametys.plugins.repository.TraversableAmetysObject; 054import org.ametys.plugins.repository.tag.TaggableAmetysObject; 055import org.ametys.plugins.workspaces.project.ProjectConstants; 056import org.ametys.plugins.workspaces.project.ProjectManager; 057import org.ametys.plugins.workspaces.project.modules.WorkspaceModuleExtensionPoint; 058import org.ametys.plugins.workspaces.project.objects.Project; 059import org.ametys.plugins.workspaces.tags.ProjectTagsDAO; 060import org.ametys.runtime.authentication.AccessDeniedException; 061import org.ametys.runtime.i18n.I18nizableText; 062import org.ametys.runtime.plugin.component.AbstractLogEnabled; 063import org.ametys.web.URIPrefixHandler; 064import org.ametys.web.WebHelper; 065import org.ametys.web.repository.site.Site; 066import org.ametys.web.repository.site.SiteManager; 067import org.ametys.web.repository.sitemap.Sitemap; 068 069import jakarta.mail.MessagingException; 070 071/** 072 * Utility classes for workspaces 073 * 074 */ 075public class WorkspacesHelper extends AbstractLogEnabled implements Component, Serviceable, Contextualizable 076{ 077 /** Avalon role */ 078 public static final String ROLE = WorkspacesHelper.class.getName(); 079 080 /** The right manager */ 081 protected RightManager _rightManager; 082 083 /** The workspace module extension point */ 084 protected WorkspaceModuleExtensionPoint _workspaceModuleEP; 085 086 /** The current user provider. */ 087 protected CurrentUserProvider _currentUserProvider; 088 089 /** 090 * Enumeration for file type 091 */ 092 public enum FileType 093 { 094 /** Text document */ 095 TEXT, 096 /** PDF document */ 097 PDF, 098 /** Spreadsheet */ 099 SPREADSHEET, 100 /** Presentation */ 101 PRES, 102 /** Image */ 103 IMAGE, 104 /** Video */ 105 VIDEO, 106 /** Audio */ 107 AUDIO, 108 /** Archive */ 109 ARCHIVE, 110 /** Unknown type */ 111 UNKNOWN 112 } 113 114 private Context _context; 115 private org.apache.cocoon.environment.Context _cocoonContext; 116 private URIPrefixHandler _prefixHandler; 117 private ProjectTagsDAO _projectTagsDAO; 118 private SiteManager _siteManager; 119 private ProjectManager _projectManager; 120 private UserLanguagesManager _userLanguagesManager; 121 private UserManager _userManager; 122 private I18nUtils _i18nUtils; 123 private WorkspacesHelper _workspacesHelper; 124 125 public void contextualize(Context context) throws ContextException 126 { 127 _context = context; 128 _cocoonContext = (org.apache.cocoon.environment.Context) _context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 129 } 130 131 public void service(ServiceManager smanager) throws ServiceException 132 { 133 _prefixHandler = (URIPrefixHandler) smanager.lookup(URIPrefixHandler.ROLE); 134 _projectTagsDAO = (ProjectTagsDAO) smanager.lookup(ProjectTagsDAO.ROLE); 135 _projectManager = (ProjectManager) smanager.lookup(ProjectManager.ROLE); 136 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 137 138 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 139 _workspaceModuleEP = (WorkspaceModuleExtensionPoint) smanager.lookup(WorkspaceModuleExtensionPoint.ROLE); 140 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 141 _userLanguagesManager = (UserLanguagesManager) smanager.lookup(UserLanguagesManager.ROLE); 142 _userManager = (UserManager) smanager.lookup(UserManager.ROLE); 143 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 144 _workspacesHelper = (WorkspacesHelper) smanager.lookup(WorkspacesHelper.ROLE); 145 } 146 147 /** 148 * Determines if the resource file is an image 149 * @param file the resource 150 * @return true if the resource file is an image 151 */ 152 public boolean isImage(Resource file) 153 { 154 return SolrResourceGroupedMimeTypes.getGroup(file.getMimeType()) 155 .map(groupMimeType -> FileType.IMAGE == FileType.valueOf(groupMimeType.toUpperCase())) 156 .orElse(false); 157 } 158 159 /** 160 * Get the file type of a resource file 161 * @param file the resource 162 * @return the {@link FileType} 163 */ 164 public FileType getFileType(Resource file) 165 { 166 Optional<String> group = SolrResourceGroupedMimeTypes.getGroup(file.getMimeType()); 167 168 return group 169 .map(groupMimeType -> FileType.valueOf(groupMimeType.toUpperCase())) 170 .orElse(FileType.UNKNOWN); 171 } 172 173 /** 174 * Get the file type of a resource from its name 175 * @param filename the resource's name 176 * @return the {@link FileType} 177 */ 178 public FileType getFileType(String filename) 179 { 180 String mimeType = _cocoonContext.getMimeType(filename); 181 Optional<String> group = SolrResourceGroupedMimeTypes.getGroup(mimeType); 182 183 return group 184 .map(groupMimeType -> FileType.valueOf(groupMimeType.toUpperCase())) 185 .orElse(FileType.UNKNOWN); 186 } 187 188 /** 189 * Get user avatar 190 * @param userIdentity the user identity 191 * @param lang the lang for user's content 192 * @param size the size in pixel of image 193 * @return the avatar url 194 */ 195 public String getAvatar(UserIdentity userIdentity, String lang, int size) 196 { 197 StringBuilder sb = new StringBuilder(); 198 199 String siteName = WebHelper.getSiteName(_getRequest()); 200 sb.append(_prefixHandler.getUriPrefix(siteName)) 201 .append("/_plugins/core-ui/user/") 202 .append(userIdentity.getPopulationId()) 203 .append("/") 204 .append(URIUtils.encodePath(userIdentity.getLogin())) 205 .append("/image_") 206 .append(size) 207 .append("?lang=") 208 .append(lang); 209 210 return sb.toString(); 211 } 212 213 private Request _getRequest() 214 { 215 return ContextHelper.getRequest(_context); 216 } 217 218 /** 219 * Get current project from request 220 * @return the current project or null if not found. 221 */ 222 public Project getProjectFromRequest() 223 { 224 Request request = _getRequest(); 225 String projectName = (String) request.getAttribute(WorkspacesConstants.REQUEST_ATTR_PROJECT_NAME); 226 227 if (StringUtils.isNotEmpty(projectName)) 228 { 229 return _projectManager.getProject(projectName); 230 } 231 232 String siteName = WebHelper.getSiteName(request); 233 if (StringUtils.isNotEmpty(siteName) && _siteManager.hasSite(siteName)) 234 { 235 Site site = _siteManager.getSite(siteName); 236 List<Project> projects = _projectManager.getProjectsForSite(site); 237 if (!projects.isEmpty()) 238 { 239 return projects.get(0); 240 } 241 } 242 243 return null; 244 } 245 246 /** 247 * Handle tags for the edition of a TaggableAmetysObject 248 * @param taggableAmetysObject the object to edit 249 * @param tags the tags 250 * @param moduleRoot the module root, used to check tag creation rights if needed 251 * @return the created tags 252 */ 253 public List<Map<String, Object>> handleTags(TaggableAmetysObject taggableAmetysObject, List<Object> tags, TraversableAmetysObject moduleRoot) 254 { 255 List<String> createdTags = new ArrayList<>(); 256 List<Map<String, Object>> createdTagsJson = new ArrayList<>(); 257 // Tag new tags 258 for (Object tag : tags) 259 { 260 // Tag doesn't exist so create the tag 261 if (tag instanceof Map) 262 { 263 if (_rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_ADD_TAG, moduleRoot) != RightResult.RIGHT_ALLOW) 264 { 265 throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to handle tags without convenient right."); 266 } 267 @SuppressWarnings("unchecked") 268 String tagText = (String) ((Map<String, Object>) tag).get("text"); 269 List<Map<String, Object>> newTags = _projectTagsDAO.addTags(new String[] {tagText}); 270 String newTag = (String) newTags.get(0).get("name"); 271 taggableAmetysObject.tag(newTag); 272 createdTags.add(newTag); 273 createdTagsJson.addAll(newTags); 274 } 275 else 276 { 277 taggableAmetysObject.tag((String) tag); 278 } 279 } 280 // Untag unused tags 281 for (String tag : taggableAmetysObject.getTags()) 282 { 283 if (!tags.contains(tag) && !createdTags.contains(tag)) 284 { 285 taggableAmetysObject.untag(tag); 286 } 287 } 288 return createdTagsJson; 289 } 290 291 /** 292 * Get the lang for this project, or en if none found 293 * @param project the project to check 294 * @return a lang in this project, or en if none found 295 */ 296 public String getLang(Project project) 297 { 298 // Return "en" by default if no language is available, or if the site could not be found 299 return getLang(project, "en"); 300 } 301 /** 302 * Get the lang for this project, or given default language if none found 303 * @param project the project to check 304 * @param defaultLanguage the default language to return if no language is available for the project or if the site could not be found 305 * @return a lang in this project or given default language 306 */ 307 public String getLang(Project project, String defaultLanguage) 308 { 309 /* 310 * get the site, then the sitemaps (transformed back to a stream of sitemap) 311 * then get the name 312 * return the 1st one, or the default language if none is available, or if the site could not be found 313 */ 314 315 Site site = project.getSite(); 316 317 if (site != null) 318 { 319 return site.getSitemaps() 320 .stream() 321 .filter(Objects::nonNull) 322 .map(Sitemap::getName) 323 .findFirst() 324 .orElse(defaultLanguage); 325 } 326 else 327 { 328 return defaultLanguage; 329 } 330 } 331 332 /** 333 * Send mail to all project managers 334 * @param project the project 335 * @param mailFrom the mail from 336 * @param i18nSubject the i18n subject 337 * @param bodyBuilder the mail body builder 338 * @param i18nTextBody the i18n text body 339 * @throws MessagingException if an error occurs while sending the mail 340 * @throws IOException if an error occurs while building the mail body 341 */ 342 public void sendMailToManagers(Project project, String mailFrom, I18nizableText i18nSubject, MailBodyBuilder bodyBuilder, I18nizableText i18nTextBody) throws MessagingException, IOException 343 { 344 String defaultLanguage = _workspacesHelper.getLang(project, _userLanguagesManager.getDefaultLanguage()); 345 Map<String, List<String>> managersToNotifyByLanguage = Arrays.stream(project.getManagers()) 346 .map(manager -> _userManager.getUser(manager)) 347 .filter(Objects::nonNull) 348 .map(user -> Pair.of(user, user.getEmail())) 349 .filter(p -> StringUtils.isNotEmpty(p.getRight())) 350 .collect(Collectors.groupingBy( 351 p -> { 352 return StringUtils.defaultIfBlank(p.getLeft().getLanguage(), defaultLanguage); 353 }, 354 Collectors.mapping( 355 Pair::getRight, 356 Collectors.toList() 357 ) 358 ) 359 ); 360 361 for (String lang : managersToNotifyByLanguage.keySet()) 362 { 363 // If no user language, user project site one 364 String subject = _i18nUtils.translate(i18nSubject, lang); 365 366 String htmlBody = bodyBuilder.withLanguage(lang).build(); 367 368 MailBuilder mail = SendMailHelper.newMail() 369 .withSubject(subject) 370 .withHTMLBody(htmlBody) 371 .withSender(mailFrom) 372 .withRecipients(managersToNotifyByLanguage.get(lang)); 373 374 if (i18nTextBody != null) 375 { 376 String textBody = _i18nUtils.translate(i18nTextBody, lang); 377 mail.withTextBody(textBody); 378 } 379 380 mail.sendMail(); 381 } 382 } 383}