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