001/* 002 * Copyright 2010 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.cms.alerts; 017 018import java.io.IOException; 019import java.time.LocalDate; 020import java.time.ZonedDateTime; 021import java.util.ArrayList; 022import java.util.Calendar; 023import java.util.Collections; 024import java.util.Date; 025import java.util.GregorianCalendar; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Set; 029 030import org.apache.avalon.framework.activity.Initializable; 031import org.apache.avalon.framework.configuration.Configuration; 032import org.apache.avalon.framework.configuration.ConfigurationException; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.cocoon.components.ContextHelper; 036import org.apache.cocoon.environment.Request; 037import org.apache.commons.lang.StringUtils; 038import org.quartz.JobDataMap; 039import org.quartz.JobExecutionContext; 040 041import org.ametys.cms.content.archive.ArchiveConstants; 042import org.ametys.cms.repository.Content; 043import org.ametys.cms.repository.ContentQueryHelper; 044import org.ametys.cms.repository.ModifiableContent; 045import org.ametys.cms.repository.WorkflowStepExpression; 046import org.ametys.cms.repository.WorkflowStepExpression.LogicalOperator; 047import org.ametys.core.right.RightManager; 048import org.ametys.core.user.User; 049import org.ametys.core.user.UserIdentity; 050import org.ametys.core.user.population.PopulationContextHelper; 051import org.ametys.core.util.DateUtils; 052import org.ametys.core.util.I18nUtils; 053import org.ametys.core.util.mail.SendMailHelper; 054import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable; 055import org.ametys.plugins.core.schedule.Scheduler; 056import org.ametys.plugins.repository.AmetysObjectIterable; 057import org.ametys.plugins.repository.AmetysObjectResolver; 058import org.ametys.plugins.repository.AmetysRepositoryException; 059import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 060import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 061import org.ametys.plugins.repository.query.expression.AndExpression; 062import org.ametys.plugins.repository.query.expression.BooleanExpression; 063import org.ametys.plugins.repository.query.expression.DateExpression; 064import org.ametys.plugins.repository.query.expression.Expression; 065import org.ametys.plugins.repository.query.expression.Expression.Operator; 066import org.ametys.plugins.repository.query.expression.ExpressionContext; 067import org.ametys.plugins.repository.query.expression.MetadataExpression; 068import org.ametys.plugins.repository.query.expression.NotExpression; 069import org.ametys.plugins.repository.query.expression.OrExpression; 070import org.ametys.plugins.repository.version.DataAndVersionAwareAmetysObject; 071import org.ametys.plugins.repository.version.ModifiableDataAwareVersionableAmetysObject; 072import org.ametys.runtime.config.Config; 073import org.ametys.runtime.i18n.I18nizableText; 074 075import jakarta.mail.MessagingException; 076 077/** 078 * Alerts engine: sends alerts mail. 079 */ 080public class AlertSchedulable extends AbstractStaticSchedulable implements Initializable 081{ 082 /** The schedulable id */ 083 public static final String SCHEDULABLE_ID = AlertSchedulable.class.getName(); 084 085 /** The job context param to set to true when using instantMode */ 086 public static final String JOBDATAMAP_INSTANT_MODE_KEY = "instantMode"; 087 088 /** The job context param to specify the target contents when using instantMode */ 089 public static final String JOBDATAMAP_CONTENT_IDS_KEY = "contentIds"; 090 091 /** The job context param to set the message when using instantMode */ 092 public static final String JOBDATAMAP_MESSAGE_KEY = "message"; 093 094 /** The context used for validation alerts expressions */ 095 protected static final ExpressionContext __VALIDATION_ALERT_EXPR_CONTEXT = ExpressionContext.newInstance().withUnversioned(true); 096 097 /** The service manager. */ 098 protected ServiceManager _manager; 099 100 /** The server base URL. */ 101 protected String _baseUrl; 102 103 /** Is the engine initialized ? */ 104 protected boolean _initialized; 105 106 /** The cocoon environment context. */ 107 protected org.apache.cocoon.environment.Context _environmentContext; 108 109 /** The ametys object resolver. */ 110 protected AmetysObjectResolver _resolver; 111 112 /** The rights manager. */ 113 protected RightManager _rightManager; 114 115 /** The i18n utils. */ 116 protected I18nUtils _i18nUtils; 117 118 /** The content of "from" field in emails. */ 119 protected String _mailFrom; 120 121 /** The "waiting for validation" e-mail will be sent to users that have at least one of this rights. */ 122 protected Set<String> _awaitingValidationRights; 123 /** The "waiting for validation" e-mail subject i18n key. */ 124 protected String _awaitingValidationSubject; 125 /** The "waiting for validation" e-mail body i18n key. */ 126 protected String _awaitingValidationBody; 127 128 /** Only contents in this steps will be taken into account for the "unmodified content" alert. */ 129 protected int[] _unmodifiedContentStepIds; 130 /** The "unmodified content" e-mail will be sent to users that have at least one of this rights. */ 131 protected Set<String> _unmodifiedContentRights; 132 /** The "unmodified content" e-mail subject i18n key. */ 133 protected String _unmodifiedContentSubject; 134 /** The "unmodified content" e-mail body i18n key. */ 135 protected String _unmodifiedContentBody; 136 137 /** The reminder e-mail will be sent to users that have this at least one of this rights. */ 138 protected Set<String> _reminderRights; 139 /** The reminder e-mail subject i18n key. */ 140 protected String _reminderSubject; 141 /** The reminder e-mail body i18n key. */ 142 protected String _reminderBody; 143 144 /** The scheduled archiving reminder e-mail will be sent to users that have this at least one of this rights. */ 145 protected Set<String> _scheduledArchivingReminderRights; 146 /** The scheduled archiving reminder e-mail subject i18n key. */ 147 protected String _scheduledArchivingReminderSubject; 148 /** The scheduled archiving reminder e-mail body i18n key. */ 149 protected String _scheduledArchivingReminderBody; 150 151 /** The instant alert e-mail will be sent to users that have this at least one of this rights. */ 152 protected Set<String> _instantAlertRights; 153 /** The instant alert e-mail subject i18n key. */ 154 protected String _instantAlertSubject; 155 /** The instant alert e-mail body i18n key. */ 156 protected String _instantAlertBody; 157 158 @Override 159 public void service(ServiceManager manager) throws ServiceException 160 { 161 super.service(manager); 162 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 163 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 164 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 165 } 166 167 public void initialize() throws Exception 168 { 169 _baseUrl = StringUtils.stripEnd(StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html"), "/"); 170 _mailFrom = Config.getInstance().getValue("smtp.mail.from"); 171 } 172 173 /** 174 * Configure the engine (called by the scheduler). 175 * @param configuration the component configuration. 176 * @throws ConfigurationException if the configuration is not valid 177 */ 178 @Override 179 public void configure(Configuration configuration) throws ConfigurationException 180 { 181 super.configure(configuration); 182 Configuration instantConf = configuration.getChild("instantAlert"); 183 Configuration validationConf = configuration.getChild("awaitingValidation"); 184 Configuration unmodifiedConf = configuration.getChild("unmodifiedContent"); 185 Configuration reminderConf = configuration.getChild("reminder"); 186 Configuration scheduledArchivingReminderConf = configuration.getChild("scheduledArchiving"); 187 188 // Configure the rights. 189 _instantAlertRights = _getRightsFromConf(instantConf); 190 _awaitingValidationRights = _getRightsFromConf(validationConf); 191 _unmodifiedContentRights = _getRightsFromConf(unmodifiedConf); 192 _reminderRights = _getRightsFromConf(reminderConf); 193 _scheduledArchivingReminderRights = _getRightsFromConf(scheduledArchivingReminderConf); 194 Configuration[] stepIds = unmodifiedConf.getChildren("stepId"); 195 _unmodifiedContentStepIds = new int[stepIds.length]; 196 int i = 0; 197 for (Configuration stepId : stepIds) 198 { 199 try 200 { 201 _unmodifiedContentStepIds[i] = Integer.parseInt(stepId.getValue("")); 202 i++; 203 } 204 catch (NumberFormatException e) 205 { 206 // Ignore 207 } 208 } 209 210 // Configure the i18n texts. 211 _awaitingValidationSubject = validationConf.getChild("subjectKey").getValue(); 212 _awaitingValidationBody = validationConf.getChild("bodyKey").getValue(); 213 _unmodifiedContentSubject = unmodifiedConf.getChild("subjectKey").getValue(); 214 _unmodifiedContentBody = unmodifiedConf.getChild("bodyKey").getValue(); 215 _reminderSubject = reminderConf.getChild("subjectKey").getValue(); 216 _reminderBody = reminderConf.getChild("bodyKey").getValue(); 217 _scheduledArchivingReminderSubject = scheduledArchivingReminderConf.getChild("subjectKey").getValue(); 218 _scheduledArchivingReminderBody = scheduledArchivingReminderConf.getChild("bodyKey").getValue(); 219 _instantAlertSubject = instantConf.getChild("subjectKey").getValue(); 220 _instantAlertBody = instantConf.getChild("bodyKey").getValue(); 221 } 222 223 /** 224 * Set the necessary request attributes 225 * @param content The content 226 */ 227 protected void _setRequestAttributes (Content content) 228 { 229 Request request = ContextHelper.getRequest(_context); 230 231 List<String> populationContexts = new ArrayList<>(); 232 populationContexts.add("/application"); 233 234 request.setAttribute(PopulationContextHelper.POPULATION_CONTEXTS_REQUEST_ATTR, populationContexts); 235 } 236 237 /** 238 * Send all the alerts. Can be overridden to add alerts. 239 * @throws AmetysRepositoryException if an error occurs. 240 */ 241 @Override 242 public void execute(JobExecutionContext context) throws Exception 243 { 244 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 245 if (isInstantMode(jobDataMap)) 246 { 247 @SuppressWarnings("unchecked") 248 List<String> contentIds = (List<String>) jobDataMap.get(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_CONTENT_IDS_KEY); 249 String message = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_MESSAGE_KEY); 250 _sendInstantAlerts(contentIds, message); 251 } 252 else 253 { 254 _sendAwaitingValidationAlerts(); 255 _sendUnmodifiedAlerts(); 256 _sendReminders(); 257 _sendScheduledArchivingReminders(); 258 } 259 } 260 261 /** 262 * Check if the scheduler was triggered to send an instant alert 263 * @param jobDataMap the job data map 264 * @return true if the scheduler was triggered to send an instant alert 265 */ 266 protected boolean isInstantMode(JobDataMap jobDataMap) 267 { 268 boolean instantMode = jobDataMap.getBooleanValue(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_INSTANT_MODE_KEY); 269 return instantMode; 270 } 271 272 /** 273 * Send instant alerts on contents 274 * @param contentIds The contents to alert on 275 * @param message The custom message to add to the mail 276 * @throws AmetysRepositoryException if an error occurred 277 */ 278 protected void _sendInstantAlerts (List<String> contentIds, String message) throws AmetysRepositoryException 279 { 280 if (contentIds != null && !contentIds.isEmpty()) 281 { 282 for (String contentId : contentIds) 283 { 284 Content content = _resolver.resolveById(contentId); 285 _sendInstantAlertEmail (content, message); 286 } 287 } 288 } 289 290 /** 291 * Send a instant e-mail alert to all the users who have the right to edit the content. 292 * @param content the content about which to send the alert. 293 * @param message the message 294 * @throws AmetysRepositoryException if an error occurred 295 */ 296 protected void _sendInstantAlertEmail(Content content, String message) throws AmetysRepositoryException 297 { 298 _setRequestAttributes(content); 299 300 Set<UserIdentity> users = new HashSet<>(); 301 for (String right : _instantAlertRights) 302 { 303 users.addAll(_rightManager.getAllowedUsers(right, content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 304 } 305 306 List<String> params = _getInstantAlertParams(content, message); 307 308 I18nizableText i18nSubject = new I18nizableText(null, getI18nKeyBody(_instantAlertSubject, content), params); 309 I18nizableText i18nBody = new I18nizableText(null, getI18nKeyBody(_instantAlertBody, content), params); 310 311 String subject = _i18nUtils.translate(i18nSubject); 312 String body = _i18nUtils.translate(i18nBody); 313 314 _sendMails(subject, body, users, _mailFrom); 315 } 316 317 /** 318 * Send the "awaiting validation" alerts. 319 * @throws AmetysRepositoryException if an error occurs. 320 */ 321 protected void _sendAwaitingValidationAlerts() throws AmetysRepositoryException 322 { 323 Long delay = Config.getInstance().getValue("remind.content.validation.delay"); 324 if (delay != null && delay > 0) 325 { 326 Calendar calendar = new GregorianCalendar(); 327 _removeTimeParts(calendar); 328 calendar.add(Calendar.DAY_OF_MONTH, 1 - delay.intValue()); 329 330 // No last date and X days after the proposal date. 331 Expression noLastDateExpr = new NotExpression(new MetadataExpression(AlertsConstants.AWAITING_VALIDATION_ALERT_LAST_DATE, __VALIDATION_ALERT_EXPR_CONTEXT)); 332 Expression waitingExpression = new DateExpression("proposalDate", Operator.LT, calendar.getTime(), __VALIDATION_ALERT_EXPR_CONTEXT); 333 // Or proposal date before the last "awaiting validation" date and the and X days after the last "awaiting validation" date. 334 Expression proposalBeforeLastDateExpr = new BinaryExpression("proposalDate", __VALIDATION_ALERT_EXPR_CONTEXT, Operator.LT, AlertsConstants.AWAITING_VALIDATION_ALERT_LAST_DATE, __VALIDATION_ALERT_EXPR_CONTEXT); 335 Expression lastDateExpr = new DateExpression(AlertsConstants.AWAITING_VALIDATION_ALERT_LAST_DATE, Operator.LT, calendar.getTime(), __VALIDATION_ALERT_EXPR_CONTEXT); 336 Expression expression = new OrExpression(new AndExpression(noLastDateExpr, waitingExpression), 337 new AndExpression(proposalBeforeLastDateExpr, lastDateExpr)); 338 339 String query = ContentQueryHelper.getContentXPathQuery(expression); 340 341 try (AmetysObjectIterable<ModifiableContent> contents = _resolver.query(query)) 342 { 343 if (getLogger().isInfoEnabled()) 344 { 345 getLogger().info("Contents waiting for validation: " + contents.getSize()); 346 } 347 348 for (ModifiableContent content : contents) 349 { 350 // Send the alert e-mails. 351 _sendAwaitingValidationEmail(content); 352 353 // Set the last validation alert date to now. 354 ModifiableModelLessDataHolder dataHolder = ((ModifiableDataAwareVersionableAmetysObject) content).getUnversionedDataHolder(); 355 dataHolder.setValue(AlertsConstants.AWAITING_VALIDATION_ALERT_LAST_DATE, ZonedDateTime.now()); 356 357 content.saveChanges(); 358 } 359 } 360 } 361 } 362 363 /** 364 * Send the unmodified content alerts. 365 * @throws AmetysRepositoryException if an error occurs. 366 */ 367 protected void _sendUnmodifiedAlerts() throws AmetysRepositoryException 368 { 369 Long delay = Config.getInstance().getValue("remind.unmodified.content.delay"); 370 if (delay != null && delay > 0) 371 { 372 Calendar calendar = new GregorianCalendar(); 373 _removeTimeParts(calendar); 374 calendar.add(Calendar.DAY_OF_MONTH, 1 - delay.intValue()); 375 376 ExpressionContext exprContext = ExpressionContext.newInstance().withUnversioned(true); 377 378 // If no step is configured, stepExpr will return the empty string. 379 Expression stepExpr = new WorkflowStepExpression(Operator.EQ, _unmodifiedContentStepIds, LogicalOperator.OR); 380 // Get only the contents on which the alert is enabled. 381 Expression unmodifiedExpr = new BooleanExpression(AlertsConstants.UNMODIFIED_ALERT_ENABLED, true, exprContext); 382 // No last date and X days after the proposal date, or X days after the last date. 383 Expression noLastDateExpr = new NotExpression(new MetadataExpression(AlertsConstants.UNMODIFIED_ALERT_LAST_DATE, exprContext)); 384 Expression lastModifiedExpression = new DateExpression("lastModified", Operator.LT, calendar.getTime()); 385 Expression lastDateExpr = new DateExpression(AlertsConstants.UNMODIFIED_ALERT_LAST_DATE, Operator.LT, calendar.getTime(), exprContext); 386 Expression dateExpr = new OrExpression(new AndExpression(noLastDateExpr, lastModifiedExpression), lastDateExpr); 387 // Full AND expression. 388 Expression expression = new AndExpression(unmodifiedExpr, dateExpr, stepExpr); 389 390 String query = ContentQueryHelper.getContentXPathQuery(expression); 391 392 try (AmetysObjectIterable<ModifiableContent> contents = _resolver.query(query)) 393 { 394 if (getLogger().isInfoEnabled()) 395 { 396 getLogger().info("Contents not modified for " + delay + " days: " + contents.getSize()); 397 } 398 399 for (ModifiableContent content : contents) 400 { 401 // Send the alert e-mail. 402 _sendUnmodifiedContentEmail(content); 403 404 // Set the last unmodified alert date to now. 405 ModifiableModelLessDataHolder dataHolder = ((ModifiableDataAwareVersionableAmetysObject) content).getUnversionedDataHolder(); 406 dataHolder.setValue(AlertsConstants.UNMODIFIED_ALERT_LAST_DATE, ZonedDateTime.now()); 407 408 content.saveChanges(); 409 } 410 } 411 } 412 } 413 414 /** 415 * Send the content reminders. 416 * @throws AmetysRepositoryException if an error occurs. 417 */ 418 protected void _sendReminders() throws AmetysRepositoryException 419 { 420 Date now = DateUtils.asDate(LocalDate.now()); 421 422 ExpressionContext exprContext = ExpressionContext.newInstance().withUnversioned(true); 423 Expression reminderExpr = new BooleanExpression(AlertsConstants.REMINDER_ENABLED, Operator.EQ, true, exprContext); 424 Expression dateExpr = new DateExpression(AlertsConstants.REMINDER_DATE, Operator.EQ, now, exprContext); 425 Expression expression = new AndExpression(reminderExpr, dateExpr); 426 427 String query = ContentQueryHelper.getContentXPathQuery(expression); 428 429 try (AmetysObjectIterable<Content> contents = _resolver.query(query)) 430 { 431 if (getLogger().isInfoEnabled()) 432 { 433 getLogger().info("Contents with reminder today: " + contents.getSize()); 434 } 435 436 for (Content content : contents) 437 { 438 _sendReminderEmail(content); 439 } 440 } 441 } 442 443 /** 444 * Send the scheduled archiving reminders on contents. 445 * @throws AmetysRepositoryException if an error occurs. 446 */ 447 protected void _sendScheduledArchivingReminders() throws AmetysRepositoryException 448 { 449 Long delay = Config.getInstance().getValue("archive.scheduler.reminder.delay"); 450 if (delay != null && delay > 0) 451 { 452 Calendar calendar = new GregorianCalendar(); 453 _removeTimeParts(calendar); 454 calendar.add(Calendar.DAY_OF_MONTH, delay.intValue()); 455 456 ExpressionContext exprContext = ExpressionContext.newInstance().withUnversioned(true); 457 458 // No last date and X days before the scheduled date. 459 Expression noLastDateExpr = new NotExpression(new MetadataExpression(AlertsConstants.SCHEDULED_ARCHIVING_REMINDER_LAST_DATE, exprContext)); 460 Expression scheduledDelayExpression = new DateExpression(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE, Operator.LE, calendar.getTime(), exprContext); 461 Expression expression = new AndExpression(noLastDateExpr, scheduledDelayExpression); 462 463 String query = ContentQueryHelper.getContentXPathQuery(expression); 464 465 try (AmetysObjectIterable<ModifiableContent> contents = _resolver.query(query)) 466 { 467 if (getLogger().isInfoEnabled()) 468 { 469 getLogger().info("Contents with scheduled archiving reminder today: " + contents.getSize()); 470 } 471 472 for (ModifiableContent content : contents) 473 { 474 _sendScheduledArchivingReminderEmail(content); 475 476 // Set the last scheduled archiving reminder date to now. 477 ModifiableModelLessDataHolder dataHolder = ((ModifiableDataAwareVersionableAmetysObject) content).getUnversionedDataHolder(); 478 dataHolder.setValue(AlertsConstants.SCHEDULED_ARCHIVING_REMINDER_LAST_DATE, ZonedDateTime.now()); 479 480 content.saveChanges(); 481 } 482 } 483 } 484 } 485 486 /** 487 * Send a "waiting for validation" e-mail alert to all the users who have the right to validate. 488 * @param content the content about which to send the alert. 489 * @throws AmetysRepositoryException if an error occured on the repository 490 */ 491 protected void _sendAwaitingValidationEmail(Content content) throws AmetysRepositoryException 492 { 493 _setRequestAttributes(content); 494 495 Set<UserIdentity> users = new HashSet<>(); 496 for (String right : _awaitingValidationRights) 497 { 498 users.addAll(_rightManager.getAllowedUsers(right, content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 499 } 500 501 List<String> params = _getAwaitingValidationParams(content); 502 503 I18nizableText i18nSubject = new I18nizableText(null, getI18nKeyBody(_awaitingValidationSubject, content), params); 504 I18nizableText i18nBody = new I18nizableText(null, getI18nKeyBody(_awaitingValidationBody, content), params); 505 506 String subject = _i18nUtils.translate(i18nSubject); 507 String body = _i18nUtils.translate(i18nBody); 508 509 _sendMails(subject, body, users, _mailFrom); 510 } 511 512 /** 513 * Send a "unmodified content" e-mail alert to all the users who have the right to edit. 514 * @param content the content about which to send the alert. 515 * @throws AmetysRepositoryException if an error occured on the repository 516 */ 517 protected void _sendUnmodifiedContentEmail(Content content) throws AmetysRepositoryException 518 { 519 _setRequestAttributes(content); 520 521 Set<UserIdentity> users = new HashSet<>(); 522 for (String right : _unmodifiedContentRights) 523 { 524 users.addAll(_rightManager.getAllowedUsers(right, content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 525 } 526 527 List<String> params = _getUnmodifiedContentParams(content); 528 529 I18nizableText i18nSubject = new I18nizableText(null, getI18nKeyBody(_unmodifiedContentSubject, content), params); 530 I18nizableText i18nBody = new I18nizableText(null, getI18nKeyBody(_unmodifiedContentBody, content), params); 531 532 String subject = _i18nUtils.translate(i18nSubject); 533 String body = _i18nUtils.translate(i18nBody); 534 535 _sendMails(subject, body, users, _mailFrom); 536 } 537 538 /** 539 * Send a reminder e-mail to all the users who have the right to edit. 540 * @param content the content about which to send the reminder. 541 * @throws AmetysRepositoryException if an error occured on the repository 542 */ 543 protected void _sendReminderEmail(Content content) throws AmetysRepositoryException 544 { 545 _setRequestAttributes(content); 546 547 Set<UserIdentity> users = new HashSet<>(); 548 for (String right : _reminderRights) 549 { 550 users.addAll(_rightManager.getAllowedUsers(right, content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 551 } 552 553 List<String> params = _getReminderParams(content); 554 555 I18nizableText i18nSubject = new I18nizableText(null, getI18nKeyBody(_reminderSubject, content), params); 556 I18nizableText i18nBody = new I18nizableText(null, getI18nKeyBody(_reminderBody, content), params); 557 558 String subject = _i18nUtils.translate(i18nSubject); 559 String body = _i18nUtils.translate(i18nBody); 560 561 _sendMails(subject, body, users, _mailFrom); 562 } 563 564 /** 565 * Send a "scheduled archiving reminder" e-mail to all the users who have the right to archive. 566 * @param content the content about which to send the alert. 567 * @throws AmetysRepositoryException if an error occured on the repository 568 */ 569 protected void _sendScheduledArchivingReminderEmail(ModifiableContent content) throws AmetysRepositoryException 570 { 571 _setRequestAttributes(content); 572 573 Set<UserIdentity> users = new HashSet<>(); 574 for (String right : _scheduledArchivingReminderRights) 575 { 576 users.addAll(_rightManager.getAllowedUsers(right, content).resolveAllowedUsers(Config.getInstance().getValue("runtime.mail.massive.sending"))); 577 } 578 579 List<String> params = _getScheduledArchivingReminderParams(content); 580 581 I18nizableText i18nSubject = new I18nizableText(null, getI18nKeyBody(_scheduledArchivingReminderSubject, content), params); 582 I18nizableText i18nBody = new I18nizableText(null, getI18nKeyBody(_scheduledArchivingReminderBody, content), params); 583 584 String subject = _i18nUtils.translate(i18nSubject); 585 String body = _i18nUtils.translate(i18nBody); 586 587 _sendMails(subject, body, users, _mailFrom); 588 } 589 590 /** 591 * Get the transform i18n body key for specific content 592 * @param bodyI18nKey the original body key 593 * @param content the content 594 * @return the transform i18n body key 595 */ 596 protected String getI18nKeyBody(String bodyI18nKey, Content content) 597 { 598 return bodyI18nKey; 599 } 600 601 /** 602 * Get the mail parameters for instant alert. 603 * @param content the content. 604 * @param message the message 605 * @return the parameters. 606 */ 607 protected List<String> _getInstantAlertParams(Content content, String message) 608 { 609 // TODO switch to named params 610 List<String> params = new ArrayList<>(); 611 612 params.add(content.getTitle(null)); // {0} 613 params.add(_getContentUrl(content)); // {1} 614 params.add(message); // {2} 615 616 params.addAll(_getAdditionalParams(content)); 617 618 return params; 619 } 620 621 /** 622 * Get the additional i18n parameters for content 623 * @param content The content 624 * @return The additional i18n parameters 625 */ 626 protected List<String> _getAdditionalParams (Content content) 627 { 628 return Collections.EMPTY_LIST; 629 } 630 631 /** 632 * Get the mail parameters. 633 * @param content the content. 634 * @return the parameters. 635 */ 636 protected List<String> _getAwaitingValidationParams(Content content) 637 { 638 // TODO switch to named params 639 List<String> params = new ArrayList<>(); 640 641 Long delay = Config.getInstance().getValue("remind.content.validation.delay"); 642 643 params.add(content.getTitle(null)); // {0} 644 params.add(_getContentUrl(content)); // {1} 645 params.add(String.valueOf(delay)); // {2} 646 647 params.addAll(_getAdditionalParams(content)); 648 649 return params; 650 } 651 652 /** 653 * Get the mail parameters. 654 * @param content the content. 655 * @return the parameters. 656 */ 657 protected List<String> _getUnmodifiedContentParams(Content content) 658 { 659 // TODO switch to named params 660 List<String> params = new ArrayList<>(); 661 662 Long delay = Config.getInstance().getValue("remind.unmodified.content.delay"); 663 664 params.add(content.getTitle(null)); // {0} 665 params.add(_getContentUrl(content)); // {1} 666 params.add(String.valueOf(delay)); // {2} 667 668 ModelLessDataHolder dataHolder = ((DataAndVersionAwareAmetysObject) content).getUnversionedDataHolder(); 669 String alertText = dataHolder.getValue(AlertsConstants.UNMODIFIED_ALERT_TEXT, StringUtils.EMPTY); 670 params.add(alertText); // {3} 671 672 params.addAll(_getAdditionalParams(content)); 673 674 return params; 675 } 676 677 /** 678 * Get the mail parameters. 679 * @param content the content. 680 * @return the parameters. 681 */ 682 protected List<String> _getReminderParams(Content content) 683 { 684 // TODO switch to named params 685 List<String> params = new ArrayList<>(); 686 687 // Should never trigger a ClassCastException, as we query on an unversioned metadata. 688 ModelLessDataHolder dataHolder = ((DataAndVersionAwareAmetysObject) content).getUnversionedDataHolder(); 689 String reminderText = dataHolder.getValue(AlertsConstants.REMINDER_TEXT, StringUtils.EMPTY); 690 691 params.add(content.getTitle(null)); // {0} 692 params.add(_getContentUrl(content)); // {1} 693 params.add(reminderText); // {2} 694 695 params.addAll(_getAdditionalParams(content)); 696 697 return params; 698 } 699 700 /** 701 * Get the mail parameters. 702 * @param content the content. 703 * @return the parameters. 704 */ 705 protected List<String> _getScheduledArchivingReminderParams(ModifiableContent content) 706 { 707 // TODO switch to named params 708 List<String> params = new ArrayList<>(); 709 710 Long delay = Config.getInstance().getValue("archive.scheduler.reminder.delay"); 711 712 params.add(content.getTitle(null)); // {0} 713 params.add(_getContentUrl(content)); // {1} 714 params.add(String.valueOf(delay)); // {2} 715 716 params.addAll(_getAdditionalParams(content)); 717 718 return params; 719 } 720 721 /** 722 * Send the alert emails. 723 * @param subject the e-mail subject. 724 * @param body the e-mail body. 725 * @param users users to send the mail to. 726 * @param from the address sending the e-mail. 727 */ 728 protected void _sendMails(String subject, String body, Set<UserIdentity> users, String from) 729 { 730 for (UserIdentity identity : users) 731 { 732 User user = _userManager.getUser(identity.getPopulationId(), identity.getLogin()); 733 734 if (user != null && StringUtils.isNotBlank(user.getEmail())) 735 { 736 String mail = user.getEmail(); 737 738 try 739 { 740 SendMailHelper.newMail() 741 .withSubject(subject) 742 .withTextBody(body) 743 .withSender(from) 744 .withRecipient(mail) 745 .sendMail(); 746 } 747 catch (MessagingException | IOException e) 748 { 749 if (getLogger().isWarnEnabled()) 750 { 751 getLogger().warn("Could not send an alert e-mail to " + mail, e); 752 } 753 } 754 } 755 } 756 } 757 758 /** 759 * Get the URL to the given content tool. 760 * @param content the content. 761 * @return the content URL. 762 */ 763 protected String _getContentUrl(Content content) 764 { 765 StringBuilder url = new StringBuilder(_baseUrl); 766 url.append("/index.html?uitool=uitool-content,id:%27").append(content.getId()).append("%27"); 767 768 return url.toString(); 769 } 770 771 /** 772 * Remove the time parts from a calendar, leaving only date parts. 773 * @param calendar the calendar. 774 */ 775 protected void _removeTimeParts(Calendar calendar) 776 { 777 calendar.set(Calendar.HOUR_OF_DAY, 0); 778 calendar.set(Calendar.MINUTE, 0); 779 calendar.set(Calendar.SECOND, 0); 780 calendar.set(Calendar.MILLISECOND, 0); 781 } 782 783 /** 784 * Get a set of rights from a configuration. 785 * @param configuration the configuration. 786 * @return the set of rights. 787 * @throws ConfigurationException if the configuration is not valid. 788 */ 789 protected Set<String> _getRightsFromConf(Configuration configuration) throws ConfigurationException 790 { 791 Set<String> rights = new HashSet<>(); 792 793 for (Configuration rightConf : configuration.getChildren("right")) 794 { 795 String right = rightConf.getValue(""); 796 if (StringUtils.isNotBlank(right)) 797 { 798 rights.add(right); 799 } 800 } 801 802 return rights; 803 } 804 805 /** 806 * Binary date expression: test on two metadatas. 807 */ 808 protected class BinaryExpression implements Expression 809 { 810 private MetadataExpression _metadata1; 811 private MetadataExpression _metadata2; 812 private Operator _operator; 813 814 /** 815 * Creates the comparison Expression. 816 * @param metadata1 the first metadata name. 817 * @param operator the operator to make the comparison 818 * @param metadata2 the second metadata name. 819 */ 820 public BinaryExpression(String metadata1, Operator operator, String metadata2) 821 { 822 _metadata1 = new MetadataExpression(metadata1); 823 _operator = operator; 824 _metadata2 = new MetadataExpression(metadata2); 825 } 826 827 /** 828 * Creates the comparison Expression. 829 * @param metadata1 the first metadata name. 830 * @param context1 context of the expression for the first metadata 831 * @param operator the operator to make the comparison. 832 * @param metadata2 the second metadata name. 833 * @param context2 context of the expression for the second metadata 834 */ 835 public BinaryExpression(String metadata1, ExpressionContext context1, Operator operator, String metadata2, ExpressionContext context2) 836 { 837 _metadata1 = new MetadataExpression(metadata1, context1); 838 _operator = operator; 839 _metadata2 = new MetadataExpression(metadata2, context2); 840 } 841 842 @Override 843 public String build() 844 { 845 return _metadata1.build() + " " + _operator + " " + _metadata2.build(); 846 } 847 } 848 849}