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