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