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 * @param projectName the project name, used to get tags root 252 * @return the created tags 253 */ 254 public List<Map<String, Object>> handleTags(TaggableAmetysObject taggableAmetysObject, List<Object> tags, TraversableAmetysObject moduleRoot, String projectName) 255 { 256 List<String> createdTags = new ArrayList<>(); 257 List<Map<String, Object>> createdTagsJson = new ArrayList<>(); 258 // Tag new tags 259 for (Object tag : tags) 260 { 261 // Tag doesn't exist so create the tag 262 if (tag instanceof Map) 263 { 264 if (_rightManager.currentUserHasRight(ProjectConstants.RIGHT_PROJECT_ADD_TAG, moduleRoot) != RightResult.RIGHT_ALLOW) 265 { 266 throw new AccessDeniedException("User '" + _currentUserProvider.getUser() + "' tried to handle tags without convenient right."); 267 } 268 @SuppressWarnings("unchecked") 269 String tagText = (String) ((Map<String, Object>) tag).get("text"); 270 List<Map<String, Object>> newTags = _projectTagsDAO.addTags(new String[] {tagText}, Map.of("projectName", projectName)); 271 String newTag = (String) newTags.get(0).get("name"); 272 taggableAmetysObject.tag(newTag); 273 createdTags.add(newTag); 274 createdTagsJson.addAll(newTags); 275 } 276 else 277 { 278 taggableAmetysObject.tag((String) tag); 279 } 280 } 281 // Untag unused tags 282 for (String tag : taggableAmetysObject.getTags()) 283 { 284 if (!tags.contains(tag) && !createdTags.contains(tag)) 285 { 286 taggableAmetysObject.untag(tag); 287 } 288 } 289 return createdTagsJson; 290 } 291 292 /** 293 * Get the lang for this project, or en if none found 294 * @param project the project to check 295 * @return a lang in this project, or en if none found 296 */ 297 public String getLang(Project project) 298 { 299 // Return "en" by default if no language is available, or if the site could not be found 300 return getLang(project, "en"); 301 } 302 /** 303 * Get the lang for this project, or given default language if none found 304 * @param project the project to check 305 * @param defaultLanguage the default language to return if no language is available for the project or if the site could not be found 306 * @return a lang in this project or given default language 307 */ 308 public String getLang(Project project, String defaultLanguage) 309 { 310 /* 311 * get the site, then the sitemaps (transformed back to a stream of sitemap) 312 * then get the name 313 * return the 1st one, or the default language if none is available, or if the site could not be found 314 */ 315 316 Site site = project.getSite(); 317 318 if (site != null) 319 { 320 return site.getSitemaps() 321 .stream() 322 .filter(Objects::nonNull) 323 .map(Sitemap::getName) 324 .findFirst() 325 .orElse(defaultLanguage); 326 } 327 else 328 { 329 return defaultLanguage; 330 } 331 } 332 333 /** 334 * Send mail to all project managers 335 * @param project the project 336 * @param mailFrom the mail from 337 * @param i18nSubject the i18n subject 338 * @param bodyBuilder the mail body builder 339 * @param i18nTextBody the i18n text body 340 * @throws MessagingException if an error occurs while sending the mail 341 * @throws IOException if an error occurs while building the mail body 342 */ 343 public void sendMailToManagers(Project project, String mailFrom, I18nizableText i18nSubject, MailBodyBuilder bodyBuilder, I18nizableText i18nTextBody) throws MessagingException, IOException 344 { 345 String defaultLanguage = _workspacesHelper.getLang(project, _userLanguagesManager.getDefaultLanguage()); 346 Map<String, List<String>> managersToNotifyByLanguage = Arrays.stream(project.getManagers()) 347 .map(manager -> _userManager.getUser(manager)) 348 .filter(Objects::nonNull) 349 .map(user -> Pair.of(user, user.getEmail())) 350 .filter(p -> StringUtils.isNotEmpty(p.getRight())) 351 .collect(Collectors.groupingBy( 352 p -> { 353 return StringUtils.defaultIfBlank(p.getLeft().getLanguage(), defaultLanguage); 354 }, 355 Collectors.mapping( 356 Pair::getRight, 357 Collectors.toList() 358 ) 359 ) 360 ); 361 362 for (String lang : managersToNotifyByLanguage.keySet()) 363 { 364 // If no user language, user project site one 365 String subject = _i18nUtils.translate(i18nSubject, lang); 366 367 String htmlBody = bodyBuilder.withLanguage(lang).build(); 368 369 MailBuilder mail = SendMailHelper.newMail() 370 .withSubject(subject) 371 .withHTMLBody(htmlBody) 372 .withSender(mailFrom) 373 .withRecipients(managersToNotifyByLanguage.get(lang)); 374 375 if (i18nTextBody != null) 376 { 377 String textBody = _i18nUtils.translate(i18nTextBody, lang); 378 mail.withTextBody(textBody); 379 } 380 381 mail.sendMail(); 382 } 383 } 384}