001/*
002 *  Copyright 2016 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.plugins.forms.workflow;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.stream.Collectors;
024
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.ConfigurationException;
027import org.apache.avalon.framework.configuration.DefaultConfiguration;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.commons.collections.ListUtils;
031
032import org.ametys.cms.clientsideelement.WorkflowStepsClientSideElement;
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.workflow.AbstractContentWorkflowComponent;
035import org.ametys.core.ui.Callable;
036import org.ametys.plugins.forms.Form;
037import org.ametys.plugins.forms.FormsException;
038import org.ametys.plugins.forms.data.FieldValue;
039import org.ametys.plugins.forms.data.UserEntry;
040import org.ametys.plugins.forms.jcr.FormPropertiesManager;
041import org.ametys.plugins.forms.table.FormTableManager;
042import org.ametys.plugins.workflow.AbstractWorkflowComponent;
043import org.ametys.plugins.workflow.store.JdbcWorkflowStore;
044import org.ametys.runtime.i18n.I18nizableText;
045
046import com.opensymphony.workflow.Workflow;
047import com.opensymphony.workflow.loader.ActionDescriptor;
048import com.opensymphony.workflow.loader.WorkflowDescriptor;
049import com.opensymphony.workflow.spi.Step;
050
051/**
052 * This class creates multiple toggle buttons each representing a workflow step on form entries
053 */
054public class FormEntriesWorkflowStepsClientSideElement extends WorkflowStepsClientSideElement
055{
056    /** The manager for the forms JCR representation */
057    protected FormPropertiesManager _formPropertiesManager;
058    /** The manager for the forms SQL tables */
059    protected FormTableManager _formTableManager;
060
061    @Override
062    public void service(ServiceManager serviceManager) throws ServiceException
063    {
064        super.service(serviceManager);
065        _formPropertiesManager = (FormPropertiesManager) serviceManager.lookup(FormPropertiesManager.ROLE);
066        _formTableManager = (FormTableManager) serviceManager.lookup(FormTableManager.ROLE);
067    }
068
069    @Override
070    protected String _getDefaultMenuClassName()
071    {
072        return "Ametys.plugins.forms.workflow.FormEntriesWorkflowMenu";
073    }
074    
075    @Override
076    protected String _getDefaultActionClassName()
077    {
078        return "Ametys.plugins.forms.workflow.FormEntriesWorkflowAction.doAction";
079    }
080
081    @Override
082    protected void _configureWorkflowStep(WorkflowDescriptor workflowDescriptor, Integer stepId, Map<String, Object> stepParameters, Configuration stepConfiguration, List<Integer> allowedActionIds) throws ConfigurationException
083    {
084        super._configureWorkflowStep(workflowDescriptor, stepId, stepParameters, stepConfiguration, allowedActionIds);
085        
086        List<Integer> sendMailIds = new ArrayList<>();
087        
088        List<Integer> currentStepActions = workflowDescriptor.getStep(stepId)
089                .getActions()
090                .stream()
091                .mapToInt(action -> ((ActionDescriptor) action).getId())
092                .boxed()
093                .collect(Collectors.toList());
094        List<Integer> allowedStepActions = ListUtils.intersection(currentStepActions, allowedActionIds);
095
096        if (stepConfiguration != null)
097        {
098            Configuration stepWorkflowSendMail = stepConfiguration.getChild("send-mail", false);
099            if (stepWorkflowSendMail != null)
100            {
101                sendMailIds.addAll(_configureWorkflowStepActions(workflowDescriptor.getName(), stepId, currentStepActions, allowedStepActions, stepWorkflowSendMail));
102            }
103            else
104            {
105                sendMailIds.addAll(allowedStepActions);
106            }
107        }
108        else
109        {
110            sendMailIds.addAll(allowedStepActions);
111        }
112        
113        stepParameters.put("workflow-send-mail-ids", sendMailIds);
114    }
115
116    
117    
118    @Override
119    protected Script _configureScript(Configuration configuration) throws ConfigurationException
120    {
121        List<ScriptFile> scriptsImports = _configureImports(configuration.getChild("scripts"));
122        List<ScriptFile> cssImports = _configureImports(configuration.getChild("css"));
123        
124        Script script = new Script(this.getId(), "", scriptsImports, cssImports, new HashMap<>());
125        List<ScriptFile> scriptFiles = new ArrayList<>();
126        scriptFiles.add(new ScriptFile("/plugins/forms/resources/js/Ametys/plugins/forms/workflow/FormEntriesWorkflowMenu.js"));
127        scriptFiles.add(new ScriptFile("/plugins/forms/resources/js/Ametys/plugins/forms/workflow/FormEntriesWorkflowAction.js"));
128        script.getScriptFiles().addAll(scriptFiles);
129        
130        return script;
131    }
132
133    @Override
134    protected String _getSelectionTargetId()
135    {
136        return "^(content-form)$";
137    }
138    
139    @Override
140    protected String _getDefaultPluginName()
141    {
142        return "forms";
143    }
144    
145    @SuppressWarnings("unchecked")
146    @Override
147    protected void _additionalMenuItemConfiguration(Configuration itemConf, DefaultConfiguration classConf, int actionId, Map<String, Object> parameters)
148    {
149        super._additionalMenuItemConfiguration(itemConf, classConf, actionId, parameters);
150        
151        // Multiselection enabled ?
152        DefaultConfiguration multiselectConf = new DefaultConfiguration("selection-enable-multiselection");
153        multiselectConf.setValue(itemConf.getChild("menu-" + actionId + "-enable-multiselection").getValueAsBoolean(true));
154        classConf.addChild(multiselectConf);
155
156        // Send mail
157        DefaultConfiguration sendMailConf = new DefaultConfiguration("send-mail");
158        sendMailConf.setValue(itemConf.getChild("menu-" + actionId + "-send-mail").getValueAsBoolean(((List<Integer>) parameters.get("workflow-send-mail-ids")).contains(actionId)));
159        classConf.addChild(sendMailConf);
160        
161        // Dialog title for the mails
162        if (itemConf.getChild("menu-" + actionId + "-dialog-title", false) != null)
163        {
164            DefaultConfiguration dialogTitleConf = new DefaultConfiguration("dialog-title");
165            dialogTitleConf.setAttribute("i18n", itemConf.getChild("menu-" + actionId + "-dialog-title").getAttribute("i18n", "true"));
166            dialogTitleConf.setValue(itemConf.getChild("menu-" + actionId + "-dialog-title").getValue(null));
167            classConf.addChild(dialogTitleConf);
168        }
169        
170        // i18n key for the subject  
171        if (itemConf.getChild("menu-" + actionId + "-subject-key", false) != null)
172        {
173            DefaultConfiguration subjectKeyConf = new DefaultConfiguration("subject-key");
174            subjectKeyConf.setAttribute("i18n", itemConf.getChild("menu-" + actionId + "-subject-key").getAttribute("i18n", "true"));
175            subjectKeyConf.setValue(itemConf.getChild("menu-" + actionId + "-subject-key").getValue(null));
176            classConf.addChild(subjectKeyConf);
177        }
178        
179        // i18n key for the body
180        if (itemConf.getChild("menu-" + actionId + "-body-key", false) != null)
181        {
182            DefaultConfiguration bodyKeyConf = new DefaultConfiguration("body-key");
183            bodyKeyConf.setAttribute("i18n", itemConf.getChild("menu-" + actionId + "-body-key").getAttribute("i18n", "true"));
184            bodyKeyConf.setValue(itemConf.getChild("menu-" + actionId + "-body-key").getValue(null));
185            classConf.addChild(bodyKeyConf);
186        }
187            
188    }
189
190    @Override
191    protected void _configureDefaultDescriptions(Map<String, Object> parameters)
192    {
193        // One entry selected
194        if (!parameters.containsKey("entryselected-description"))
195        {
196            parameters.put("entryselected-description", new I18nizableText("plugin.forms", "ENTRY_WORKFLOW_DESCRIPTION"));
197        }
198        
199        // Several entries selected
200        if (!parameters.containsKey("several-entryselected-description"))
201        {
202            parameters.put("several-entryselected-description", new I18nizableText("plugin.forms", "ENTRIES_WORKFLOW_DESCRIPTION"));
203        }
204        
205        // Empty selection
206        if (!parameters.containsKey("selection-description-empty"))
207        {
208            parameters.put("selection-description-empty", new I18nizableText("plugin.forms", "ENTRY_WORKFLOW_NOSELECTION_DESCRIPTION"));
209        }
210        
211        // No match selection
212        if (!parameters.containsKey("selection-description-nomatch"))
213        {
214            parameters.put("selection-description-nomatch", new I18nizableText("plugin.forms", "ENTRY_WORKFLOW_NOMATCH_DESCRIPTION"));
215        }
216        
217        // Multiselection forbidden
218        if (!parameters.containsKey("selection-description-multiselectionforbidden"))
219        {
220            parameters.put("selection-description-multiselectionforbidden", new I18nizableText("plugin.forms", "ENTRY_WORKFLOW_NOMULTISELECT_DESCRIPTION"));
221        }
222        
223        // No available action
224        if (!parameters.containsKey("noaction-available-description"))
225        {
226            parameters.put("noaction-available-description", new I18nizableText("plugin.forms", "ENTRY_WORKFLOW_NOACTIONAVAILABLE_DESCRIPTION"));
227        }
228        
229        // Refreshing
230        if (!parameters.containsKey("refreshing-description"))
231        {
232            parameters.put("refreshing-description", new I18nizableText("plugin.forms", "ENTRY_WORKFLOW_REFRESH_DESCRIPTION"));
233        }
234    }
235
236    /**
237     * Get the workflow state of contents
238     * @param entryIds The ids of the selected entries
239     * @param formId The id of the form
240     * @param scriptId The id of the script
241     * @return The workflow state as a map
242     */
243    @Callable
244    @SuppressWarnings("unchecked")
245    public Map<String, Object> getWorkflowState(List<Integer> entryIds, String formId, String scriptId)
246    {
247        List<Map<String, Object>> entries = new ArrayList<> ();
248        
249        List<UserEntry> allEntries = new ArrayList<> ();
250        Workflow workflow = _workflowProvider.getExternalWorkflow(JdbcWorkflowStore.ROLE);
251        
252        try
253        {
254            Form form = _formPropertiesManager.getForm(formId);
255            Map<String, FieldValue> columns = _formTableManager.getColumns(form);
256           
257            allEntries = _formTableManager.getSubmissions(form, columns, 0, Integer.MAX_VALUE, entryIds);
258
259            for (UserEntry entry : allEntries)
260            {
261                for (Script script : _scripts)
262                {
263                    if (script.getId().equals(scriptId))
264                    {
265                        Map<String, Object> entryMap = new HashMap<> ();
266                        
267                        Integer workflowId = entry.getWorkflowId();
268                        if (workflowId != null)
269                        {
270                            List<Step> steps = workflow.getCurrentSteps(workflowId);
271                            List<Integer> stepIds = new ArrayList<>();
272                            for (Step step : steps)
273                            {
274                                stepIds.add(step.getStepId());
275                            }
276                            
277                            Integer workflowStepId = (Integer) script.getParameters().get("workflow-step");
278                            if (stepIds.contains(workflowStepId))
279                            {
280                                Map<String, Object> transientVars = new HashMap<>();
281                                
282                                // Used to check the rights on the form's content
283                                Content formContent = _formPropertiesManager.getFormContent(form.getId());
284                                transientVars.put(AbstractContentWorkflowComponent.CONTENT_KEY, formContent);
285                                transientVars.put(AbstractWorkflowComponent.FAIL_CONDITIONS_KEY, new ArrayList<String> ());
286                                
287                                int[] availableActions = workflow.getAvailableActions(workflowId, transientVars);
288                                Arrays.sort(availableActions);
289                                
290                                List<Integer> activeActions = new ArrayList<>();
291                                for (int actionId : (List<Integer>) script.getParameters().get("workflow-actions-ids"))
292                                {
293                                    if (Arrays.binarySearch(availableActions, actionId) >= 0)
294                                    {
295                                        activeActions.add(actionId);
296                                    }
297                                }
298                                
299                                entryMap.put("id", entry.getId());
300                                entryMap.put("actions", activeActions);
301                                entries.add(entryMap);
302                            }
303                        }
304                    }
305                }
306            }
307        }
308        catch (FormsException e)
309        {
310            getLogger().error("Error getting forms for a content.", e);
311        }
312        
313        Map<String, Object> results = new HashMap<> ();
314        results.put("entries", entries);
315        return results;
316    }
317}