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