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