001/*
002 *  Copyright 2014 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.clientsideelement;
017
018import java.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
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.ObservationConstants;
031import org.ametys.cms.alerts.AlertsConstants;
032import org.ametys.cms.content.archive.ArchiveConstants;
033import org.ametys.cms.repository.Content;
034import org.ametys.core.observation.Event;
035import org.ametys.core.observation.ObservationManager;
036import org.ametys.core.right.RightManager.RightResult;
037import org.ametys.core.ui.Callable;
038import org.ametys.core.util.DateUtils;
039import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
040import org.ametys.plugins.repository.version.ModifiableDataAwareVersionableAmetysObject;
041import org.ametys.runtime.config.Config;
042import org.ametys.runtime.i18n.I18nizableText;
043
044/**
045 * This element creates a ribbon button to schedule the archiving of a content
046 */
047public class ScheduleArchivingClientSideElement extends SmartContentClientSideElement
048{
049    /** I18n error key : content type error */
050    private static final String __I18N_KEY_CONTENT_TYPE_ERROR = "PLUGINS_CMS_ARCHIVE_SCHEDULE_CONTENT_TYPE_ERROR";
051    
052    /** I18n error key : no right */
053    private static final String __I18N_KEY_RIGHT_ERROR = "PLUGINS_CMS_ARCHIVE_SCHEDULE_NO_RIGHT";
054    
055    /** I18n error key : unexpected error */
056    private static final String __I18N_KEY_UNEXPECTED_ERROR = "PLUGINS_CMS_ARCHIVE_SCHEDULE_UNEXPECTED_ERROR";
057    
058    private ObservationManager _observationManager;
059
060    @Override
061    public void service(ServiceManager manager) throws ServiceException
062    {
063        super.service(manager);
064        _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE);
065    }
066    
067    /**
068     * Set or remove the <code>ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE</code> on a content.
069     * @param contentIds The ids of contents
070     * @param scheduledDateAsStr The schedule date as string
071     * @return the successful and failed contents
072     */
073    @Callable(rights = Callable.SKIP_BUILTIN_CHECK)
074    public Map<String, Object> setScheduledArchivingDate (List<String> contentIds, String scheduledDateAsStr)
075    {
076        Map<String, Object> results = new HashMap<>();
077        
078        // Error list
079        List<I18nizableText> failedContents = new ArrayList<>();
080
081        // Success list, containing the contentId that have been successfully scheduled
082        List<String> successContents = new ArrayList<>();
083
084        ZonedDateTime scheduledDate = DateUtils.parseZonedDateTime(scheduledDateAsStr);
085        
086        if (StringUtils.isNotEmpty(scheduledDateAsStr))
087        {
088            if (scheduledDate == null)
089            {
090                getLogger().error("Cannot cast value '" + scheduledDateAsStr + "' into type 'date'");
091                throw new IllegalArgumentException("Invalid format for date value '" + scheduledDateAsStr + "'");
092            }
093
094            // Date must be set at least after tomorrow
095            /*if (scheduledDate.compareTo(new DateMidnight().plusDays(1).toDate()) < 0)
096            {
097                getLogger().error("The scheduled date '" + scheduledDate + "' cannot be set before the current date");
098                throw new IllegalArgumentException("The scheduled date '" + scheduledDate + "' cannot be set before the current date");
099            }*/
100        }
101        
102        // For each content
103        for (String contentId : contentIds)
104        {
105            try
106            {
107                Content content = _resolver.resolveById(contentId);
108                
109                if (_rightManager.currentUserHasRight(ArchiveContentClientSideElement.WORKFLOW_RIGHTS_ARCHIVE, content) != RightResult.RIGHT_ALLOW)
110                {
111                    // report error
112                    List<String> i18nParams = new ArrayList<>();
113                    i18nParams.add(content.getTitle(null));
114                    failedContents.add(new I18nizableText("plugin.cms", __I18N_KEY_RIGHT_ERROR, i18nParams));
115                    continue;
116                }
117                
118                if (content instanceof ModifiableDataAwareVersionableAmetysObject)
119                {
120                    ModifiableDataAwareVersionableAmetysObject uContent = (ModifiableDataAwareVersionableAmetysObject) content;
121                    ModifiableModelLessDataHolder dataHolder = uContent.getUnversionedDataHolder();
122
123                    // Remove META_ARCHIVE_SCHEDULED_DATE and
124                    // SCHEDULED_ARCHIVING_REMINDER_LAST_DATE if date is empty.
125                    if (StringUtils.isEmpty(scheduledDateAsStr))
126                    {
127                        dataHolder.removeValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE);
128                        if (getLogger().isInfoEnabled())
129                        {
130                            getLogger().info("Content with id : '" + uContent.getId() + "' does not have a scheduled archiving date anymore.");
131                        }
132
133                        dataHolder.removeValue(AlertsConstants.SCHEDULED_ARCHIVING_REMINDER_LAST_DATE);
134                    }
135                    else
136                    {
137                        dataHolder.setValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE, scheduledDate);
138                    }
139
140                    if (uContent.needsSave())
141                    {
142                        uContent.saveChanges();
143
144                        // report success
145                        successContents.add(uContent.getId());
146
147                        // Notify observer
148                        Map<String, Object> eventParams = new HashMap<>();
149                        eventParams.put(ObservationConstants.ARGS_CONTENT, content);
150                        eventParams.put(ObservationConstants.ARGS_CONTENT_ID, contentId);
151
152                        _observationManager.notify(new Event(ObservationConstants.EVENT_CONTENT_MODIFIED, _currentUserProvider.getUser(), eventParams));
153                    }
154                }
155                else
156                {
157                    getLogger().error("Unable to set a scheduled archiving for the content : '" + content.getId() + "'. It is not a ModifiableMetadataAwareVersionableAmetysObject.");
158
159                    // report error
160                    List<String> i18nParams = new ArrayList<>();
161                    i18nParams.add(content.getTitle(null));
162                    failedContents.add(new I18nizableText("plugin.cms", __I18N_KEY_CONTENT_TYPE_ERROR, i18nParams));
163
164                }
165            }
166            catch (Exception e)
167            {
168                getLogger().error("Unexpected exception while trying to schedule an archiving for the content : '" + contentId + "'.");
169
170                // report error
171                List<String> i18nParams = new ArrayList<>();
172                i18nParams.add(contentId);
173                failedContents.add(new I18nizableText("plugin.cms", __I18N_KEY_UNEXPECTED_ERROR, i18nParams));
174            }
175        }
176
177        results.put("error", failedContents);
178        results.put("success", successContents);
179        
180        return results;
181    }
182
183    /**
184     * Gets the scheduled archiving dates of one or several contents
185     * @param contentIds the ids of the contents
186     * @return result the server's response in JSON
187     */
188    @Callable(rights = ArchiveContentClientSideElement.WORKFLOW_RIGHTS_ARCHIVE)
189    public Map<String, Object> getScheduledArchivingDate(List<String> contentIds)
190    {
191        Map<String, Object> results = new HashMap<> ();
192        Set<ZonedDateTime> dates = new HashSet<>();
193    
194        // Retrieves the date for each content
195        for (String contentId : contentIds)
196        {
197            Content content = _resolver.resolveById(contentId);
198    
199            if (content instanceof ModifiableDataAwareVersionableAmetysObject)
200            {
201                ZonedDateTime date = ((ModifiableDataAwareVersionableAmetysObject) content).getUnversionedDataHolder().getValue(ArchiveConstants.META_ARCHIVE_SCHEDULED_DATE);
202                dates.add(date);
203            }
204        }
205    
206        // Retrieves the closest date from current time.
207        dates.remove(null);
208        ZonedDateTime scheduledDate = getDateNearestToCurrent(dates);
209    
210        // Generate the JSON
211        Map<String, Object> content = new HashMap<> ();
212        if (scheduledDate != null)
213        {
214            content.put("date", DateUtils.zonedDateTimeToString(scheduledDate));
215        }
216        results.put("content", content);
217        
218        return results;
219    }
220    
221    private ZonedDateTime getDateNearestToCurrent(Set<ZonedDateTime> dates)
222    {
223        ZonedDateTime returnDate = null;
224        ZonedDateTime currentDate = ZonedDateTime.now();
225
226        for (ZonedDateTime date : dates)
227        {
228            if (date.compareTo(currentDate) >= 0 && (returnDate == null || date.compareTo(returnDate) < 0))
229            {
230                returnDate = date;
231            }
232        }
233        return returnDate;
234    }
235    
236    @Override
237    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
238    {
239        boolean enabled = Config.getInstance().getValue("archive.scheduler.enabled");
240        if (enabled)
241        {
242            return super.getScripts(ignoreRights, contextParameters);
243        }
244        
245        return new ArrayList<>();
246    }
247}