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}