Commit dc4be67e96df79b2f6fa5512a9d7e5793ab4e15e

Authored by Brad Shuttleworth
1 parent fad00927

Basic workflow triggers + UI.

 - note: still not fully functional.


git-svn-id: https://kt-dms.svn.sourceforge.net/svnroot/kt-dms/trunk@5544 c91229c3-7414-0410-bfa2-8a42b809f60b
lib/plugins/plugin.inc.php
... ... @@ -51,6 +51,7 @@ class KTPlugin {
51 51 var $_ai18nLang = array();
52 52 var $_aLanguage = array();
53 53 var $_aHelpLanguage = array();
  54 + var $_aWFTriggers = array();
54 55  
55 56 function KTPlugin($sFilename = null) {
56 57 $this->sFilename = $sFilename;
... ... @@ -80,6 +81,11 @@ class KTPlugin {
80 81 $sWebPath = sprintf("%s/%s", $this->sNamespace, $sWebPath);
81 82 $this->_aPages[$sWebPath] = array($sWebPath, $sPageClassName, $sFilename, $this->sNamespace);
82 83 }
  84 +
  85 + function registerWorkflowTrigger($sNamespace, $sTriggerClassName, $sFilename = null) {
  86 + $sFilename = $this->_fixFilename($sFilename);
  87 + $this->_aWFTriggers[$sNamespace] = array($sNamespace, $sTriggerClassName, $sFilename);
  88 + }
83 89  
84 90 function getPagePath($sPath) {
85 91 $sExt = ".php";
... ... @@ -191,6 +197,7 @@ class KTPlugin {
191 197 require_once(KT_LIB_DIR . "/dashboard/dashletregistry.inc.php");
192 198 require_once(KT_LIB_DIR . "/i18n/i18nregistry.inc.php");
193 199 require_once(KT_LIB_DIR . "/help/help.inc.php");
  200 + require_once(KT_LIB_DIR . "/workflow/workflowutil.inc.php");
194 201  
195 202 $oPRegistry =& KTPortletRegistry::getSingleton();
196 203 $oTRegistry =& KTTriggerRegistry::getSingleton();
... ... @@ -201,6 +208,7 @@ class KTPlugin {
201 208 $oDashletRegistry =& KTDashletRegistry::getSingleton();
202 209 $oi18nRegistry =& KTi18nRegistry::getSingleton();
203 210 $oKTHelpRegistry =& KTHelpRegistry::getSingleton();
  211 + $oWFTriggerRegistry =& KTWorkflowTriggerRegistry::getSingleton();
204 212  
205 213 foreach ($this->_aPortlets as $k => $v) {
206 214 call_user_func_array(array(&$oPRegistry, 'registerPortlet'), $v);
... ... @@ -249,6 +257,10 @@ class KTPlugin {
249 257 foreach ($this->_aHelpLanguage as $k => $v) {
250 258 call_user_func_array(array(&$oKTHelpRegistry, 'registerHelp'), $v);
251 259 }
  260 +
  261 + foreach ($this->_aWFTriggers as $k => $v) {
  262 + call_user_func_array(array(&$oWFTriggerRegistry, 'registerWorkflowTrigger'), $v);
  263 + }
252 264 }
253 265  
254 266 function setup() {
... ...
lib/workflow/workflowtrigger.inc.php
... ... @@ -50,6 +50,7 @@ class KTWorkflowTrigger {
50 50  
51 51 function loadConfig($oTriggerInstance) {
52 52 $this->oTriggerInstance = $oTriggerInstance;
  53 + $this->aConfig = $oTriggerInstance->getConfig();
53 54 }
54 55  
55 56 function isLoaded() { return (!is_null($this->oTriggerInstance)); }
... ... @@ -64,6 +65,10 @@ class KTWorkflowTrigger {
64 65 );
65 66 }
66 67  
  68 + function getName() { return $this->sFriendlyName; }
  69 + function getNamespace() { return $this->sNamespace; }
  70 + function getConfigId() { return $this->oTriggerInstance->getId(); }
  71 +
67 72 // return true for transition allowed on doc, false for transition not allowed on doc.
68 73 function allowTransition($oDocument, $oUser) {
69 74 return true; // abstract base class
... ... @@ -85,6 +90,22 @@ class KTWorkflowTrigger {
85 90 return true;
86 91 }
87 92  
  93 + // display the configuration page for this plugin
  94 + // will be called -after- loadConfig, so it can prepopulate the options.
  95 + function displayConfiguration($args) {
  96 + return _kt('No configuration has been implemented for this plugin.');
  97 + }
  98 +
  99 + // dispatched - again, after loadConfig, so it can set the config.
  100 + // throw an error to redispatch displayConfiguration, or return true to cause a db commit (probably).
  101 + function saveConfiguration() {
  102 + return true;
  103 + }
  104 +
  105 + // give a brief, friendly description of what we are and what we do.
  106 + function getConfigDescription() {
  107 + return '';
  108 + }
88 109 }
89 110  
90 111 ?>
... ...
lib/workflow/workflowtriggerinstance.inc.php
... ... @@ -59,6 +59,7 @@ class KTWorkflowTriggerInstance extends KTEntity {
59 59 function &get($iId) { return KTEntityUtil::get('KTWorkflowTriggerInstance', $iId); }
60 60 function &createFromArray($aOptions) {
61 61 $aOptions['configarraytext'] = serialize($aOptions['config']);
  62 + unset($aOptions['config']);
62 63 return KTEntityUtil::createFromArray('KTWorkflowTriggerInstance', $aOptions);
63 64 }
64 65 function &getList($sWhereClause = null) { return KTEntityUtil::getList2('KTWorkflowTriggerInstance', $sWhereClause); }
... ...
lib/workflow/workflowutil.inc.php
... ... @@ -745,6 +745,10 @@ class KTWorkflowUtil {
745 745 class KTWorkflowTriggerRegistry {
746 746 var $triggers;
747 747  
  748 + function KTWorkflowTriggerRegistry() {
  749 + $this->triggers = array();
  750 + }
  751 +
748 752 // {{{ getSingleton
749 753 function &getSingleton () {
750 754 if (!KTUtil::arrayGet($GLOBALS['_KT_PLUGIN'], 'oKTWorkflowTriggerRegistry')) {
... ... @@ -764,11 +768,9 @@ class KTWorkflowTriggerRegistry {
764 768 return PEAR::raiseError(sprintf(_kt("Unable to find workflow trigger: %s"), $sNamespace));
765 769 }
766 770  
767   - if (file_exists($aInfo['path'])) {
768   - require_once($aInfo['path']);
769   - }
770   -
771   - return new $sClassname;
  771 + require_once($aInfo['path']);
  772 +
  773 + return new $aInfo['class'];
772 774 }
773 775  
774 776 // get a keyed list of workflow triggers
... ...
plugins/ktcore/KTCorePlugin.php
... ... @@ -29,6 +29,7 @@
29 29 require_once(KT_LIB_DIR . '/plugins/pluginregistry.inc.php');
30 30 require_once(KT_LIB_DIR . '/plugins/plugin.inc.php');
31 31  
  32 +
32 33 class KTCorePlugin extends KTPlugin {
33 34 var $bAlwaysInclude = true;
34 35 var $sNamespace = "ktcore.plugin";
... ... @@ -179,6 +180,9 @@ class KTCorePlugin extends KTPlugin {
179 180 'admin/manageCleanup.php', null);
180 181 // plugins
181 182  
  183 +
  184 + // workflow triggers
  185 + $this->registerWorkflowTrigger('ktcore.workflowtriggers.permissionguard', 'PermissionGuardTrigger', 'KTWorkflowTriggers.inc.php');
182 186 }
183 187 }
184 188  
... ...
plugins/ktcore/KTDocumentActions.php
... ... @@ -996,14 +996,14 @@ class KTDocumentWorkflowAction extends KTDocumentAction {
996 996 $oUser =& User::get($_SESSION['userID']);
997 997 $res = KTWorkflowUtil::performTransitionOnDocument($oTransition, $oDocument, $oUser, $sComments);
998 998  
999   - if(!Permission::userHasDocumentReadPermission($oDocument)) {
1000   - $this->commitTransaction();
  999 + if(!Permission::userHasDocumentReadPermission($oDocument)) {
  1000 + $this->commitTransaction();
1001 1001 $_SESSION['KTInfoMessage'][] = _kt('Transition performed') . '. ' . _kt('You no longer have permission to view this document');
1002   - controllerRedirect('browse', sprintf('fFolderId=%d', $oDocument->getFolderId()));
1003   - } else {
1004   - $this->successRedirectToMain(_kt('Transition performed'),
1005   - array('fDocumentId' => $oDocument->getId()));
1006   - }
  1002 + controllerRedirect('browse', sprintf('fFolderId=%d', $oDocument->getFolderId()));
  1003 + } else {
  1004 + $this->successRedirectToMain(_kt('Transition performed'),
  1005 + array('fDocumentId' => $oDocument->getId()));
  1006 + }
1007 1007 }
1008 1008 }
1009 1009 // }}}
... ...
plugins/ktcore/admin/workflows.php
... ... @@ -1224,41 +1224,35 @@ class KTWorkflowDispatcher extends KTAdminDispatcher {
1224 1224 }
1225 1225 $aOptions['vocab'] = $vocab;
1226 1226 $edit_fields[] = new KTLookupWidget(_kt('Destination State'), _kt('Once this transition is complete, which state should the document be in?'), 'fTargetStateId', $oTransition->getTargetStateId(), $this->oPage, true, null, null, $aOptions);
1227   - $aOptions = array();
1228   - $vocab = array();
1229   - $vocab[0] = _kt('None');
1230   - foreach($aPermissions as $permission) {
1231   - $vocab[$permission->getId()] = $permission->getHumanName();
1232   - }
1233   - $aOptions['vocab'] = $vocab;
1234   - $edit_fields[] = new KTLookupWidget(_kt('Guard Permission.'), _kt('Which permission must the user have in order to follow this transition?'), 'fPermissionId', $oTransition->getGuardPermissionId(), $this->oPage, true, null, null, $aOptions);
1235   - $aOptions = array();
  1227 +
  1228 + // triggers
  1229 + $add_trigger_fields = array();
1236 1230 $vocab = array();
1237   - $vocab[0] = _kt('None');
1238   - foreach($aGroups as $group) {
1239   - $vocab[$group->getId()] = $group->getName();
1240   - }
  1231 + $vocab[0] = _kt('-- Please select a trigger --');
  1232 + $oTriggerSingleton =& KTWorkflowTriggerRegistry::getSingleton();
  1233 + $aTriggerList = $oTriggerSingleton->listWorkflowTriggers(); // only want registered triggers - no other kind exists.
  1234 + foreach ($aTriggerList as $ns => $aTriggerInfo) {
  1235 + $aInfo = $aTriggerInfo; // i am lazy.
  1236 + //var_dump($aInfo);
  1237 + $actions = array();
  1238 + if ($aInfo['guard']) {
  1239 + $actions[] = _kt('Guard');
  1240 + }
  1241 + if ($aInfo['action']) {
  1242 + $actions[] = _kt('Action');
  1243 + }
  1244 + $sActStr = implode(', ', $actions);
  1245 + $vocab[$ns] = sprintf(_kt("%s (%s)"), $aInfo['name'], $sActStr);
  1246 + }
  1247 +
1241 1248 $aOptions['vocab'] = $vocab;
1242   - $edit_fields[] = new KTLookupWidget(_kt('Guard Group.'), _kt('Which group must the user belong to in order to follow this transition?'), 'fGroupId', $oTransition->getGuardGroupId(), $this->oPage, false, null, null, $aOptions);
  1249 + $add_trigger_fields[] = new KTLookupWidget(_kt('Trigger'), _kt('Select the trigger to add to this transition. Each trigger indicates whether it controls who can see this transition, what occurs when the transition is performed, or both.'), 'fTriggerId', '0', $this->oPage, true, null, null, $aOptions);
1243 1250 $aOptions = array();
1244   - $vocab = array();
1245   - $vocab[0] = _kt('None');
1246   - foreach($aRoles as $role) {
1247   - $vocab[$role->getId()] = $role->getName();
1248   - }
1249   - $aOptions['vocab'] = $vocab;
1250   - $edit_fields[] = new KTLookupWidget(_kt('Guard Role.'), _kt('Which role must the user have in order to follow this transition?'), 'fRoleId', $oTransition->getGuardRoleId(), $this->oPage, false, null, null, $aOptions);
1251 1251  
1252   - if (!empty($aConditions)) {
1253   - $aOptions = array();
1254   - $vocab = array();
1255   - $vocab[0] = _kt('None');
1256   - foreach($aConditions as $condition) {
1257   - $vocab[$condition->getId()] = $condition->getName();
1258   - }
1259   - $aOptions['vocab'] = $vocab;
1260   - $edit_fields[] = new KTLookupWidget(_kt('Guard Condition.'), _kt('Which condition (stored search) must be satisfied before the transition can take place?'), 'fConditionId', $oTransition->getGuardConditionId(), $this->oPage, false, null, null, $aOptions);
1261   - }
  1252 +
  1253 + // attached triggers.
  1254 + $aGuardTriggers = KTWorkflowUtil::getGuardTriggersForTransition($oTransition);
  1255 + $aActionTriggers = KTWorkflowUtil::getActionTriggersForTransition($oTransition);
1262 1256  
1263 1257 $this->aBreadcrumbs[] = array(
1264 1258 'url' => $_SERVER['PHP_SELF'],
... ... @@ -1273,9 +1267,11 @@ class KTWorkflowDispatcher extends KTAdminDispatcher {
1273 1267 'aGroups' => $aGroups,
1274 1268 'aRoles' => $aRoles,
1275 1269 'aConditions' => $aConditions,
  1270 + 'aGuardTriggers' => $aGuardTriggers,
  1271 + 'aActionTriggers' => $aActionTriggers,
1276 1272  
1277 1273 // fields
1278   -
  1274 + 'add_trigger_fields' => $add_trigger_fields,
1279 1275 'edit_fields' => $edit_fields,
1280 1276 ));
1281 1277 return $oTemplate;
... ... @@ -1343,6 +1339,115 @@ class KTWorkflowDispatcher extends KTAdminDispatcher {
1343 1339 }
1344 1340 // }}}
1345 1341  
  1342 + function do_addTrigger() {
  1343 + $aRequest = $this->oValidator->validateDict($_REQUEST, array(
  1344 + 'fWorkflowId' => array('type' => 'workflow'),
  1345 + 'fTransitionId' => array('type' => 'workflowtransition'),
  1346 + ));
  1347 + $oWorkflow =& $this->oValidator->validateWorkflow($_REQUEST['fWorkflowId']);
  1348 + $oTransition =& $this->oValidator->validateWorkflowTransition($_REQUEST['fTransitionId']);
  1349 +
  1350 + // grab the transition ns from the request.
  1351 + $KTWFTriggerReg =& KTWorkflowTriggerRegistry::getSingleton();
  1352 +
  1353 + $this->startTransaction();
  1354 +
  1355 + $oTrigger = $KTWFTriggerReg->getWorkflowTrigger(KTUtil::arrayGet($_REQUEST, 'fTriggerId'));
  1356 + if (PEAR::isError($oTrigger)) {
  1357 + $this->errorRedirectTo('editTransition', _kt('Unable to add trigger.'), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1358 + exit(0);
  1359 + }
  1360 +
  1361 + $oTriggerConfig = KTWorkflowTriggerInstance::createFromArray(array(
  1362 + 'transitionid' => KTUtil::getId($oTransition),
  1363 + 'namespace' => KTUtil::arrayGet($_REQUEST, 'fTriggerId'),
  1364 + 'config' => array(),
  1365 + ));
  1366 +
  1367 + if (PEAR::isError($oTriggerConfig)) {
  1368 + $this->errorRedirectTo('editTransition', _kt('Unable to add trigger.' . $oTriggerConfig->getMessage()), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1369 + exit(0);
  1370 + }
  1371 +
  1372 + $this->successRedirectTo('editTransition', _kt('Trigger added.'), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1373 + exit(0);
  1374 + }
  1375 +
  1376 + function do_editTrigger() {
  1377 + $this->oPage->setBreadcrumbDetails(_kt('editing trigger'));
  1378 + $aRequest = $this->oValidator->validateDict($_REQUEST, array(
  1379 + 'fWorkflowId' => array('type' => 'workflow'),
  1380 + 'fTransitionId' => array('type' => 'workflowtransition'),
  1381 + ));
  1382 + $oWorkflow =& $this->oValidator->validateWorkflow($_REQUEST['fWorkflowId']);
  1383 + $oTransition =& $this->oValidator->validateWorkflowTransition($_REQUEST['fTransitionId']);
  1384 + $oTriggerInstance =& KTWorkflowTriggerInstance::get($_REQUEST['fTriggerInstanceId']);
  1385 + if (PEAR::isError($oTriggerInstance)) {
  1386 + $this->errorRedirectTo('editTransition', _kt('Unable to load trigger.'), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1387 + exit(0);
  1388 + }
  1389 +
  1390 + // grab the transition ns from the request.
  1391 + $KTWFTriggerReg =& KTWorkflowTriggerRegistry::getSingleton();
  1392 +
  1393 + $this->startTransaction();
  1394 +
  1395 + $oTrigger = $KTWFTriggerReg->getWorkflowTrigger($oTriggerInstance->getNamespace());
  1396 + if (PEAR::isError($oTrigger)) {
  1397 + $this->errorRedirectTo('editTransition', _kt('Unable to add trigger.'), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1398 + exit(0);
  1399 + }
  1400 + $oTrigger->loadConfig($oTriggerInstance);
  1401 +
  1402 +
  1403 + // simplify our 'config' stuff.
  1404 + $args = array();
  1405 + $args['fWorkflowId'] = $_REQUEST['fWorkflowId'];
  1406 + $args['fTriggerInstanceId'] = $_REQUEST['fTriggerInstanceId'];
  1407 + $args['fTransitionId'] = $_REQUEST['fTransitionId'];
  1408 + $args['action'] = 'saveTrigger';
  1409 +
  1410 + return $oTrigger->displayConfiguration($args);
  1411 + }
  1412 +
  1413 + // }}}
  1414 +
  1415 + function do_saveTrigger() {
  1416 + $this->oPage->setBreadcrumbDetails(_kt('editing trigger'));
  1417 + $aRequest = $this->oValidator->validateDict($_REQUEST, array(
  1418 + 'fWorkflowId' => array('type' => 'workflow'),
  1419 + 'fTransitionId' => array('type' => 'workflowtransition'),
  1420 + ));
  1421 + $oWorkflow =& $this->oValidator->validateWorkflow($_REQUEST['fWorkflowId']);
  1422 + $oTransition =& $this->oValidator->validateWorkflowTransition($_REQUEST['fTransitionId']);
  1423 + $oTriggerInstance =& KTWorkflowTriggerInstance::get($_REQUEST['fTriggerInstanceId']);
  1424 + if (PEAR::isError($oTriggerInstance)) {
  1425 + $this->errorRedirectTo('editTransition', _kt('Unable to load trigger.'), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1426 + exit(0);
  1427 + }
  1428 +
  1429 + // grab the transition ns from the request.
  1430 + $KTWFTriggerReg =& KTWorkflowTriggerRegistry::getSingleton();
  1431 +
  1432 + $this->startTransaction();
  1433 +
  1434 + $oTrigger = $KTWFTriggerReg->getWorkflowTrigger($oTriggerInstance->getNamespace());
  1435 + if (PEAR::isError($oTrigger)) {
  1436 + $this->errorRedirectTo('editTransition', _kt('Unable to load trigger.'), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1437 + exit(0);
  1438 + }
  1439 + $oTrigger->loadConfig($oTriggerInstance);
  1440 +
  1441 + $res = $oTrigger->saveConfiguration();
  1442 + if (PEAR::isError($res)) {
  1443 + $this->errorRedirectTo('editTransition', _kt('Unable to save trigger: ') . $res->getMessage(), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1444 + exit(0);
  1445 + }
  1446 +
  1447 + $this->successRedirectTo('editTransition', _kt('Trigger saved.'), 'fWorkflowId=' . $oWorkflow->getId() . '&fTransitionId=' . $oTransition->getId());
  1448 + exit(0);
  1449 + }
  1450 +
1346 1451 // }}}
1347 1452  
1348 1453 }
... ...
templates/ktcore/workflow/editTransition.smarty
... ... @@ -25,3 +25,65 @@ requirement.{/i18n}</p>
25 25 </fieldset>
26 26 </form>
27 27  
  28 +
  29 +<fieldset>
  30 + <legend>{i18n}Transition Triggers{/i18n}</legend>
  31 +
  32 + <p class="descriptiveText">{i18n}Transition triggers allow you to have special actions automatically
  33 + occur when a transition is performed, and to control who can perform the transition. Some triggers
  34 + perform <strong>both</strong> of these functions, especially if performing the action requires that
  35 + certain conditions are in place before the action will occur.{/i18n}</p>
  36 +
  37 + <form method="POST" action="{$smarty.server.PHP_SELF}">
  38 +
  39 + <input type="hidden" name="action" value="addTrigger" />
  40 + <input type="hidden" name="fWorkflowId" value="{$oWorkflow->getId()}" />
  41 + <input type="hidden" name="fTransitionId" value="{$oTransition->getId()}" />
  42 +
  43 + {foreach item=oWidget from=$add_trigger_fields}
  44 + {$oWidget->render()}
  45 + {/foreach}
  46 +
  47 + <div class="form_actions">
  48 + <input type="submit" value="Add Trigger" />
  49 + </div>
  50 + </form>
  51 +
  52 + <h3>{i18n}Guards{/i18n}</h3>
  53 +
  54 + <p class="descriptiveText">{i18n}Items which control whether a given user can perform this transition
  55 + on a specific document. <strong>All of these must allow the user to perform the transition.</strong>{/i18n}</p>
  56 + {if empty($aGuardTriggers)}
  57 + <div class="ktInfo"><p>{i18n}Anybody (with the ability to see the document) can perform this transition.{/i18n}</p></div>
  58 + {else}
  59 +
  60 + <table class="kt_collection narrow" cellspacing="0">
  61 + <thead>
  62 + <tr>
  63 + <th>{i18n}Trigger{/i18n}</th>
  64 + <th>{i18n}Configuration{/i18n}</th>
  65 + <th>{i18n}Edit{/i18n}</th>
  66 + <th>{i18n}Delete{/i18n}</th>
  67 + </tr>
  68 + </thead>
  69 + <tbody>
  70 + {foreach from=$aGuardTriggers item=oTrigger}
  71 + <tr>
  72 + <td>{$oTrigger->getName()}</td>
  73 + <td>{$oTrigger->getConfigDescription()}</td>
  74 + <td><a href="{addQS}action=editTrigger&fWorkflowId={$oWorkflow->getId()}&fTransitionId={$oTransition->getId()}&fTriggerInstanceId={$oTrigger->getConfigId()}{/addQS}">edit</a></td>
  75 + <td>x</td>
  76 + </tr>
  77 + {/foreach}
  78 + </tbody>
  79 + </table>
  80 +
  81 + {/if}
  82 + <h3>Actions</h3>
  83 +
  84 + <p class="descriptiveText">{i18n}Actions which are performed when the document follows the transition.{/i18n}</p>
  85 + {if empty($aActionTriggers)}
  86 + <div class="ktInfo"><p>{i18n}No actions are performed when this transition occurs.{/i18n}</p></div>
  87 + {else}
  88 + {/if}
  89 +</fieldset>
... ...
templates/ktcore/workflowtriggers/permissions.smarty 0 → 100644
  1 +<h2>{i18n}Guard permissions for Transition{/i18n}</h2>
  2 +
  3 +<form action="{$smarty.server.PHP_SELF}" method="POST">
  4 +<fieldset>
  5 + <legend>{i18n}Guard Permissions{/i18n}</legend>
  6 +
  7 +{* misc, boring args *}
  8 +{foreach from=$args item=val key=name}
  9 +<input type="hidden" name="{$name}" value="{$val}" />
  10 +{/foreach}
  11 +
  12 +<p class="descriptiveText">{i18n}Specify which permissions the user will require in order to perform this transition. Note that
  13 +the user will be required to have <strong>all</strong> these permissions.{/i18n}</p>
  14 +
  15 +{* FIXME keep old selection *}
  16 +
  17 +{foreach from=$perms item=oPerm}
  18 + <input type="checkbox" name="trigger_perms[{$oPerm->getName()}]" /> {i18n}{$oPerm->getHumanName()}{/i18n} <br />
  19 +{/foreach}
  20 +
  21 +<div class="form_action">
  22 + <input type="submit" value="{i18n}Save Trigger{/i18n}" />
  23 + {* FIXME how do I cancel again? *}
  24 +</div>
  25 +</fieldset>
  26 +</form>
0 27 \ No newline at end of file
... ...