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