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.plugins.repository.AmetysObjectResolver;
036import org.ametys.plugins.repository.AmetysRepositoryException;
037import org.ametys.plugins.repository.lock.LockAwareAmetysObject;
038import org.ametys.plugins.repository.metadata.CompositeMetadata;
039import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata;
040import org.ametys.plugins.repository.version.MetadataAndVersionAwareAmetysObject;
041import org.ametys.plugins.repository.version.ModifiableMetadataAwareVersionableAmetysObject;
042import org.ametys.runtime.config.Config;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.runtime.parameter.ParameterHelper;
045import org.ametys.runtime.parameter.ParameterHelper.ParameterType;
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().getValueAsLong("remind.content.validation.delay");
098        Long unmodifiedAlertDelay = Config.getInstance().getValueAsLong("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 MetadataAndVersionAwareAmetysObject && 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                CompositeMetadata meta = ((MetadataAndVersionAwareAmetysObject) content).getUnversionedMetadataHolder();
121
122                boolean unmodifiedAlertEnabled = meta.getBoolean(AlertsConstants.UNMODIFIED_ALERT_ENABLED, false);
123                if (unmodifiedAlertEnabled)
124                {
125                    String unmodifiedAlertText = meta.getString(AlertsConstants.UNMODIFIED_ALERT_TEXT, "");
126                    results.put("unmodifiedAlertText", unmodifiedAlertText);
127                }
128
129                unmodifiedAlertStatus = _getUnmodifiedStatusAlertsInformations(unmodifiedAlertStatus, unmodifiedAlertEnabled);
130
131                boolean reminderEnabled = meta.getBoolean(AlertsConstants.REMINDER_ENABLED, false);
132                if (reminderEnabled)
133                {
134                    String reminderDateStr = meta.getString(AlertsConstants.REMINDER_DATE, "");
135                    String reminderText = meta.getString(AlertsConstants.REMINDER_TEXT, "");
136
137                    results.put("reminderDate", reminderDateStr);
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) || (localUnmodifiedAlertStatus == AlertsStatus.DISABLED && unmodifiedAlertEnabled))
174        {
175            // Alert status is different for at least one content
176            localUnmodifiedAlertStatus = AlertsStatus.MIXED;
177        }
178        return localUnmodifiedAlertStatus;
179    }
180
181    private void _getEnabledDisabledAlertsInformations(Map<String, Object> results, Long unmodifiedAlertDelay, Content content, Map<String, Object> contentParams,
182            boolean unmodifiedAlertEnabled, boolean reminderEnabled)
183    {
184        if (unmodifiedAlertEnabled && unmodifiedAlertDelay > 0 || reminderEnabled)
185        {
186            I18nizableText ed = (I18nizableText) this._script.getParameters().get("alerts-enabled-description");
187            I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), Collections.singletonList(_contentHelper.getTitle(content)));
188
189            contentParams.put("description", msg);
190
191            @SuppressWarnings("unchecked")
192            List<Map<String, Object>> enabledAlerts = (List<Map<String, Object>>) results.get("alert-enabled-contents");
193            enabledAlerts.add(contentParams);
194        }
195        else
196        {
197            I18nizableText ed = (I18nizableText) this._script.getParameters().get("alerts-disabled-description");
198            I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), Collections.singletonList(_contentHelper.getTitle(content)));
199
200            contentParams.put("description", msg);
201
202            @SuppressWarnings("unchecked")
203            List<Map<String, Object>> disabledAlerts = (List<Map<String, Object>>) results.get("alert-disabled-contents");
204            disabledAlerts.add(contentParams);
205        }
206    }
207
208    /**
209     * Test if the content is locked and cannot be unlocked by the current user.
210     * <br>
211     * If this is so, fill the "not-unlockable" list in the result map.
212     * 
213     * @param content the content to test.
214     * @param results the result map.
215     * @return true if the content is locked and cannot be unlocked by the
216     *         current user, false otherwise.
217     */
218    @SuppressWarnings("unchecked")
219    protected boolean isUnlockable(Content content, Map<String, Object> results)
220    {
221        boolean isUnlockable = false;
222
223        if (content instanceof LockAwareAmetysObject)
224        {
225            LockAwareAmetysObject lockAwareContent = (LockAwareAmetysObject) content;
226
227            if (lockAwareContent.isLocked() && !_lockManager.canUnlock(lockAwareContent))
228            {
229                Map<String, Object> contentParams = getContentDefaultParameters(content);
230
231                I18nizableText ed = (I18nizableText) this._script.getParameters().get("not-unlockable-description");
232                I18nizableText msg = new I18nizableText(ed.getCatalogue(), ed.getKey(), Collections.singletonList(_contentHelper.getTitle(content)));
233
234                contentParams.put("description", msg);
235
236                List<Map<String, Object>> notUnlockable = (List<Map<String, Object>>) results.get("not-unlockable-contents");
237                notUnlockable.add(contentParams);
238
239                isUnlockable = true;
240            }
241        }
242
243        return isUnlockable;
244    }
245
246    /**
247     * Get the default content's parameters
248     * 
249     * @param content The content
250     * @return The default parameters
251     */
252    protected Map<String, Object> getContentDefaultParameters(Content content)
253    {
254        Map<String, Object> contentParams = new HashMap<>();
255        contentParams.put("id", content.getId());
256        contentParams.put("title", _contentHelper.getTitle(content));
257
258        return contentParams;
259    }
260
261    /**
262     * Set alerts on content
263     * 
264     * @param contentIds the content's id
265     * @param params the alerts' parameters
266     * @throws ServiceException if an error occurred while retrieving the alert
267     *             scheduler component
268     */
269    @Callable
270    public void setAlertsOnContent(List<String> contentIds, Map<String, Object> params) throws ServiceException
271    {
272        for (String contentId : contentIds)
273        {
274            Content content = _resolver.resolveById(contentId);
275            if (content instanceof ModifiableMetadataAwareVersionableAmetysObject)
276            {
277                _setAlerts((ModifiableMetadataAwareVersionableAmetysObject) content, params);
278            }
279        }
280
281        String role = params.containsKey("role") ? (String) params.get("role") : AlertScheduler.ROLE;
282        AlertScheduler alertScheduler = (AlertScheduler) _smanager.lookup(role);
283
284        boolean instantAlertEnabled = (Boolean) params.get("instantAlertEnabled");
285        if (instantAlertEnabled)
286        {
287            String instantAlertText = (String) params.get("instantAlertText");
288            alertScheduler.sendInstantAlerts(contentIds, org.apache.commons.lang3.StringUtils.trimToEmpty(instantAlertText));
289        }
290    }
291
292    /**
293     * Sets the alerts on the specified content.
294     * 
295     * @param content the content to set the alerts on.
296     * @param params the alerts' parameters
297     * @throws AmetysRepositoryException if a repository error occurs.
298     */
299    protected void _setAlerts(ModifiableMetadataAwareVersionableAmetysObject content, Map<String, Object> params) throws AmetysRepositoryException
300    {
301        ModifiableCompositeMetadata meta = content.getUnversionedMetadataHolder();
302
303        // Set alert for unmodified contents
304        if (params.get("unmodifiedAlertEnabled") != null)
305        {
306            boolean unmodifiedAlertEnabled = (Boolean) params.get("unmodifiedAlertEnabled");
307            String unmodifiedAlertText = (String) params.get("unmodifiedAlertText");
308
309            meta.setMetadata(AlertsConstants.UNMODIFIED_ALERT_ENABLED, unmodifiedAlertEnabled);
310            meta.setMetadata(AlertsConstants.UNMODIFIED_ALERT_TEXT, org.apache.commons.lang3.StringUtils.trimToEmpty(unmodifiedAlertText));
311        }
312
313        // Set reminders
314        if (params.get("reminderEnabled") != null)
315        {
316            boolean reminderEnabled = (Boolean) params.get("reminderEnabled");
317            meta.setMetadata(AlertsConstants.REMINDER_ENABLED, reminderEnabled);
318
319            String reminderDateStr = (String) params.get("reminderDate");
320            String reminderText = (String) params.get("reminderText");
321            meta.setMetadata(AlertsConstants.REMINDER_TEXT, org.apache.commons.lang3.StringUtils.trimToEmpty(reminderText));
322
323            // Parse the reminder date.
324            if (StringUtils.isNotBlank(reminderDateStr))
325            {
326                Date date = (Date) ParameterHelper.castValue(reminderDateStr, ParameterType.DATE);
327                if (date != null)
328                {
329                    meta.setMetadata(AlertsConstants.REMINDER_DATE, date);
330                }
331                else
332                {
333                    getLogger().error("Unable to parse reminder date " + reminderDateStr);
334                }
335            }
336        }
337
338        content.saveChanges();
339    }
340
341    @Override
342    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
343    {
344        if (Config.getInstance().getValueAsBoolean("remind.content.enabled"))
345        {
346            return super.getScripts(ignoreRights, contextParameters);
347        }
348
349        return new ArrayList<>();
350    }
351}