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.clientsideelement;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.component.ComponentException;
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;
031import org.slf4j.LoggerFactory;
032
033import org.ametys.cms.content.ContentHelper;
034import org.ametys.core.ui.ClientSideElement;
035import org.ametys.core.ui.MenuClientSideElement;
036import org.ametys.core.ui.StaticClientSideElement;
037import org.ametys.core.ui.StaticFileImportsClientSideElement;
038import org.ametys.core.user.UserManager;
039import org.ametys.plugins.core.ui.util.ConfigurationHelper;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.workflow.support.WorkflowHelper;
042import org.ametys.plugins.workflow.support.WorkflowProvider;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
045
046import com.opensymphony.workflow.loader.ActionDescriptor;
047import com.opensymphony.workflow.loader.StepDescriptor;
048import com.opensymphony.workflow.loader.WorkflowDescriptor;
049
050/**
051 * This element creates multiple toggle buttons representing a workflow.
052 * A menu represent workflow actions.
053 * 
054 * The awaited configuration is:
055 *          <workflow name="WORKFLOWNAME">
056 *              <action>
057 *                  <menu-1-label>I18N_KEY</menu-1-label>
058 *              </action>
059 * 
060 *              <workflow-actions mode="exclude">
061 *                  <action>22</action>
062 *                  <action>222</action>
063 *              </workflow-actions>
064 * 
065 *              <workflow-steps mode="include">
066 *                  <step>1</step>
067 *                  <step>2</step>
068 *              </workflow-steps>
069 * 
070 *              <steps>
071 *                  <step id="1">
072 *                      <workflow-actions mode="include">
073 *                          <action>2</action>
074 *                      </workflow-actions>
075 * 
076 *                      <comments mode="include">
077 *                          <action>3</action>
078 *                      </comments>
079 * 
080 *                      <action>
081 *                          <menu-1-label />
082 *                      </action>
083 *                  </step>
084 * 
085 *              </steps>
086 *          </workflow>
087 * 
088 * Where WORKFLOWNAME is the name of the workflow such as 'content'. MANDATORY
089 * Where <action> is optional to override the default configuration of every step button and step menu
090 *  The attribute name="..." is optional to specify the JS class used.
091 * Where <workflow-actions< is optional to restrict the available actions in the menu. Default value is <workflow-actions mode="exclude"/>
092 *  The attribute mode="include" indicates that the list of actions ids will replace the one automatically determined (even if it only can by a sublist). Listing ids here ensure that no new actions will appear in this menu if the workflow is modified.
093 *  The attribute mode="exclude" (default value) indicates that the list of actions ids will be removed from the one automatically determined (it only can by a sublist of it). Listing ids here ensure that when the workflow has a new action it will be added here.
094 * Where <steps> is optional and allows you to configure each step specifically.
095 *  The <step> id attribute is MANDATORY
096 *  The <workflow-actions> is identical to the one at the root of <workflow> but only for this specific step
097 *  Where <comments< is identical to <workflow-actions< but to display to the user a dialog box to enter comments. The workflow has to support comments on that action. To avoid comments, set <comments mode="include"/>
098 *  The <action> is identical to the one at the root of <workflow> but only for this specific step
099 * 
100 * 
101 * Here is a declaration sample in a plugin.xml file
102 * 
103 *          <extension id="org.ametys.cms.workflow.WorkflowSteps"
104 *                     point="org.ametys.core.ui.RibbonControlsManager"
105 *                     class="org.ametys.cms.clientsideelement.WorkflowStepsClientSideElement">
106 *              <workflow name="content">
107 *                  <steps>
108 *                      <step id="1"> <!-- Draft -->
109 *                          <workflow-actions mode="exclude">
110 *                              <action>2</action><!-- Edit -->
111 *                          </workflow-actions>
112 * 
113 *                          <comments mode="include">
114 *                              <action>3</action><!-- propose -->
115 *                          </comments>
116 *                      </step>
117 *                  </steps>
118 *              </workflow>
119 *          </extension>
120 *
121 * Default configuration is proposed upon the StaticFileImportsClientSideElement.
122 * - default js class is "Ametys.plugins.cms.content.controller.WorkflowMenu"
123 * - default js files are loaded : /plugins/cms/resources/js/Ametys/plugins/cms/content/controller/WorkflowMenu.js and /plugins/cms/resources/js/Ametys/plugins/cms/content/actions/WorkflowAction.js
124 * - default js parameters are
125 *      - label (to the i18nkey application:WORFKLOW_STEP_NAME)
126 *      - description (to the i18nkey application:WORFKLOW_STEP_NAME_DESCRIPTION)
127 *      - footerDescription (to the i18nkey application:WORFKLOW_STEP_NAME_FOOTER)
128 *      - selection-target-id (to ^content$)
129 *      - icon-small, icon-medium, icon-large (to /plugins/cms/resources_workflow/WORFKLOW_STEP_NAME-small.png for an application i18nkey, or /plugins/PLUGINNAME/resources/img/workflow/WORFKLOW_STEP_NAME-small.png for a plugin i18nkey)
130 *      - workflow-step (tp the value configured avove)
131 *      - workflow-name (to the value configured above)
132 *      - selection-enable-multiselection (true)
133 *      - selection-description-empty : i18nkey plugin.cms:CONTENT_WORKFLOW_NOSELECTION_DESCRIPTION
134 *      - selection-description-nomatch : i18nkey plugin.cms:CONTENT_WORKFLOW_NOMATCH_DESCRIPTION
135 *      - selection-description-multiselectionforbidden : i18nkey plugin.cms:CONTENT_WORKFLOW_NOMULTISELECT_DESCRIPTION
136 * - additionnal js parameters are
137 *      - noaction-available-description : i18nkey for description when no actions are available, plugin.cms:CONTENT_WORKFLOW_NOACTIONAVAILABLE_DESCRIPTION
138 *      - refreshing-description : i18nkey for description when refreshing, plugin.cms:CONTENT_WORKFLOW_REFRESH_DESCRIPTION
139 *      - contentselected-start-description : i18nkey for description of the current content selection (start of the description) : plugin.cms:CONTENT_WORKFLOW_DESCRIPTION_BEGIN
140 *      - contentselected-end-description : i18nkey for description of the current content selection (end of the description) : plugin.cms:CONTENT_WORKFLOW_DESCRIPTION_END
141 *      - editing-description : i18nkey for description when the content is beeing edited : plugin.cms:CONTENT_WORKFLOW_EDITING_DESCRIPTION
142 * So there is no need to define a <class> by default or import js files.
143 * Additionnal i18nkeys used are plugin.cms:CONTENT_WORKFLOW_DESCRIPTION and plugin.cms:CONTENT_WORKFLOW_LOCKED_DESCRIPTION
144 */
145public class WorkflowStepsClientSideElement extends StaticFileImportsClientSideElement implements MenuClientSideElement
146{
147    /** The client side element component manager for menu items. */
148    protected ThreadSafeComponentManager<ClientSideElement> _menuItemManager;
149    /** The service manager */
150    protected ServiceManager _smanager;
151    /** Runtime users manager */
152    protected UserManager _userManager;
153    /** Workflow provider */
154    protected WorkflowProvider _workflowProvider;
155    /** Workflow helper */
156    protected WorkflowHelper _workflowHelper;
157    /** Ametys object resolver */
158    protected AmetysObjectResolver _resolver;
159    /** The content helper */
160    protected ContentHelper _contentHelper;
161    /** The referenced client side element */
162    protected List<ClientSideElement> _referencedClientSideElement;
163    /** The menu items */
164    protected Map<String, List<ClientSideElement>> _menuItems;
165    /** The unresolved items */
166    protected Map<String, List<String>> _unresolvedMenuItems;
167    /** The scripts */
168    protected List<Script> _scripts;
169    
170    @Override
171    public void service(ServiceManager manager) throws ServiceException
172    {
173        super.service(manager);
174        _smanager = manager;
175        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
176        _workflowProvider = (WorkflowProvider) manager.lookup(WorkflowProvider.ROLE);
177        _workflowHelper = (WorkflowHelper) manager.lookup(WorkflowHelper.ROLE);
178        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
179        _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE);
180    }
181    
182    @Override
183    public void configure(Configuration configuration) throws ConfigurationException
184    {
185        _menuItemManager = new ThreadSafeComponentManager<>();
186        _menuItemManager.setLogger(LoggerFactory.getLogger("cms.plugin.threadsafecomponent"));
187        _menuItemManager.service(_smanager);
188
189        // Menu items
190        _referencedClientSideElement = new ArrayList<>();
191        _unresolvedMenuItems = new HashMap<>();
192        _menuItems = new HashMap<>();
193        
194        super.configure(configuration);
195        
196        _configureWorkflow(configuration);
197    }
198    
199    /**
200     * Read the workflow configuration, to set up the scripts.
201     * @param configuration The configuration
202     * @throws ConfigurationException If an error occurs
203     */
204    protected void _configureWorkflow(Configuration configuration) throws ConfigurationException
205    {
206        Configuration workflowConfiguration = configuration.getChild("workflow", false);
207        if (workflowConfiguration != null)
208        {
209            String workflowName = workflowConfiguration.getAttribute("name");
210            
211            Configuration actionsConfiguration = workflowConfiguration.getChild("workflow-actions", true);
212            List<Integer> actions = new ArrayList<>();
213            boolean actionsIncludeMode = "include".equals(actionsConfiguration.getAttribute("mode", "exclude"));
214            for (Configuration action : actionsConfiguration.getChildren("action"))
215            {
216                actions.add(action.getValueAsInteger());
217            }
218            
219            Configuration stepsConfiguration = workflowConfiguration.getChild("workflow-steps", true);
220            List<Integer> steps = new ArrayList<>();
221            boolean stepsIncludeMode = "include".equals(stepsConfiguration.getAttribute("mode", "exclude"));
222            for (Configuration action : stepsConfiguration.getChildren("step"))
223            {
224                steps.add(action.getValueAsInteger());
225            }
226            
227            Map<Integer, Configuration> specificStepConfig = new HashMap<>();
228            for (Configuration stepConfiguration : workflowConfiguration.getChild("steps", true).getChildren("step"))
229            {
230                Integer id = stepConfiguration.getAttributeAsInteger("id");
231                specificStepConfig.put(id, stepConfiguration);
232            }
233            
234            WorkflowDescriptor workflowDescriptor = _workflowHelper.getWorkflowDescriptor(workflowName);
235            
236            if (workflowDescriptor == null)
237            {
238                throw new ConfigurationException("Unknown workflow name '" + workflowName + "' specified in configuration", workflowConfiguration);
239            }
240            
241            List<Integer> allowedStepIds = _getAllowedSteps(workflowDescriptor, stepsIncludeMode, steps);
242            List<Integer> allowedActionIds = _getAllowedActions(workflowDescriptor, allowedStepIds, actionsIncludeMode, actions);
243            
244            _configureScripts(workflowConfiguration, allowedStepIds, specificStepConfig, allowedActionIds, workflowDescriptor);
245        }
246    }
247
248    /**
249     * Get the steps allowed for the current workflow
250     * @param workflowDescriptor The workflow descriptor
251     * @param stepsIncludeMode True if the step listed should be the only steps included, false if they should be excluded from all available workflow's steps.
252     * @param configuredSteps A list of step ids.
253     * @return The steps allowed.
254     */
255    protected List<Integer> _getAllowedSteps(WorkflowDescriptor workflowDescriptor, boolean stepsIncludeMode, List<Integer> configuredSteps)
256    {
257        List<Integer> workflowStepIds = workflowDescriptor.getSteps().stream()
258                                                                     .mapToInt(step -> ((StepDescriptor) step).getId())
259                                                                     .boxed()
260                                                                     .collect(Collectors.toList());
261
262        if (stepsIncludeMode)
263        {
264            List<Integer> stepsToRemove = new ArrayList<>();
265            for (Integer stepId : configuredSteps)
266            {
267                if (!workflowStepIds.contains(stepId))
268                {
269                    getLogger().warn("Unknown step id '" + stepId + "' for workflow '" + workflowDescriptor.getName() + "', in workflow-steps configuration of extension '" + this.getId() + "', it will be ignored");
270                    stepsToRemove.add(stepId);
271                }
272            }
273            configuredSteps.removeAll(stepsToRemove);
274            
275            return configuredSteps;
276        }
277        else
278        {
279            List<Integer> unknownStepIds = new ArrayList<>(configuredSteps);
280            unknownStepIds.removeAll(workflowStepIds);
281            for (Integer stepId : unknownStepIds)
282            {
283                getLogger().warn("Unknown step id '" + stepId + "' for workflow '" + workflowDescriptor.getName() + "', in workflow-steps configuration of extension '" + this.getId() + "', it will be ignored");
284            }
285            
286            workflowStepIds.removeAll(configuredSteps);
287            return workflowStepIds;
288        }
289    }
290
291    /**
292     * Get the actions allowed for the current workflow and only for the specified steps.
293     * @param workflowDescriptor The workflow descriptor
294     * @param stepIds The list of steps specified
295     * @param actionsIncludeMode True if the action listed should be the only actions included, false if they should be excluded from all available workflow's actions.
296     * @param configuredActions A list of actions ids.
297     * @return The actions allowed.
298     */
299    @SuppressWarnings("unchecked")
300    protected List<Integer> _getAllowedActions(WorkflowDescriptor workflowDescriptor, List<Integer> stepIds, boolean actionsIncludeMode, List<Integer> configuredActions)
301    {
302        if (actionsIncludeMode)
303        {
304            return configuredActions;
305        }
306        else
307        {
308            // get the list of action ids for the allowed steps.
309            List<Integer> workflowActionIds = stepIds.stream()
310                                                     .map(stepId -> workflowDescriptor.getStep(stepId).getActions())
311                                                     .flatMap(actionsLists -> ((List<ActionDescriptor>) actionsLists).stream())
312                                                     .distinct()
313                                                     .mapToInt(ActionDescriptor::getId)
314                                                     .boxed()
315                                                     .collect(Collectors.toList());
316            
317            workflowActionIds.removeAll(configuredActions);
318            return workflowActionIds;
319        }
320    }
321    
322    /**
323     * Configure the list of Scripts, for each step available to the workflow.
324     * @param workflowConfiguration The configuration
325     * @param stepIds The list of steps
326     * @param stepsConfiguration The parameters for each step
327     * @param allowedActionIds The list of globally allowed actions for this workflow
328     * @param workflowDescriptor The descriptor for the current workflow
329     * @throws ConfigurationException If an error occurs
330     */
331    protected void _configureScripts(Configuration workflowConfiguration, List<Integer> stepIds, Map<Integer, Configuration> stepsConfiguration, List<Integer> allowedActionIds, WorkflowDescriptor workflowDescriptor) throws ConfigurationException
332    {
333        _scripts = new ArrayList<>();
334        
335        Map<String, Object> defaultParameters = null;
336        String defaultClassName = "";
337        
338        Configuration actionConfiguration = workflowConfiguration.getChild("action", true);
339        defaultClassName = actionConfiguration.getAttribute("name", _getDefaultMenuClassName());
340        defaultParameters = ConfigurationHelper.parsePluginParameters(actionConfiguration, getPluginName(), getLogger());
341        
342        for (Integer stepId : stepIds)
343        {
344            Map<String, Object> parameters;
345            String className;
346            
347            Configuration stepConfiguration = null;
348            
349            if (stepsConfiguration.containsKey(stepId))
350            {
351                stepConfiguration = stepsConfiguration.get(stepId);
352                parameters = ConfigurationHelper.parsePluginParameters(stepConfiguration, getPluginName(), getLogger());
353                className = stepConfiguration.getAttribute("name", defaultClassName);
354            }
355            else
356            {
357                parameters = new HashMap<>();
358                parameters.putAll(defaultParameters);
359                className = defaultClassName;
360            }
361
362            _configureWorkflowStep(workflowDescriptor, stepId, parameters, stepConfiguration, allowedActionIds);
363            _configureParameters(parameters);
364            
365            List<ScriptFile> scriptFiles = new ArrayList<>(_script.getScriptFiles());
366            List<ScriptFile> cssFiles = new ArrayList<>(_script.getCSSFiles());
367            Script script = new Script(this.getId() + "-" + stepId, this.getId(), className, scriptFiles, cssFiles, parameters);
368            
369            _scripts.add(script);
370            
371            if (stepConfiguration != null && stepConfiguration.getChild("action", false) != null)
372            {
373                _configureMenuItems(script, stepConfiguration.getChild("action", false));
374            }
375            else
376            {
377                _configureMenuItems(script, actionConfiguration);
378            }
379        }
380    }
381    
382    /**
383     * Get the default class name for workflow menu
384     * @return the default class name
385     */
386    protected String _getDefaultMenuClassName()
387    {
388        return "Ametys.plugins.cms.content.controller.WorkflowMenu";
389    }
390    
391    /**
392     * Get the default class name for workflow action
393     * @return the default class name
394     */
395    protected String _getDefaultActionClassName()
396    {
397        return "Ametys.plugins.cms.content.actions.WorkflowAction.doAction";
398    }
399
400    /**
401     * Configure the parameters specific to the workflow, for the given step
402     * @param workflowDescriptor The descriptor of the workflow
403     * @param stepId The step
404     * @param stepParameters The parameters of the step
405     * @param stepConfiguration The step configuration
406     * @param allowedActionIds The list of globally allowed actions
407     * @throws ConfigurationException If an error occurs
408     */
409    protected void _configureWorkflowStep(WorkflowDescriptor workflowDescriptor, Integer stepId, Map<String, Object> stepParameters, Configuration stepConfiguration, List<Integer> allowedActionIds) throws ConfigurationException
410    {
411        stepParameters.put("workflow-name", workflowDescriptor.getName());
412        stepParameters.put("workflow-step", stepId);
413        stepParameters.put("workflow-step-name", _workflowHelper.getStepName(workflowDescriptor.getName(), stepId));
414        
415        List<Integer> currentStepActions = workflowDescriptor.getStep(stepId)
416                                                             .getActions()
417                                                             .stream()
418                                                             .mapToInt(action -> ((ActionDescriptor) action).getId())
419                                                             .boxed()
420                                                             .collect(Collectors.toList());
421        List<Integer> allowedStepActions = ListUtils.intersection(currentStepActions, allowedActionIds);
422
423        List<Integer> stepActions = new ArrayList<>();
424        List<Integer> commentIds = new ArrayList<>();
425        
426        if (stepConfiguration != null)
427        {
428            Configuration stepWorkflowActions = stepConfiguration.getChild("workflow-actions", false);
429            if (stepWorkflowActions != null)
430            {
431                stepActions.addAll(_configureWorkflowStepActions(workflowDescriptor.getName(), stepId, currentStepActions, allowedStepActions, stepWorkflowActions));
432            }
433            else
434            {
435                stepActions.addAll(allowedStepActions);
436            }
437            
438            Configuration stepWorkflowComments = stepConfiguration.getChild("comments", false);
439            if (stepWorkflowComments != null)
440            {
441                commentIds.addAll(_configureWorkflowStepActions(workflowDescriptor.getName(), stepId, currentStepActions, allowedStepActions, stepWorkflowComments));
442            }
443            else
444            {
445                commentIds.addAll(allowedStepActions);
446            }
447        }
448        else
449        {
450            stepActions.addAll(allowedStepActions);
451            commentIds.addAll(allowedStepActions);
452        }
453        
454        stepParameters.put("workflow-actions-ids", stepActions);
455        stepParameters.put("workflow-comments-ids", commentIds);
456    }
457
458    /**
459     * Get the list of actions available for a step, from the configuration
460     * @param workflowName The name of the current workflow
461     * @param stepId The step
462     * @param currentStepActions All the actions available for this step
463     * @param allowedStepActions The actions allowed by the configuration for this step
464     * @param stepActionsConfiguration The configuration for the step actions
465     * @return The list of actions for the step
466     * @throws ConfigurationException If an error occurs
467     */
468    protected List<Integer> _configureWorkflowStepActions(String workflowName, Integer stepId, List<Integer> currentStepActions, List<Integer> allowedStepActions, Configuration stepActionsConfiguration) throws ConfigurationException
469    {
470        List<Integer> stepActions = new ArrayList<>();
471        
472        boolean actionsIncludeMode = "include".equals(stepActionsConfiguration.getAttribute("mode", "exclude"));
473        if (actionsIncludeMode)
474        {
475            // include mode, add all listed action if they are available for the current step.
476            for (Configuration workflowAction : stepActionsConfiguration.getChildren("action"))
477            {
478                int action = workflowAction.getValueAsInteger();
479                if (currentStepActions.contains(action))
480                {
481                    stepActions.add(action);
482                }
483                else
484                {
485                    getLogger().warn("Unknown action id '" + action + "' for step '" + stepId + "', for workflow '"
486                            + workflowName + "', in steps configuration of extension '" + this.getId() + "', it will be ignored");
487                }
488            }
489        }
490        else
491        {
492            // exclude mode, add all allowed step actions but the ones listed
493            stepActions.addAll(allowedStepActions);
494            for (Configuration workflowAction : stepActionsConfiguration.getChildren("action"))
495            {
496                Integer action = workflowAction.getValueAsInteger();
497                if (allowedStepActions.contains(action))
498                {
499                    stepActions.remove(action);
500                }
501                else
502                {
503                    getLogger().warn("Unknown action id '" + action + "' for step '" + stepId + "', for workflow '"
504                            + workflowName + "', in steps configuration of extension '" + this.getId() + "', it will be ignored");
505                }
506            }
507        }
508        
509        return stepActions;
510    }
511
512    @Override
513    public List<ClientSideElement> getReferencedClientSideElements(Map<String, Object> contextParameters)
514    {
515        if (hasRight(getRights(contextParameters)))
516        {
517            return _referencedClientSideElement;
518        }
519        else
520        {
521            return List.of();
522        }
523    }
524    
525    @Override
526    public List<Script> getScripts(boolean ignoreRights, Map<String, Object> contextParameters)
527    {
528        try
529        {
530            _resolveMenuItems();
531        }
532        catch (Exception e)
533        {
534            throw new IllegalStateException("Unable to lookup client side element local components", e);
535        }
536
537        for (Script script : _scripts)
538        {
539            Map<String, Object> parameters = script.getParameters();
540            
541            if (_menuItems.containsKey(script.getId()))
542            {
543                List<String> menuItems = new ArrayList<>();
544                for (ClientSideElement element : _menuItems.get(script.getId()))
545                {
546                    menuItems.add(element.getId());
547                }
548                parameters.put("menu-items", menuItems);
549            }
550        }
551        
552        return _scripts;
553    }
554    
555    @Override
556    protected Script _configureScript(Configuration configuration) throws ConfigurationException
557    {
558        Script script = super._configureScript(configuration);
559        List<ScriptFile> scriptFiles = new ArrayList<>();
560        scriptFiles.add(new ScriptFile("/plugins/cms/resources/js/Ametys/plugins/cms/content/controller/WorkflowMenu.js"));
561        scriptFiles.add(new ScriptFile("/plugins/cms/resources/js/Ametys/plugins/cms/content/actions/WorkflowAction.js"));
562        script.getScriptFiles().addAll(scriptFiles);
563
564        return script;
565    }
566
567    /**
568     * Configure parameters recursively
569     * @param parameters The parameters map to fill
570     * @throws ConfigurationException The configuration is incorrect
571     */
572    protected void _configureParameters(Map<String, Object> parameters) throws ConfigurationException
573    {
574        String workflowStepName = (String) parameters.get("workflow-step-name");
575        
576        // Default label
577        if (parameters.get("label") == null)
578        {
579            parameters.put("label", new I18nizableText("application", workflowStepName));
580        }
581        
582        // Default description
583        if (parameters.get("description") == null)
584        {
585            parameters.put("description", new I18nizableText("application", workflowStepName + "_DESCRIPTION"));
586        }
587        
588        // Default footer description
589        if (parameters.get("footerDescription") == null)
590        {
591            parameters.put("footerDescription", new I18nizableText("application", workflowStepName + "_FOOTER"));
592        }
593        
594        // Selection target type
595        if (parameters.get("selection-target-id") == null)
596        {
597            parameters.put("selection-target-id", _getSelectionTargetId());
598            // DO NOT add selection-target-parameter with workflow name : the button is show/hide according the workflow name on client side
599        }
600        
601        // Default icons
602        String[] icons = new String[]{"-small", "-medium", "-large"};
603        for (String icon : icons)
604        {
605            if (parameters.get("icon" + icon) == null)
606            {
607                I18nizableText workflowStepNameText = new I18nizableText("application", workflowStepName);
608                if ("application".equals(workflowStepNameText.getCatalogue()))
609                {
610                    parameters.put("icon" + icon, new I18nizableText("/plugins/" + _getDefaultPluginName() + "/resources_workflow/" + workflowStepNameText.getKey() + icon + ".png"));
611                }
612                else
613                {
614                    String pluginName = workflowStepNameText.getCatalogue().substring("plugin.".length());
615                    parameters.put("icon" + icon, new I18nizableText("/plugins/" + pluginName + "/resources/img/workflow/" + workflowStepNameText.getKey() + icon + ".png"));
616                }
617            }
618        }
619        
620        // Multiselection enabled
621        if (!parameters.containsKey("selection-enable-multiselection"))
622        {
623            parameters.put("selection-enable-multiselection", true);
624        }
625        
626        _configureDefaultDescriptions(parameters);
627    }
628    
629    /**
630     * Get the default plugin name
631     * @return the default plugin name
632     */
633    protected String _getDefaultPluginName()
634    {
635        return "cms";
636    }
637    
638    /**
639     * Get the selection target id (can be a Regexp)
640     * @return the selection target id
641     */
642    protected String _getSelectionTargetId()
643    {
644        return "^content$";
645    }
646    
647    /**
648     * Configure the menu items
649     * @param script The parameters map to fill
650     * @param configuration The items configuration
651     */
652    @SuppressWarnings("unchecked")
653    protected void _configureMenuItems(Script script, Configuration configuration)
654    {
655        Map<String, Object> parameters = script.getParameters();
656        for (int actionId : (List<Integer>) parameters.get("workflow-actions-ids"))
657        {
658            String id = script.getId() + ".workflow-action-" + actionId;
659            
660            String actionName = _workflowHelper.getActionName((String) parameters.get("workflow-name"), actionId);
661            
662            DefaultConfiguration conf = new DefaultConfiguration("extension");
663            conf.setAttribute("id", id);
664            
665            DefaultConfiguration classConf = new DefaultConfiguration("class");
666            classConf.setAttribute("name", "Ametys.ribbon.element.ui.ButtonController");
667            
668            // Label
669            _addActionLabelConfiguration(configuration, classConf, actionId, actionName);
670            
671            // Description
672            _addActionDescriptionConfiguration(configuration, classConf, actionId, actionName);
673            
674            // Workflow action id
675            DefaultConfiguration wActionConf = new DefaultConfiguration("workflow-action-id");
676            wActionConf.setValue(actionId);
677            classConf.addChild(wActionConf);
678            
679            // Action
680            DefaultConfiguration actionConf = new DefaultConfiguration("action");
681            actionConf.setValue(configuration.getChild("menu-" + actionId + "-action").getValue(_getDefaultActionClassName()));
682            classConf.addChild(actionConf);
683            
684            // Comment
685            _addActionCommentConfiguration(configuration, classConf, actionId, actionName, parameters);
686            
687            _additionalMenuItemConfiguration(configuration, classConf, actionId, parameters);
688            
689            conf.addChild(classConf);
690            
691            _menuItemManager.addComponent(_pluginName, null, id, StaticClientSideElement.class, conf);
692            if (!_unresolvedMenuItems.containsKey(script.getId()))
693            {
694                _unresolvedMenuItems.put(script.getId(), new ArrayList<>());
695            }
696            _unresolvedMenuItems.get(script.getId()).add(id);
697        }
698    }
699    
700    /**
701     * Add the action label configuration
702     * @param configuration the parent configuration
703     * @param classConf the class configuration
704     * @param actionId the action id
705     * @param actionName the action name
706     */
707    protected void _addActionLabelConfiguration(Configuration configuration, DefaultConfiguration classConf, int actionId, String actionName)
708    {
709        DefaultConfiguration labelConf = new DefaultConfiguration("label");
710        labelConf.setAttribute("i18n", configuration.getChild("menu-" + actionId + "-label").getAttribute("i18n", "true"));
711        labelConf.setValue(configuration.getChild("menu-" + actionId + "-label").getValue(actionName));
712        classConf.addChild(labelConf);
713    }
714    
715    /**
716     * Add the action description configuration
717     * @param configuration the parent configuration
718     * @param classConf the class configuration
719     * @param actionId the action id
720     * @param actionName the action name
721     */
722    protected void _addActionDescriptionConfiguration(Configuration configuration, DefaultConfiguration classConf, int actionId, String actionName)
723    {
724        DefaultConfiguration descConf = new DefaultConfiguration("description");
725        descConf.setAttribute("i18n", configuration.getChild("menu-" + actionId + "-description").getAttribute("i18n", "true"));
726        descConf.setValue(configuration.getChild("menu-" + actionId + "-description").getValue(actionName + "_DESCRIPTION"));
727        classConf.addChild(descConf);
728    }
729    
730    /**
731     * Add the action comment configuration
732     * @param configuration the parent configuration
733     * @param classConf the class configuration
734     * @param actionId the action id
735     * @param actionName the action name
736     * @param parameters the script parameters
737     */
738    @SuppressWarnings("unchecked")
739    protected void _addActionCommentConfiguration(Configuration configuration, DefaultConfiguration classConf, int actionId, String actionName, Map<String, Object> parameters)
740    {
741        DefaultConfiguration commentConf = new DefaultConfiguration("comment");
742        commentConf.setValue(configuration.getChild("menu-" + actionId + "-comment").getValueAsBoolean(((List<Integer>) parameters.get("workflow-comments-ids")).contains(actionId)));
743        classConf.addChild(commentConf);
744    }
745    
746    /**
747     * Additional configuration for menu items
748     * @param itemConf The item configuration
749     * @param classConf The class configuration
750     * @param actionId The workflow action id
751     * @param parameters The script parameters
752     */
753    protected void _additionalMenuItemConfiguration (Configuration itemConf, DefaultConfiguration classConf, int actionId, Map<String, Object> parameters)
754    {
755        // Nothing to do
756    }
757    
758    /**
759     * Configure the default description
760     * @param parameters The parameters
761     */
762    protected void _configureDefaultDescriptions(Map<String, Object> parameters)
763    {
764        // Empty selection
765        if (!parameters.containsKey("selection-description-empty"))
766        {
767            parameters.put("selection-description-empty", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_NOSELECTION_DESCRIPTION"));
768        }
769        
770        // No match selection
771        if (!parameters.containsKey("selection-description-nomatch"))
772        {
773            parameters.put("selection-description-nomatch", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_NOMATCH_DESCRIPTION"));
774        }
775        
776        // Multiselection forbidden
777        if (!parameters.containsKey("selection-description-multiselectionforbidden"))
778        {
779            parameters.put("selection-description-multiselectionforbidden", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_NOMULTISELECT_DESCRIPTION"));
780        }
781        
782        // No available action
783        if (!parameters.containsKey("noaction-available-description"))
784        {
785            parameters.put("noaction-available-description", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_NOACTIONAVAILABLE_DESCRIPTION"));
786        }
787        
788        // Refreshing
789        if (!parameters.containsKey("refreshing-description"))
790        {
791            parameters.put("refreshing-description", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_REFRESH_DESCRIPTION"));
792        }
793        
794        // Content selected
795        if (!parameters.containsKey("contentselected-start-description"))
796        {
797            parameters.put("contentselected-start-description", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_DESCRIPTION_BEGIN"));
798        }
799        if (!parameters.containsKey("contentselected-end-description"))
800        {
801            parameters.put("contentselected-end-description", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_DESCRIPTION_END"));
802        }
803        
804        // Editing
805        if (!parameters.containsKey("editing-description"))
806        {
807            parameters.put("editing-description", new I18nizableText("plugin.cms", "CONTENT_WORKFLOW_EDITING_DESCRIPTION"));
808        }
809    }
810    
811    private void _resolveMenuItems() throws Exception
812    {
813        if (_unresolvedMenuItems != null)
814        {
815            _menuItemManager.initialize();
816            
817            for (String scriptId : _unresolvedMenuItems.keySet())
818            {
819                for (String id : _unresolvedMenuItems.get(scriptId))
820                {
821                    ClientSideElement element;
822                    try
823                    {
824                        element = _menuItemManager.lookup(id);
825                    }
826                    catch (ComponentException e)
827                    {
828                        throw new Exception("Unable to lookup client side element role: '" + id + "'", e);
829                    }
830                    
831                    if (!_menuItems.containsKey(scriptId))
832                    {
833                        _menuItems.put(scriptId, new ArrayList<>());
834                    }
835                    _menuItems.get(scriptId).add(element);
836                    _referencedClientSideElement.add(element);
837                }
838            }
839        }
840        
841        _unresolvedMenuItems = null;
842    }
843}