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.Collections; 022import java.util.HashMap; 023import java.util.List; 024import java.util.Map; 025 026import org.apache.avalon.framework.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.commons.lang3.StringUtils; 029 030import org.ametys.cms.content.ContentHelper; 031import org.ametys.cms.lock.LockContentManager; 032import org.ametys.cms.repository.Content; 033import org.ametys.cms.repository.ModifiableContent; 034import org.ametys.core.ui.Callable; 035import org.ametys.core.ui.StaticClientSideElement; 036import org.ametys.core.util.DateUtils; 037import org.ametys.plugins.repository.AmetysObjectResolver; 038import org.ametys.plugins.repository.AmetysRepositoryException; 039import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 040import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder; 041import org.ametys.plugins.repository.lock.LockAwareAmetysObject; 042import org.ametys.plugins.repository.version.DataAndVersionAwareAmetysObject; 043import org.ametys.plugins.repository.version.ModifiableDataAwareVersionableAmetysObject; 044import org.ametys.runtime.config.Config; 045import org.ametys.runtime.i18n.I18nizableText; 046 047/** 048 * This element creates a toggle button representing the reminders state. 049 */ 050public class ContentAlertsClientSideElement extends StaticClientSideElement 051{ 052 /** Repository content. */ 053 protected AmetysObjectResolver _resolver; 054 055 /** The lock manager. */ 056 protected LockContentManager _lockManager; 057 058 /** The service manager */ 059 protected ServiceManager _smanager; 060 061 /** The content helper */ 062 protected ContentHelper _contentHelper; 063 064 @Override 065 public void service(ServiceManager serviceManager) throws ServiceException 066 { 067 super.service(serviceManager); 068 _smanager = serviceManager; 069 _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE); 070 _lockManager = (LockContentManager) serviceManager.lookup(LockContentManager.ROLE); 071 _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE); 072 } 073 074 enum AlertsStatus 075 { 076 /** All the alerts are enabled */ 077 ENABLED, 078 079 /** All the alerts are disabled */ 080 DISABLED, 081 082 /** Some alerts are enabled, others are disabled */ 083 MIXED 084 } 085 086 /** 087 * Get information on reminders state. 088 * 089 * @param contentsId The list of contents' ids 090 * @return informations on reminders state. 091 */ 092 @Callable 093 public Map<String, Object> getAlertsInformations(List<String> contentsId) 094 { 095 Map<String, Object> results = new HashMap<>(); 096 097 Long validationAlertDelay = Config.getInstance().getValue("remind.content.validation.delay"); 098 Long unmodifiedAlertDelay = Config.getInstance().getValue("remind.unmodified.content.delay"); 099 100 results.put("alert-enabled-contents", new ArrayList<Map<String, Object>>()); 101 results.put("alert-disabled-contents", new ArrayList<Map<String, Object>>()); 102 results.put("all-right-contents", new ArrayList<Map<String, Object>>()); 103 results.put("not-unlockable-contents", new ArrayList<Map<String, Object>>()); 104 105 AlertsStatus unmodifiedAlertStatus = null; 106 AlertsStatus reminderAlertStatus = null; 107 108 for (String contentId : contentsId) 109 { 110 Content content = _resolver.resolveById(contentId); 111 112 if (content instanceof DataAndVersionAwareAmetysObject && content instanceof ModifiableContent) 113 { 114 Map<String, Object> contentParams = getContentDefaultParameters(content); 115 116 // Test if the content is locked and cannot be unlocked by the 117 // current user. 118 boolean isValid = !isUnlockable(content, results); 119 120 ModelLessDataHolder dataHolder = ((DataAndVersionAwareAmetysObject) content).getUnversionedDataHolder(); 121 122 boolean unmodifiedAlertEnabled = dataHolder.getValue(AlertsConstants.UNMODIFIED_ALERT_ENABLED, false); 123 if (unmodifiedAlertEnabled) 124 { 125 String unmodifiedAlertText = dataHolder.getValue(AlertsConstants.UNMODIFIED_ALERT_TEXT, StringUtils.EMPTY); 126 results.put("unmodifiedAlertText", unmodifiedAlertText); 127 } 128 129 unmodifiedAlertStatus = _getUnmodifiedStatusAlertsInformations(unmodifiedAlertStatus, unmodifiedAlertEnabled); 130 131 boolean reminderEnabled = dataHolder.getValue(AlertsConstants.REMINDER_ENABLED, false); 132 if (reminderEnabled) 133 { 134 ZonedDateTime reminderDate = dataHolder.getValue(AlertsConstants.REMINDER_DATE, null); 135 String reminderText = dataHolder.getValue(AlertsConstants.REMINDER_TEXT, StringUtils.EMPTY); 136 137 results.put("reminderDate", reminderDate != null ? DateUtils.zonedDateTimeToString(reminderDate) : StringUtils.EMPTY); 138 results.put("reminderText", reminderText); 139 } 140 141 reminderAlertStatus = _getUnmodifiedStatusAlertsInformations(reminderAlertStatus, reminderEnabled); 142 143 // Alerts enabled/disabled 144 _getEnabledDisabledAlertsInformations(results, unmodifiedAlertDelay, content, contentParams, unmodifiedAlertEnabled, reminderEnabled); 145 146 // All right 147 if (isValid) 148 { 149 @SuppressWarnings("unchecked") 150 List<Map<String, Object>> validContents = (List<Map<String, Object>>) results.get("all-right-contents"); 151 validContents.add(contentParams); 152 } 153 } 154 } 155 156 results.put("reminderEnabled", reminderAlertStatus == AlertsStatus.MIXED ? null : (reminderAlertStatus == AlertsStatus.ENABLED ? true : false)); 157 results.put("unmodifiedAlertEnabled", unmodifiedAlertStatus == AlertsStatus.MIXED ? null : (unmodifiedAlertStatus == AlertsStatus.ENABLED ? true : false)); 158 159 results.put("validation-alert-delay", validationAlertDelay); 160 results.put("unmodified-alert-delay", unmodifiedAlertDelay); 161 162 return results; 163 } 164 165 private AlertsStatus _getUnmodifiedStatusAlertsInformations(AlertsStatus unmodifiedAlertStatus, boolean unmodifiedAlertEnabled) 166 { 167 AlertsStatus localUnmodifiedAlertStatus = unmodifiedAlertStatus; 168 if (localUnmodifiedAlertStatus == null) 169 { 170 // Initialize the alert status with first content 171 localUnmodifiedAlertStatus = unmodifiedAlertEnabled ? AlertsStatus.ENABLED : AlertsStatus.DISABLED; 172 } 173 else if (localUnmodifiedAlertStatus == AlertsStatus.ENABLED && !unmodifiedAlertEnabled 174 || localUnmodifiedAlertStatus == AlertsStatus.DISABLED && unmodifiedAlertEnabled) 175 { 176 // Alert status is different for at least one content 177 localUnmodifiedAlertStatus = AlertsStatus.MIXED; 178 } 179 return localUnmodifiedAlertStatus; 180 } 181 182 private void _getEnabledDisabledAlertsInformations(Map<String, Object> results, Long unmodifiedAlertDelay, Content content, Map<String, Object> contentParams, 183 boolean unmodifiedAlertEnabled, boolean reminderEnabled) 184 { 185 if (unmodifiedAlertEnabled && unmodifiedAlertDelay > 0 || reminderEnabled) 186 { 187 I18nizableText ed = (I18nizableText) this._script.getParameters().get("alerts-enabled-description"); 188 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), Collections.singletonList(_contentHelper.getTitle(content))); 189 190 contentParams.put("description", msg); 191 192 @SuppressWarnings("unchecked") 193 List<Map<String, Object>> enabledAlerts = (List<Map<String, Object>>) results.get("alert-enabled-contents"); 194 enabledAlerts.add(contentParams); 195 } 196 else 197 { 198 I18nizableText ed = (I18nizableText) this._script.getParameters().get("alerts-disabled-description"); 199 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), Collections.singletonList(_contentHelper.getTitle(content))); 200 201 contentParams.put("description", msg); 202 203 @SuppressWarnings("unchecked") 204 List<Map<String, Object>> disabledAlerts = (List<Map<String, Object>>) results.get("alert-disabled-contents"); 205 disabledAlerts.add(contentParams); 206 } 207 } 208 209 /** 210 * Test if the content is locked and cannot be unlocked by the current user. 211 * <br> 212 * If this is so, fill the "not-unlockable" list in the result map. 213 * 214 * @param content the content to test. 215 * @param results the result map. 216 * @return true if the content is locked and cannot be unlocked by the 217 * current user, false otherwise. 218 */ 219 @SuppressWarnings("unchecked") 220 protected boolean isUnlockable(Content content, Map<String, Object> results) 221 { 222 boolean isUnlockable = false; 223 224 if (content instanceof LockAwareAmetysObject) 225 { 226 LockAwareAmetysObject lockAwareContent = (LockAwareAmetysObject) content; 227 228 if (lockAwareContent.isLocked() && !_lockManager.canUnlock(lockAwareContent)) 229 { 230 Map<String, Object> contentParams = getContentDefaultParameters(content); 231 232 I18nizableText ed = (I18nizableText) this._script.getParameters().get("not-unlockable-description"); 233 I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), Collections.singletonList(_contentHelper.getTitle(content))); 234 235 contentParams.put("description", msg); 236 237 List<Map<String, Object>> notUnlockable = (List<Map<String, Object>>) results.get("not-unlockable-contents"); 238 notUnlockable.add(contentParams); 239 240 isUnlockable = true; 241 } 242 } 243 244 return isUnlockable; 245 } 246 247 /** 248 * Get the default content's parameters 249 * 250 * @param content The content 251 * @return The default parameters 252 */ 253 protected Map<String, Object> getContentDefaultParameters(Content content) 254 { 255 Map<String, Object> contentParams = new HashMap<>(); 256 contentParams.put("id", content.getId()); 257 contentParams.put("title", _contentHelper.getTitle(content)); 258 259 return contentParams; 260 } 261 262 /** 263 * Set alerts on content 264 * 265 * @param contentIds the content's id 266 * @param params the alerts' parameters 267 * @throws ServiceException if an error occurred while retrieving the alert 268 * scheduler component 269 */ 270 @Callable 271 public void setAlertsOnContent(List<String> contentIds, Map<String, Object> params) throws ServiceException 272 { 273 for (String contentId : contentIds) 274 { 275 Content content = _resolver.resolveById(contentId); 276 if (content instanceof ModifiableDataAwareVersionableAmetysObject) 277 { 278 _setAlerts((ModifiableDataAwareVersionableAmetysObject) content, params); 279 } 280 } 281 282 String role = params.containsKey("role") ? (String) params.get("role") : AlertScheduler.ROLE; 283 AlertScheduler alertScheduler = (AlertScheduler) _smanager.lookup(role); 284 285 boolean instantAlertEnabled = (Boolean) params.get("instantAlertEnabled"); 286 if (instantAlertEnabled) 287 { 288 String instantAlertText = (String) params.get("instantAlertText"); 289 alertScheduler.sendInstantAlerts(contentIds, org.apache.commons.lang3.StringUtils.trimToEmpty(instantAlertText)); 290 } 291 } 292 293 /** 294 * Sets the alerts on the specified content. 295 * 296 * @param content the content to set the alerts on. 297 * @param params the alerts' parameters 298 * @throws AmetysRepositoryException if a repository error occurs. 299 */ 300 protected void _setAlerts(ModifiableDataAwareVersionableAmetysObject content, Map<String, Object> params) throws AmetysRepositoryException 301 { 302 ModifiableModelLessDataHolder dataHolder = content.getUnversionedDataHolder(); 303 304 // Set alert for unmodified contents 305 if (params.get("unmodifiedAlertEnabled") != null) 306 { 307 boolean unmodifiedAlertEnabled = (Boolean) params.get("unmodifiedAlertEnabled"); 308 String unmodifiedAlertText = (String) params.get("unmodifiedAlertText"); 309 310 dataHolder.setValue(AlertsConstants.UNMODIFIED_ALERT_ENABLED, unmodifiedAlertEnabled); 311 dataHolder.setValue(AlertsConstants.UNMODIFIED_ALERT_TEXT, StringUtils.trimToEmpty(unmodifiedAlertText)); 312 } 313 314 // Set reminders 315 if (params.get("reminderEnabled") != null) 316 { 317 boolean reminderEnabled = (Boolean) params.get("reminderEnabled"); 318 dataHolder.setValue(AlertsConstants.REMINDER_ENABLED, reminderEnabled); 319 320 String reminderDateStr = (String) params.get("reminderDate"); 321 String reminderText = (String) params.get("reminderText"); 322 dataHolder.setValue(AlertsConstants.REMINDER_TEXT, StringUtils.trimToEmpty(reminderText)); 323 324 // Parse the reminder date. 325 if (StringUtils.isNotBlank(reminderDateStr)) 326 { 327 LocalDate localDate = DateUtils.parseLocalDate(reminderDateStr); 328 if (localDate != null) 329 { 330 ZonedDateTime zonedDateTime = DateUtils.asZonedDateTime(localDate, null); 331 dataHolder.setValue(AlertsConstants.REMINDER_DATE, zonedDateTime); 332 } 333 else 334 { 335 getLogger().error("Unable to parse reminder date " + reminderDateStr); 336 } 337 } 338 } 339 340 content.saveChanges(); 341 } 342 343 @Override 344 public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters) 345 { 346 boolean enabled = Config.getInstance().getValue("remind.content.enabled"); 347 if (enabled) 348 { 349 return super.getScripts(ignoreRights, contextParameters); 350 } 351 352 return new ArrayList<>(); 353 } 354}