001/*
002 *  Copyright 2026 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.workflow.schedule;
017
018import java.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.context.Context;
024import org.apache.avalon.framework.context.ContextException;
025import org.apache.avalon.framework.context.Contextualizable;
026import org.apache.cocoon.components.ContextHelper;
027import org.apache.cocoon.environment.Request;
028
029import org.ametys.cms.clientsideelement.ScheduleContentPublicationClientSideElement;
030import org.ametys.cms.repository.WorkflowAwareContent;
031import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
032import org.ametys.plugins.repository.data.holder.ModifiableDataHolder;
033import org.ametys.plugins.repository.version.ModifiableDataAwareVersionableAmetysObject;
034import org.ametys.plugins.workflow.EnhancedCondition;
035import org.ametys.runtime.i18n.I18nizableText;
036
037import com.opensymphony.module.propertyset.PropertySet;
038import com.opensymphony.workflow.WorkflowException;
039
040/**
041 * Condition that check if a publication or unpublication is currently scheduled on the content.
042 * If so, the condition is invalid unless a parameter is provided to allow the scheduled action to process
043 */
044public class ScheduledPublicationCondition extends AbstractContentWorkflowComponent implements EnhancedCondition, Contextualizable
045{
046    /** Set a transientVars or request param to true with this key to ignore this condition */
047    public static final String ALLOW_SCHEDULED_ACTION = ScheduledPublicationCondition.class.getName() + "$allowScheduled";
048    /** The avalon context */
049    protected Context _context;
050    
051    public void contextualize(Context context) throws ContextException
052    {
053        _context = context;
054    }
055    
056    public boolean passesCondition(Map transientVars, Map args, PropertySet ps) throws WorkflowException
057    {
058        WorkflowAwareContent content = getContent(transientVars);
059        ModifiableDataHolder unversionedDataHolder = ((ModifiableDataAwareVersionableAmetysObject) content).getUnversionedDataHolder();
060        
061        Long scheduledAction;
062        if (allowScheduledAction(transientVars))
063        {
064            // Check any scheduled action because we don't know the current state.
065            Long currentAction = Long.valueOf((Integer) transientVars.get("actionId"));
066            scheduledAction = unversionedDataHolder.getValue(ScheduleContentPublicationClientSideElement.PUBLISH_ACTION_ID);
067            if (currentAction.equals(scheduledAction))
068            {
069                return true;
070            }
071            scheduledAction = unversionedDataHolder.getValue(ScheduleContentPublicationClientSideElement.UNPUBLISH_ACTION_ID);
072            if (currentAction.equals(scheduledAction))
073            {
074                return true;
075            }
076        }
077        
078        // check the scheduled action are expired
079        scheduledAction = unversionedDataHolder.getValue(ScheduleContentPublicationClientSideElement.PUBLISH_ACTION_ID);
080        ZonedDateTime scheduledDate = unversionedDataHolder.getValue(ScheduleContentPublicationClientSideElement.PUBLISH_DATE);
081        // publication not planned or expired
082        if (scheduledAction == null || ZonedDateTime.now().isAfter(scheduledDate))
083        {
084            // check the unpublish
085            scheduledAction = unversionedDataHolder.getValue(ScheduleContentPublicationClientSideElement.UNPUBLISH_ACTION_ID);
086            scheduledDate = unversionedDataHolder.getValue(ScheduleContentPublicationClientSideElement.UNPUBLISH_DATE);
087            
088            // unpublication not planned or expired
089            if (scheduledAction == null || ZonedDateTime.now().isAfter(scheduledDate))
090            {
091                return true;
092            }
093        }
094        
095        _reportFailure(transientVars, content, scheduledAction);
096        
097        return false;
098    }
099
100    private void _reportFailure(Map transientVars, WorkflowAwareContent content, Long scheduledAction) throws WorkflowException
101    {
102        List<ConditionFailure> conditionFailures = getConditionFailures(transientVars);
103        if (conditionFailures != null)
104        {
105            conditionFailures.add(new ConditionFailure(
106                    String.format("Schedule condition failed : content: %s is schedule for action: %s", content.getId(), scheduledAction),
107                    ScheduledPublicationCondition.class.getName()
108                    ));
109        }
110        
111        List<String> i18nParams = new ArrayList<>();
112        i18nParams.add(_contentHelper.getTitle(content));
113        
114        addWorkflowError(transientVars, new I18nizableText("plugin.cms", "WORKFLOW_CONTENT_SCHEDULED_PUBLICATION_CONDITION_FAILED", i18nParams));
115        
116        if (_logger.isDebugEnabled())
117        {
118            _logger.debug(String.format("Content: %s is schedule for action: %s, failing condition", content.getId(), scheduledAction));
119        }
120    }
121
122    private boolean allowScheduledAction(Map transientVars)
123    {
124        Boolean allow = (Boolean) transientVars.get(ALLOW_SCHEDULED_ACTION);
125        if (allow == null)
126        {
127            Request request = ContextHelper.getRequest(_context);
128            allow = (Boolean) request.getAttribute(ALLOW_SCHEDULED_ACTION);
129        }
130        return Boolean.TRUE.equals(allow);
131    }
132   
133
134    public I18nizableText getLabel()
135    {
136        return new I18nizableText("plugin.cms", "PLUGINS_CMS_SCHEDULED_PUBLICATION_CONDITION_LABEL");
137    }
138
139}