diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
new file mode 100644
index 0000000..7c4e659
--- /dev/null
+++ b/.github/workflows/ci.yml
@@ -0,0 +1,11 @@
+# .github/workflows/ci.yml
+name: ci
+
+on: [push, pull_request]
+
+jobs:
+ test:
+ uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main
+ with:
+ disable_behat: true
+ disable_phpdoc: true
diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml
deleted file mode 100644
index 66fbd9b..0000000
--- a/.github/workflows/master.yml
+++ /dev/null
@@ -1,144 +0,0 @@
-name: master branch test
-
-on: [push, pull_request]
-
-jobs:
- citest:
- name: CI test
- env:
- IGNORE_PATHS: tests/fixtures
- runs-on: 'ubuntu-latest'
-
- services:
- postgres:
- image: postgres:10
- env:
- POSTGRES_USER: 'postgres'
- POSTGRES_HOST_AUTH_METHOD: 'trust'
- options: >-
- --health-cmd pg_isready
- --health-interval 10s
- --health-timeout 5s
- --health-retries 3
- ports:
- - 5432:5432
-
- mariadb:
- image: mariadb:10.5
- env:
- MYSQL_USER: 'root'
- MYSQL_ALLOW_EMPTY_PASSWORD: "true"
- ports:
- - 3306:3306
- options: >-
- --health-cmd="mysqladmin ping"
- --health-interval 10s
- --health-timeout 5s
- --health-retries 3
- strategy:
- fail-fast: false
- matrix:
- include:
- - php: '7.1'
- moodle-branch: 'MOODLE_35_STABLE'
- database: 'mariadb'
- node: '14.15'
- - php: '7.2'
- moodle-branch: 'MOODLE_35_STABLE'
- database: 'pgsql'
- node: '14.15'
- - php: '7.3'
- moodle-branch: 'MOODLE_38_STABLE'
- database: 'mariadb'
- node: '14.15'
- - php: '7.3'
- moodle-branch: 'MOODLE_38_STABLE'
- database: 'pgsql'
- node: '14.15'
- - php: '7.3'
- moodle-branch: 'MOODLE_39_STABLE'
- database: 'mariadb'
- node: '14.15'
- - php: '7.3'
- moodle-branch: 'MOODLE_39_STABLE'
- database: 'pgsql'
- node: '14.15'
- - php: '7.3'
- moodle-branch: 'MOODLE_310_STABLE'
- database: 'mariadb'
- node: '14.15'
- - php: '7.3'
- moodle-branch: 'MOODLE_310_STABLE'
- database: 'pgsql'
- node: '14.15'
-
- steps:
- - name: Check out repository code
- uses: actions/checkout@v2
- with:
- path: plugin
-
- - name: Install node ${{ matrix.node }}
- uses: actions/setup-node@v2
- with:
- node-version: ${{ matrix.node }}
-
- - name: Setup PHP ${{ matrix.php }}
- uses: shivammathur/setup-php@v2
- with:
- php-version: ${{ matrix.php }}
- extensions: pgsql, mysqli, zip, gd, xmlrpc, soap
- coverage: none
-
- - name: Initialise moodle-plugin-ci
- run: |
- composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3
- # Add dirs to $PATH
- echo $(cd ci/bin; pwd) >> $GITHUB_PATH
- echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH
- # PHPUnit depends on en_AU.UTF-8 locale
- sudo locale-gen en_AU.UTF-8
- - name: Install Moodle
- run: moodle-plugin-ci install -vvv --plugin ./plugin --db-host=127.0.0.1
- env:
- DB: ${{ matrix.database }}
- MOODLE_BRANCH: ${{ matrix.moodle-branch }}
-
- - name: Run phplint
- if: ${{ always() }}
- run: moodle-plugin-ci phplint
-
- - name: Run codechecker
- if: ${{ always() }}
- run: moodle-plugin-ci codechecker
-
- - name: Run validate
- if: ${{ always() }}
- run: moodle-plugin-ci validate
-
- - name: Run savepoints
- if: ${{ always() }}
- run: moodle-plugin-ci savepoints
-
- - name: Run mustache
- continue-on-error: true # This step will show errors but will not fail
- if: ${{ always() }}
- run: moodle-plugin-ci mustache
-
- - name: Run phpunit
- if: ${{ always() }}
- run: moodle-plugin-ci phpunit
-
- - name: Run behat
- if: ${{ always() }}
- run: moodle-plugin-ci behat --profile chrome
-
- - name: PHP Copy/Paste Detector
- continue-on-error: true # This step will show errors but will not fail
- if: ${{ always() }}
- run: moodle-plugin-ci phpcpd
-
- - name: PHP Mess Detector
- continue-on-error: true # This step will show errors but will not fail
- if: ${{ always() }}
- run: moodle-plugin-ci phpmd
diff --git a/README.md b/README.md
index a1c5128..be32ebe 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-
+[](https://github.com/catalyst/moodle-tool_trigger/actions/workflows/ci.yml?branch=MOODLE_35_STABLE)
# Event Trigger
@@ -12,7 +12,7 @@ Each workflow is made up of a series of *steps*. Steps can be things like:
The plugin is designed to be extensible and contributions are welcome to extend the available actions.
-More configuration documentation can be found at the following link:
+More configuration documentation can be found at the following link:
* https://github.com/catalyst/moodle-tool_trigger/wiki
@@ -20,22 +20,13 @@ More Information on Moodle events can be found in the Moodle documentation at th
* https://docs.moodle.org/dev/Event_2
-## Supported Moodle Versions
-This plugin currently supports Moodle:
-
-* 3.5
-* 3.8
-* 3.9
-* 3.10
-* 3.11
-
## Branches ##
The following maps the plugin version to use depending on your Moodle version.
-| Moodle verion | Branch |
-| ------------------ | ----------- |
-| Moodle 3.5 to 3.10 | master |
-| Moodle 3.11+ | MOODLE_311 |
+| Moodle verion | Branch |
+| ------------------ | ------------------ |
+| Moodle 3.5 to 3.10 | MOODLE_35_STABLE |
+| Moodle 3.11+ | MOODLE_311_STABLE |
## Moodle Plugin Installation
The following sections outline how to install the Moodle plugin.
@@ -55,7 +46,7 @@ To install the plugin in Moodle via the Moodle User Interface:
3. Install plugin from Moodle Plugin directory or via zip upload.
## Plugin Setup
-Plugin setup and configuration documentation can be found at the following link:
+Plugin setup and configuration documentation can be found at the following link:
* https://github.com/catalyst/moodle-tool_trigger/wiki
@@ -74,7 +65,7 @@ https://www.catalyst-au.net/
# Contributing and Support
-Issues, and pull requests using github are welcome and encouraged!
+Issues, and pull requests using github are welcome and encouraged!
https://github.com/catalyst/moodle-tool_trigger/issues
diff --git a/amd/build/import_workflow.min.js b/amd/build/import_workflow.min.js
index cd58e91..5000d1b 100644
--- a/amd/build/import_workflow.min.js
+++ b/amd/build/import_workflow.min.js
@@ -1,2 +1,12 @@
-define ("tool_trigger/import_workflow",["jquery","core/str","core/modal_factory","core/modal_events","core/templates","core/ajax","core/fragment","core/notification"],function(a,b,c,d,e,f,g,h){var k={},l,m,n="
Loading...
";function i(){var a={jsonformdata:JSON.stringify({})};m.setBody(n);m.setBody(g.loadFragment("tool_trigger","new_import_form",l,a))}function j(a){a.preventDefault();var c=m.getRoot().find("form").serialize();m.setBody(n);f.call([{methodname:"tool_trigger_process_import_form",args:{jsonformdata:JSON.stringify(c)}}])[0].done(function(a){var b=JSON.parse(a);if("success"==b.errorcode){location.reload(!0)}else{Object.keys(b.message).forEach(function(a){h.addNotification({message:b.message[a],type:"error"})})}m.hide()}).fail(function(){h.addNotification({message:b.get_string("errorimportworkflow","tool_trigger"),type:"error"});m.hide()})}k.init=function(e){l=e;b.get_string("importmodaltitle","tool_trigger").then(function(b){c.create({type:c.types.SAVE_CANCEL,title:b,body:n,large:!0},a("[name=importbtn]")).done(function(a){m=a;m.getRoot().on(d.save,j);m.getRoot().on(d.hidden,i);i()})})};return k});
-//# sourceMappingURL=import_workflow.min.js.map
+/**
+ * Workflow step select javascript.
+ *
+ * @module tool_trigger/workflow
+ * @class Workflow
+ * @copyright 2018 Matt Porritt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since 3.4
+ */
+define("tool_trigger/import_workflow",["jquery","core/str","core/modal_factory","core/modal_events","core/templates","core/ajax","core/fragment","core/notification"],(function($,Str,ModalFactory,ModalEvents,Templates,ajax,Fragment,Notification){var contextid,modalObj,ImportWorkflow={},spinner='Loading...
';function updateModalBody(){var params={jsonformdata:JSON.stringify({})};modalObj.setBody(spinner),modalObj.setBody(Fragment.loadFragment("tool_trigger","new_import_form",contextid,params))}function processModalForm(e){e.preventDefault();var fileform=modalObj.getRoot().find("form").serialize();modalObj.setBody(spinner),ajax.call([{methodname:"tool_trigger_process_import_form",args:{jsonformdata:JSON.stringify(fileform)}}])[0].done((function(responsejson){var responseobj=JSON.parse(responsejson);"success"==responseobj.errorcode?location.reload(!0):Object.keys(responseobj.message).forEach((function(key){Notification.addNotification({message:responseobj.message[key],type:"error"})})),modalObj.hide()})).fail((function(){Notification.addNotification({message:Str.get_string("errorimportworkflow","tool_trigger"),type:"error"}),modalObj.hide()}))}return ImportWorkflow.init=function(context){contextid=context,Str.get_string("importmodaltitle","tool_trigger").then((function(title){ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:title,body:spinner,large:!0},$("[name=importbtn]")).done((function(modal){(modalObj=modal).getRoot().on(ModalEvents.save,processModalForm),modalObj.getRoot().on(ModalEvents.hidden,updateModalBody),updateModalBody()}))}))},ImportWorkflow}));
+
+//# sourceMappingURL=import_workflow.min.js.map
\ No newline at end of file
diff --git a/amd/build/import_workflow.min.js.map b/amd/build/import_workflow.min.js.map
index 8b5431d..79a6fab 100644
--- a/amd/build/import_workflow.min.js.map
+++ b/amd/build/import_workflow.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/import_workflow.js"],"names":["define","$","Str","ModalFactory","ModalEvents","Templates","ajax","Fragment","Notification","ImportWorkflow","contextid","modalObj","spinner","updateModalBody","params","jsonformdata","JSON","stringify","setBody","loadFragment","processModalForm","e","preventDefault","fileform","getRoot","find","serialize","call","methodname","args","done","responsejson","responseobj","parse","errorcode","location","reload","Object","keys","message","forEach","key","addNotification","type","hide","fail","get_string","init","context","then","title","create","types","SAVE_CANCEL","body","large","modal","on","save","hidden"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Workflow step select javascript.\n *\n * @module tool_trigger/workflow\n * @package tool_trigger\n * @class Workflow\n * @copyright 2018 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.4\n */\n\ndefine(\n ['jquery', 'core/str', 'core/modal_factory', 'core/modal_events','core/templates', 'core/ajax', 'core/fragment',\n 'core/notification'],\n function ($, Str, ModalFactory, ModalEvents, Templates, ajax, Fragment, Notification) {\n\n /**\n * Module level variables.\n */\n var ImportWorkflow = {};\n var contextid;\n var modalObj;\n var spinner = ''\n + 'Loading... '\n + '
';\n\n /**\n * Updates the body of the modal window.\n *\n * @private\n */\n function updateModalBody() {\n var formdata = {};\n var params = {jsonformdata: JSON.stringify(formdata)};\n modalObj.setBody(spinner);\n modalObj.setBody(Fragment.loadFragment('tool_trigger', 'new_import_form', contextid, params));\n }\n\n /**\n * Updates Moodle form with selected information.\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n var fileform = modalObj.getRoot().find('form').serialize();\n modalObj.setBody(spinner);\n\n // Submit form via ajax to do server side validation.\n ajax.call([{\n methodname: 'tool_trigger_process_import_form',\n args: {\n jsonformdata: JSON.stringify(fileform)\n },\n }])[0].done(function(responsejson) {\n var responseobj = JSON.parse(responsejson);\n\n if (responseobj.errorcode == 'success') {\n // Validation succeeded! Update the list of workflows.\n location.reload(true); // We're lazy so we'll just reload the page.\n } else {\n Object.keys(responseobj.message).forEach(function(key) {\n Notification.addNotification({\n message: responseobj.message[key],\n type: 'error'\n });\n });\n }\n\n modalObj.hide(); // Hide the modal.\n\n }).fail(function() {\n // Validation failed!\n Notification.addNotification({\n message: Str.get_string('errorimportworkflow', 'tool_trigger'),\n type: 'error'\n });\n\n modalObj.hide(); // Hide the modal.\n });\n }\n\n /**\n * Initialise the class.\n *\n * @public\n */\n ImportWorkflow.init = function(context) {\n // Save the context ID in a closure variable.\n contextid = context;\n\n // Get the Title String.\n Str.get_string('importmodaltitle', 'tool_trigger').then(function(title) {\n // Create the Modal.\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: title,\n body: spinner,\n large: true\n }, $('[name=importbtn]'))\n .done(function(modal) {\n modalObj = modal;\n modalObj.getRoot().on(ModalEvents.save, processModalForm);\n modalObj.getRoot().on(ModalEvents.hidden, updateModalBody);\n updateModalBody();\n });\n });\n\n };\n\n return ImportWorkflow;\n });\n"],"file":"import_workflow.min.js"}
\ No newline at end of file
+{"version":3,"file":"import_workflow.min.js","sources":["../src/import_workflow.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Workflow step select javascript.\n *\n * @module tool_trigger/workflow\n * @class Workflow\n * @copyright 2018 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.4\n */\n\ndefine(\n ['jquery', 'core/str', 'core/modal_factory', 'core/modal_events','core/templates', 'core/ajax', 'core/fragment',\n 'core/notification'],\n function ($, Str, ModalFactory, ModalEvents, Templates, ajax, Fragment, Notification) {\n\n /**\n * Module level variables.\n */\n var ImportWorkflow = {};\n var contextid;\n var modalObj;\n var spinner = ''\n + 'Loading... '\n + '
';\n\n /**\n * Updates the body of the modal window.\n *\n * @private\n */\n function updateModalBody() {\n var formdata = {};\n var params = {jsonformdata: JSON.stringify(formdata)};\n modalObj.setBody(spinner);\n modalObj.setBody(Fragment.loadFragment('tool_trigger', 'new_import_form', contextid, params));\n }\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {event} e The event from the modal submitting.\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n var fileform = modalObj.getRoot().find('form').serialize();\n modalObj.setBody(spinner);\n\n // Submit form via ajax to do server side validation.\n ajax.call([{\n methodname: 'tool_trigger_process_import_form',\n args: {\n jsonformdata: JSON.stringify(fileform)\n },\n }])[0].done(function(responsejson) {\n var responseobj = JSON.parse(responsejson);\n\n if (responseobj.errorcode == 'success') {\n // Validation succeeded! Update the list of workflows.\n location.reload(true); // We're lazy so we'll just reload the page.\n } else {\n Object.keys(responseobj.message).forEach(function(key) {\n Notification.addNotification({\n message: responseobj.message[key],\n type: 'error'\n });\n });\n }\n\n modalObj.hide(); // Hide the modal.\n\n }).fail(function() {\n // Validation failed!\n Notification.addNotification({\n message: Str.get_string('errorimportworkflow', 'tool_trigger'),\n type: 'error'\n });\n\n modalObj.hide(); // Hide the modal.\n });\n }\n\n /**\n * Initialise the class.\n *\n * @param {int} context the context id from PHP.\n * @public\n */\n ImportWorkflow.init = function(context) {\n // Save the context ID in a closure variable.\n contextid = context;\n\n // Get the Title String.\n Str.get_string('importmodaltitle', 'tool_trigger').then(function(title) {\n // Create the Modal.\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: title,\n body: spinner,\n large: true\n }, $('[name=importbtn]'))\n .done(function(modal) {\n modalObj = modal;\n modalObj.getRoot().on(ModalEvents.save, processModalForm);\n modalObj.getRoot().on(ModalEvents.hidden, updateModalBody);\n updateModalBody();\n });\n });\n\n };\n\n return ImportWorkflow;\n });\n"],"names":["define","$","Str","ModalFactory","ModalEvents","Templates","ajax","Fragment","Notification","contextid","modalObj","ImportWorkflow","spinner","updateModalBody","params","jsonformdata","JSON","stringify","setBody","loadFragment","processModalForm","e","preventDefault","fileform","getRoot","find","serialize","call","methodname","args","done","responsejson","responseobj","parse","errorcode","location","reload","Object","keys","message","forEach","key","addNotification","type","hide","fail","get_string","init","context","then","title","create","types","SAVE_CANCEL","body","large","modal","on","save","hidden"],"mappings":";;;;;;;;;AAyBAA,sCACE,CAAC,SAAU,WAAY,qBAAsB,oBAAoB,iBAAkB,YAAa,gBAC5F,sBACE,SAAUC,EAAGC,IAAKC,aAAcC,YAAaC,UAAWC,KAAMC,SAAUC,kBAMhEC,UACAC,SAFAC,eAAiB,GAGjBC,QAAU,gIASLC,sBAEDC,OAAS,CAACC,aAAcC,KAAKC,UADlB,KAEfP,SAASQ,QAAQN,SACjBF,SAASQ,QAAQX,SAASY,aAAa,eAAgB,kBAAmBV,UAAWK,kBAShFM,iBAAiBC,GACtBA,EAAEC,qBAGEC,SAAWb,SAASc,UAAUC,KAAK,QAAQC,YAC/ChB,SAASQ,QAAQN,SAGjBN,KAAKqB,KAAK,CAAC,CACPC,WAAY,mCACZC,KAAM,CACFd,aAAcC,KAAKC,UAAUM,cAEjC,GAAGO,MAAK,SAASC,kBACbC,YAAchB,KAAKiB,MAAMF,cAEA,WAAzBC,YAAYE,UAEZC,SAASC,QAAO,GAEhBC,OAAOC,KAAKN,YAAYO,SAASC,SAAQ,SAASC,KAC9CjC,aAAakC,gBAAgB,CACzBH,QAASP,YAAYO,QAAQE,KAC7BE,KAAM,aAKlBjC,SAASkC,UAEVC,MAAK,WAEJrC,aAAakC,gBAAgB,CACzBH,QAASrC,IAAI4C,WAAW,sBAAuB,gBAC/CH,KAAM,UAGVjC,SAASkC,iBAUjBjC,eAAeoC,KAAO,SAASC,SAE3BvC,UAAYuC,QAGZ9C,IAAI4C,WAAW,mBAAoB,gBAAgBG,MAAK,SAASC,OAE7D/C,aAAagD,OAAO,CAChBR,KAAMxC,aAAaiD,MAAMC,YACzBH,MAAOA,MACPI,KAAM1C,QACN2C,OAAO,GACRtD,EAAE,qBACJ6B,MAAK,SAAS0B,QACX9C,SAAW8C,OACFhC,UAAUiC,GAAGrD,YAAYsD,KAAMtC,kBACxCV,SAASc,UAAUiC,GAAGrD,YAAYuD,OAAQ9C,iBAC1CA,yBAMLF"}
\ No newline at end of file
diff --git a/amd/build/step_select.min.js b/amd/build/step_select.min.js
index 489ccbe..4a7eb21 100644
--- a/amd/build/step_select.min.js
+++ b/amd/build/step_select.min.js
@@ -1,2 +1,12 @@
-define ("tool_trigger/step_select",["jquery","core/str","core/modal_factory","core/modal_events","core/templates","core/ajax","core/fragment","core/notification"],function(a,b,c,d,e,f,g,h){var u={},v,w,x="Loading...
";function i(){var b=a("[name=stepjson]").val(),c=[];if(""!==b){c=JSON.parse(b)}return c}function j(b){a("[name=stepjson]").val(JSON.stringify(b));a("[name=isstepschanged]").val(1)}function k(){var a={jsonformdata:JSON.stringify({})};w.setBody(x);w.setBody(g.loadFragment("tool_trigger","new_base_form",v,a))}function l(b){var c=b.map(function(a,b){return{name:a.name,typedesc:a.typedesc,stepdesc:a.stepdesc,steporder:b}});e.render("tool_trigger/workflow_steps",{rows:c}).then(function(b){a("#steps-table").html(b);t()}).fail(function(){h.exception({message:"Error updating steps table"})})}function m(b){b.preventDefault();var c=w.getRoot().find("form"),d=c.serializeArray().reduce(function(a,b){if(b.name.endsWith("[]")){var c=b.name.substring(0,b.name.length-2);if(a[c]===void 0){a[c]=[b.value]}else{a[c].push(b.value)}}else if("sesskey"!==b.name&&!b.name.startsWith("_qf__")&&!b.value.startsWith("_qf__")){a[b.name]=b.value}return a},{});d.stepdesc=a("[name=stepclass] option:selected").text();d.typedesc=a("[name=type] option:selected").text();f.call([{methodname:"tool_trigger_validate_form",args:{stepclass:d.stepclass,jsonformdata:JSON.stringify(c.serialize())}}])[0].done(function(){var a=i();if(0<=d.steporder){a[d.steporder]=d}else{a.push(d);d.steporder=a.length-1}j(a);l(a);w.hide()}).fail(function(){q(d.type,d.stepclass,"",c.serialize())})}function n(b){a("[name=stepclass]").empty().append(a("",{value:"",text:"Choose..."}));a.each(b,function(b,c){a("[name=stepclass]").append(a(" ",{value:c.class,text:c.name}))})}function o(a){f.call([{methodname:"tool_trigger_step_by_type",args:{steptype:a}}])[0].done(function(a){n(a)})}function p(){var b=a("[name=eventtomonitor]").val();return b}function q(a,b,c,d,e){if(c===void 0){c=""}if(d===void 0){d=""}if(e===void 0){e=0}w.setBody(x);w.setBody(g.loadFragment("tool_trigger","new_step_form",v,{steptype:a,stepclass:b,defaults:JSON.stringify(c),ajaxformdata:d,event:p(),existingsteps:JSON.stringify(i()),steporder:e}))}function r(){a("body").on("change","[name=type]",function(){o(this.value)});a("body").on("change","[name=stepclass]",function(){var b=a("[name=type]").val(),c=this.value;q(b,c,"","",-1)})}function s(b,c,d){var e=b[c],f=b[d];e.steporder=d;f.steporder=d;b[c]=f;b[d]=e;var g=a("tr.tool-trigger-step-table-row"),h=g.eq(c),i=g.eq(d),k=h.height(),m=i.height(),n=h.find("th").css("background-color"),o=i.find("th").css("background-color");a([h,i]).each(function(a,b){b[0].style.position="relative";b[0].style.top="0";b[0].style.transition="top 400ms";b.find("th,td").each(function(a,b){b.style.transition="background-color 400ms"})});window.setTimeout(function(){h[0].style.top=m+"px";h.find("td,th").each(function(a,b){b.style.backgroundColor=o});i[0].style.top=-1*k+"px";i.find("td,th").each(function(a,b){b.style.backgroundColor=n})});h.find(".tool-trigger-step-movedown").fadeTo(400,.1);i.find(".tool-trigger-step-moveup").fadeTo(400,.1).promise().always(function(){j(b);l(b)})}function t(){a(".tool-trigger-step-moveup").slice(1).removeClass("tool-trigger-initial-hidden").on("click",function(){var b=i(),c=a(this).data("steporder");if(0===c){return!0}s(b,c-1,c);return!0});a(".tool-trigger-step-movedown").slice(0,-1).removeClass("tool-trigger-initial-hidden").on("click",function(){var b=i(),c=a(this).data("steporder");if(c>=b.length-1){return!0}s(b,c,c+1);return!0});a(".tool-trigger-step-edit").removeClass("tool-trigger-initial-hidden").on("click",function(){w.setBody(x);w.show();var b=i(),c=a(this).data("steporder"),d=b[c];q(d.type,d.stepclass,d,void 0,c)});a(".tool-trigger-step-delete").removeClass("tool-trigger-initial-hidden").on("click",function(){var b=i(),c=a(this).data("steporder");b.splice(c,1);if(c<=b.length){b.slice(c).forEach(function(a){a.steporder=a.steporder-1})}j(b);a(this).closest("tr").fadeOut(function(){l(b)});return!0})}u.init=function(e){v=e;b.get_string("modaltitle","tool_trigger").then(function(b){c.create({type:c.types.SAVE_CANCEL,title:b,body:x,large:!0},a("#id_stepmodalbutton")).done(function(a){w=a;w.getRoot().on(d.save,m);w.getRoot().on(d.hidden,k);r();k()})});t()};return u});
-//# sourceMappingURL=step_select.min.js.map
+/**
+ * Workflow step select javascript.
+ *
+ * @module tool_trigger/workflow
+ * @class Workflow
+ * @copyright 2018 Matt Porritt
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @since 3.4
+ */
+define("tool_trigger/step_select",["jquery","core/str","core/modal_factory","core/modal_events","core/templates","core/ajax","core/fragment","core/notification"],(function($,Str,ModalFactory,ModalEvents,Templates,ajax,Fragment,Notification){var contextid,modalObj,StepSelect={},spinner='Loading...
';function getParentFormSteps(){var stepsjson=$("[name=stepjson]").val(),steps=[];return""!==stepsjson&&(steps=JSON.parse(stepsjson)),steps}function setCurrentFormSteps(steps){$("[name=stepjson]").val(JSON.stringify(steps)),$("[name=isstepschanged]").val(1)}function updateModalBody(){var params={jsonformdata:JSON.stringify({})};modalObj.setBody(spinner),modalObj.setBody(Fragment.loadFragment("tool_trigger","new_base_form",contextid,params))}function updateTable(stepData){var tableData={rows:stepData.map((function(step,stepidx){return{name:step.name,typedesc:step.typedesc,stepdesc:step.stepdesc,steporder:stepidx}}))};Templates.render("tool_trigger/workflow_steps",tableData).then((function(html){$("#steps-table").html(html),setupTableHandlers()})).fail((function(){Notification.exception({message:"Error updating steps table"})}))}function processModalForm(e){e.preventDefault();var $stepform=modalObj.getRoot().find("form"),curstep=$stepform.serializeArray().reduce((function(finalobj,field){if(field.name.endsWith("[]")){var fieldname=field.name.substring(0,field.name.length-2);void 0===finalobj[fieldname]?finalobj[fieldname]=[field.value]:finalobj[fieldname].push(field.value)}else"sesskey"===field.name||field.name.startsWith("_qf__")||field.value.startsWith("_qf__")||(finalobj[field.name]=field.value);return finalobj}),{});curstep.stepdesc=$("[name=stepclass] option:selected").text(),curstep.typedesc=$("[name=type] option:selected").text(),ajax.call([{methodname:"tool_trigger_validate_form",args:{stepclass:curstep.stepclass,jsonformdata:JSON.stringify($stepform.serialize())}}])[0].done((function(){var steps=getParentFormSteps();curstep.steporder>=0?steps[curstep.steporder]=curstep:(steps.push(curstep),curstep.steporder=steps.length-1),setCurrentFormSteps(steps),updateTable(steps),modalObj.hide()})).fail((function(){renderStepForm(curstep.type,curstep.stepclass,"",$stepform.serialize())}))}function getStepsOfType(valfilter){ajax.call([{methodname:"tool_trigger_step_by_type",args:{steptype:valfilter}}])[0].done((function(response){var events;events=response,$("[name=stepclass]").empty().append($("",{value:"",text:"Choose..."})),$.each(events,(function(i,event){$("[name=stepclass]").append($(" ",{value:event.class,text:event.name}))}))}))}function renderStepForm(steptype,stepclass,formdefaults,formsubmission,steporder){void 0===formdefaults&&(formdefaults=""),void 0===formsubmission&&(formsubmission=""),void 0===steporder&&(steporder=0),modalObj.setBody(spinner),modalObj.setBody(Fragment.loadFragment("tool_trigger","new_step_form",contextid,{steptype:steptype,stepclass:stepclass,defaults:JSON.stringify(formdefaults),ajaxformdata:formsubmission,event:$("[name=eventtomonitor]").val(),existingsteps:JSON.stringify(getParentFormSteps()),steporder:steporder}))}function swapSteps(steps,pos1,pos2){var step1=steps[pos1],step2=steps[pos2];step1.steporder=pos2,step2.steporder=pos2,steps[pos1]=step2,steps[pos2]=step1;var $rows=$("tr.tool-trigger-step-table-row"),$row1=$rows.eq(pos1),$row2=$rows.eq(pos2),row1height=$row1.height(),row2height=$row2.height(),row1color=$row1.find("th").css("background-color"),row2color=$row2.find("th").css("background-color");$([$row1,$row2]).each((function(idx,row){row[0].style.position="relative",row[0].style.top="0",row[0].style.transition="top 400ms",row.find("th,td").each((function(idx,cell){cell.style.transition="background-color 400ms"}))})),window.setTimeout((function(){$row1[0].style.top=row2height+"px",$row1.find("td,th").each((function(idx,cell){cell.style.backgroundColor=row2color})),$row2[0].style.top=-1*row1height+"px",$row2.find("td,th").each((function(idx,cell){cell.style.backgroundColor=row1color}))})),$row1.find(".tool-trigger-step-movedown").fadeTo(400,.1),$row2.find(".tool-trigger-step-moveup").fadeTo(400,.1).promise().always((function(){setCurrentFormSteps(steps),updateTable(steps)}))}function setupTableHandlers(){$(".tool-trigger-step-moveup").slice(1).removeClass("tool-trigger-initial-hidden").on("click",(function(){var steps=getParentFormSteps(),steporder=$(this).data("steporder");return 0===steporder||swapSteps(steps,steporder-1,steporder),!0})),$(".tool-trigger-step-movedown").slice(0,-1).removeClass("tool-trigger-initial-hidden").on("click",(function(){var steps=getParentFormSteps(),steporder=$(this).data("steporder");return steporder>=steps.length-1||swapSteps(steps,steporder,steporder+1),!0})),$(".tool-trigger-step-edit").removeClass("tool-trigger-initial-hidden").on("click",(function(){modalObj.setBody(spinner),modalObj.show();var steps=getParentFormSteps(),steporder=$(this).data("steporder"),step=steps[steporder];renderStepForm(step.type,step.stepclass,step,void 0,steporder)})),$(".tool-trigger-step-delete").removeClass("tool-trigger-initial-hidden").on("click",(function(){var steps=getParentFormSteps(),steporder=$(this).data("steporder");return steps.splice(steporder,1),steporder<=steps.length&&steps.slice(steporder).forEach((function(step){step.steporder=step.steporder-1})),setCurrentFormSteps(steps),$(this).closest("tr").fadeOut((function(){updateTable(steps)})),!0}))}return StepSelect.init=function(context){contextid=context,Str.get_string("modaltitle","tool_trigger").then((function(title){ModalFactory.create({type:ModalFactory.types.SAVE_CANCEL,title:title,body:spinner,large:!0},$("#id_stepmodalbutton")).done((function(modal){(modalObj=modal).getRoot().on(ModalEvents.save,processModalForm),modalObj.getRoot().on(ModalEvents.hidden,updateModalBody),$("body").on("change","[name=type]",(function(){getStepsOfType(this.value)})),$("body").on("change","[name=stepclass]",(function(){renderStepForm($("[name=type]").val(),this.value,"","",-1)})),updateModalBody()}))})),setupTableHandlers()},StepSelect}));
+
+//# sourceMappingURL=step_select.min.js.map
\ No newline at end of file
diff --git a/amd/build/step_select.min.js.map b/amd/build/step_select.min.js.map
index 459ea4a..1eb84f3 100644
--- a/amd/build/step_select.min.js.map
+++ b/amd/build/step_select.min.js.map
@@ -1 +1 @@
-{"version":3,"sources":["../src/step_select.js"],"names":["define","$","Str","ModalFactory","ModalEvents","Templates","ajax","Fragment","Notification","StepSelect","contextid","modalObj","spinner","getParentFormSteps","stepsjson","val","steps","JSON","parse","setCurrentFormSteps","stringify","updateModalBody","params","jsonformdata","setBody","loadFragment","updateTable","stepData","rows","map","step","stepidx","name","typedesc","stepdesc","steporder","render","then","html","setupTableHandlers","fail","exception","message","processModalForm","e","preventDefault","$stepform","getRoot","find","curstep","serializeArray","reduce","finalobj","field","endsWith","fieldname","substring","length","value","push","startsWith","text","call","methodname","args","stepclass","serialize","done","hide","renderStepForm","updateStepOptions","events","empty","append","each","i","event","class","getStepsOfType","valfilter","response","getEventName","eventname","steptype","formdefaults","formsubmission","setupModalChangeHandlers","on","swapSteps","pos1","pos2","step1","step2","$rows","$row1","eq","$row2","row1height","height","row2height","row1color","css","row2color","idx","row","style","position","top","transition","cell","window","setTimeout","backgroundColor","fadeTo","promise","always","slice","removeClass","data","show","splice","forEach","closest","fadeOut","init","context","get_string","title","create","type","types","SAVE_CANCEL","body","large","modal","save","hidden"],"mappings":"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","sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Workflow step select javascript.\n *\n * @module tool_trigger/workflow\n * @package tool_trigger\n * @class Workflow\n * @copyright 2018 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.4\n */\n\ndefine(\n ['jquery', 'core/str', 'core/modal_factory', 'core/modal_events','core/templates', 'core/ajax', 'core/fragment',\n 'core/notification'],\n function ($, Str, ModalFactory, ModalEvents, Templates, ajax, Fragment, Notification) {\n\n /**\n * Module level variables.\n */\n var StepSelect = {};\n var contextid;\n var modalObj;\n var spinner = ''\n + 'Loading... '\n + '
';\n\n /**\n * Retrieves the steps serialized to JSON in the stepsjson hidden form field.\n */\n function getParentFormSteps() {\n var stepsjson = $('[name=stepjson]').val();\n var steps = [];\n if (stepsjson !== '') {\n steps = JSON.parse(stepsjson);\n }\n return steps;\n }\n\n /**\n * Updates the steps stored in the hidden form field\n */\n function setCurrentFormSteps(steps) {\n $('[name=stepjson]').val(JSON.stringify(steps));\n // Set the flag field that indicates there was a change to the steps.\n $('[name=isstepschanged]').val(1);\n }\n\n /**\n * Updates the body of the modal window.\n *\n * @private\n */\n function updateModalBody() {\n var formdata = {};\n var params = {jsonformdata: JSON.stringify(formdata)};\n modalObj.setBody(spinner);\n modalObj.setBody(Fragment.loadFragment('tool_trigger', 'new_base_form', contextid, params));\n }\n\n function updateTable(stepData) {\n // Format data for template.\n // Filter out only the fields we want for each step, and make sure the \"steporder\" values\n // are correct.\n var rows = stepData.map(\n function(step, stepidx) {\n return {\n name: step.name,\n typedesc: step.typedesc,\n stepdesc: step.stepdesc,\n steporder: stepidx\n };\n }\n );\n var tableData = {'rows': rows};\n Templates.render(\n 'tool_trigger/workflow_steps',\n tableData\n ).then(function(html) {\n $('#steps-table').html(html);\n setupTableHandlers();\n }).fail(function() {\n Notification.exception({message: 'Error updating steps table'});\n });\n }\n\n /**\n * Updates Moodle form with selected information.\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n var $stepform = modalObj.getRoot().find('form');\n // Use jQuery().serializeArray() to collect the values of all the form fields.\n // Then convert from its array-of-objects output format into a single object.\n var curstep = $stepform.serializeArray().reduce(\n function(finalobj, field) {\n // If field ends with [], the form el was an array.\n if (field.name.endsWith('[]')) {\n let fieldname = field.name.substring(0, field.name.length - 2);\n if (finalobj[fieldname] === undefined) {\n finalobj[fieldname] = [field.value];\n } else {\n finalobj[fieldname].push(field.value);\n }\n } else if (field.name !== 'sesskey'\n && !field.name.startsWith('_qf__')\n && !field.value.startsWith('_qf__')) {\n // Filter out the sesskey and formslib system fields.\n finalobj[field.name] = field.value;\n }\n\n return finalobj;\n },\n {}\n );\n\n // Add the description string for the step class and type, in order to make later rendering\n // easier...\n curstep['stepdesc'] = $('[name=stepclass] option:selected').text();\n curstep['typedesc'] = $('[name=type] option:selected').text();\n\n // Submit form via ajax to do server side validation.\n ajax.call([{\n methodname: 'tool_trigger_validate_form',\n args: {\n stepclass: curstep['stepclass'],\n jsonformdata: JSON.stringify($stepform.serialize())\n },\n }])[0].done(function() {\n\n // Validation succeeded! Update the parent form's hidden steps data, and update\n // the table.\n var steps = getParentFormSteps();\n\n if (curstep.steporder >= 0) {\n // If we were editing an existing step, swap it into place in the list.\n steps[curstep.steporder] = curstep;\n } else {\n // If we were creating a new step, add it to the end of the list.\n steps.push(curstep);\n curstep.steporder = steps.length - 1;\n }\n setCurrentFormSteps(steps); // Update steps in hidden form field\n updateTable(steps); // Update table in workflow form.\n modalObj.hide(); // Hide the modal.\n\n }).fail(function() {\n\n // Validation failed! Don't close the modal, don't update anything on the parent\n // form.\n renderStepForm(curstep['type'], curstep['stepclass'], '', $stepform.serialize());\n });\n }\n\n /**\n * Updates the step list in the step modal edit form,\n * with only the steps that correspond to the selected\n * step type.\n *\n * @param array events Array of steps to update selection with.\n */\n function updateStepOptions(events) {\n\n // First clear the existing options in the select element.\n $('[name=stepclass]').empty().append($('', {\n value: '',\n text : 'Choose...'\n }));\n\n // Update the select with applicable events.\n $.each(events, function (i, event) {\n $('[name=stepclass]').append($(' ', {\n value: event.class,\n text : event.name\n }));\n });\n }\n\n /**\n * Gets a list of filtered steps based on the selected step type.\n * Triggers updating of the form step select element.\n *\n * @param string varfilter The filter area.\n */\n function getStepsOfType(valfilter) {\n ajax.call([\n { methodname: 'tool_trigger_step_by_type', args: {'steptype': valfilter} },\n ])[0].done(function(response) {\n updateStepOptions(response);\n });\n }\n\n /**\n * Get the event name that triggers this workflow.\n *\n * @return {string} The event name.\n */\n function getEventName() {\n var eventname = $('[name=eventtomonitor]').val();\n\n return eventname;\n }\n\n /**\n * Render the correct form for a particular step (or type of step)\n *\n * @param {string} steptype The step category (triggers, filters, lookups)\n * @param {string} stepclass The step class (\\tool_trigger\\steps\\triggers\\http_post_trigger_step, ...)\n * @param {Object} formdefaults Default values to display in a new form\n * @param {string} formsubmission Serialized (via jQuery().serialize()) form submission values to load\n * into the form, when re-displaying a form that has failed validation.\n */\n function renderStepForm(steptype, stepclass, formdefaults, formsubmission, steporder) {\n if (formdefaults === undefined) {\n formdefaults = '';\n }\n\n if (formsubmission === undefined) {\n formsubmission = '';\n }\n\n if (steporder === undefined) {\n steporder = 0;\n }\n\n modalObj.setBody(spinner);\n modalObj.setBody(\n Fragment.loadFragment(\n 'tool_trigger',\n 'new_step_form',\n contextid,\n {\n 'steptype' : steptype,\n 'stepclass' : stepclass,\n 'defaults': JSON.stringify(formdefaults),\n 'ajaxformdata': formsubmission,\n 'event': getEventName(),\n 'existingsteps': JSON.stringify(getParentFormSteps()),\n 'steporder': steporder,\n }\n )\n );\n }\n\n /**\n *\n */\n function setupModalChangeHandlers() {\n // Add event listener for step type select onchange.\n $('body').on('change', '[name=type]', function() {\n getStepsOfType(this.value);\n });\n\n // Add event listener for step select onchange.\n $('body').on('change', '[name=stepclass]', function() {\n var steptype = $('[name=type]').val();\n var stepclass = this.value;\n renderStepForm(steptype, stepclass, '', '', -1);\n });\n }\n\n function swapSteps(steps, pos1, pos2){\n // Milliseconds of animation.\n var duration = 400;\n\n // Swap the steps in the JSON list.\n var step1 = steps[pos1];\n var step2 = steps[pos2];\n step1.steporder = pos2;\n step2.steporder = pos2;\n steps[pos1] = step2;\n steps[pos2] = step1;\n\n var $rows = $('tr.tool-trigger-step-table-row');\n var $row1 = $rows.eq(pos1);\n var $row2 = $rows.eq(pos2);\n var row1height = $row1.height();\n var row2height = $row2.height();\n var row1color = $row1.find('th').css('background-color');\n var row2color = $row2.find('th').css('background-color');\n\n $([$row1, $row2]).each(function(idx, row){\n row[0].style.position = 'relative';\n row[0].style.top = '0';\n row[0].style.transition = 'top ' + duration + 'ms';\n row.find('th,td').each(function(idx, cell){\n cell.style.transition = 'background-color ' + duration + 'ms';\n });\n });\n window.setTimeout(function(){\n $row1[0].style.top = row2height + \"px\";\n $row1.find('td,th').each(function(idx, cell){\n cell.style.backgroundColor = row2color;\n });\n $row2[0].style.top = (-1 * row1height) + \"px\";\n $row2.find('td,th').each(function(idx, cell){\n cell.style.backgroundColor = row1color;\n });\n });\n\n // Also fade out the up/down buttons on the rows, because otherwise it can\n // be visually confusing.\n $row1.find('.tool-trigger-step-movedown').fadeTo(duration, 0.1);\n // Replace the form once the animation is finished.\n $row2.find('.tool-trigger-step-moveup')\n .fadeTo(duration, 0.1)\n .promise()\n .always(\n function(){\n setCurrentFormSteps(steps);\n updateTable(steps);\n }\n );\n\n }\n\n /**\n * Display the action icons for the steps table, and set up\n * handlers on them to make them clickable.\n */\n function setupTableHandlers() {\n $('.tool-trigger-step-moveup')\n // Don't show an up arrow for the top row of the table.\n .slice(1)\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n var steps = getParentFormSteps();\n\n // Already at the top. Can't move any higher!\n var steporder = $(this).data('steporder');\n if (steporder === 0) {\n return true;\n }\n\n swapSteps(steps, steporder - 1 , steporder);\n\n return true;\n });\n\n $('.tool-trigger-step-movedown')\n // Don't show a down arrow for the bottom row of the table.\n .slice(0, -1)\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n var steps = getParentFormSteps();\n\n // Already at the end. Can't move any further!\n var steporder = $(this).data('steporder');\n if (steporder >= steps.length - 1) {\n return true;\n }\n\n swapSteps(steps, steporder, steporder + 1);\n\n return true;\n });\n\n $('.tool-trigger-step-edit')\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n\n modalObj.setBody(spinner);\n modalObj.show();\n var steps = getParentFormSteps();\n var steporder = $(this).data('steporder');\n var step = steps[steporder];\n\n renderStepForm(\n step['type'],\n step['stepclass'],\n step,\n undefined,\n steporder\n );\n });\n\n $('.tool-trigger-step-delete')\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n var steps = getParentFormSteps();\n\n // Remove it from the array.\n var steporder = $(this).data('steporder');\n steps.splice(steporder, 1);\n // Adjust the steporder of all subsequent steps.\n if (steporder <= steps.length) {\n steps.slice(steporder).forEach(\n function(step) {\n step.steporder = step.steporder - 1;\n }\n );\n }\n\n setCurrentFormSteps(steps);\n $(this).closest('tr').fadeOut(\n function(){\n updateTable(steps);\n }\n );\n\n return true;\n });\n }\n\n /**\n * Initialise the class.\n *\n * @public\n */\n StepSelect.init = function(context) {\n // Save the context ID in a closure variable.\n contextid = context;\n\n // Get the Title String.\n Str.get_string('modaltitle', 'tool_trigger').then(function(title) {\n // Create the Modal.\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: title,\n body: spinner,\n large: true\n }, $('#id_stepmodalbutton'))\n .done(function(modal) {\n modalObj = modal;\n modalObj.getRoot().on(ModalEvents.save, processModalForm);\n modalObj.getRoot().on(ModalEvents.hidden, updateModalBody);\n setupModalChangeHandlers();\n updateModalBody();\n });\n });\n\n // Setup click handlers on the edit/delete icons in the steps table.\n setupTableHandlers();\n };\n\n return StepSelect;\n });"],"file":"step_select.min.js"}
\ No newline at end of file
+{"version":3,"file":"step_select.min.js","sources":["../src/step_select.js"],"sourcesContent":["// This file is part of Moodle - http://moodle.org/\n//\n// Moodle is free software: you can redistribute it and/or modify\n// it under the terms of the GNU General Public License as published by\n// the Free Software Foundation, either version 3 of the License, or\n// (at your option) any later version.\n//\n// Moodle is distributed in the hope that it will be useful,\n// but WITHOUT ANY WARRANTY; without even the implied warranty of\n// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n// GNU General Public License for more details.\n//\n// You should have received a copy of the GNU General Public License\n// along with Moodle. If not, see .\n\n/**\n * Workflow step select javascript.\n *\n * @module tool_trigger/workflow\n * @class Workflow\n * @copyright 2018 Matt Porritt \n * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later\n * @since 3.4\n */\n\ndefine(\n ['jquery', 'core/str', 'core/modal_factory', 'core/modal_events','core/templates', 'core/ajax', 'core/fragment',\n 'core/notification'],\n function ($, Str, ModalFactory, ModalEvents, Templates, ajax, Fragment, Notification) {\n\n /**\n * Module level variables.\n */\n var StepSelect = {};\n var contextid;\n var modalObj;\n var spinner = ''\n + 'Loading... '\n + '
';\n\n /**\n * Retrieves the steps serialized to JSON in the stepsjson hidden form field.\n */\n function getParentFormSteps() {\n var stepsjson = $('[name=stepjson]').val();\n var steps = [];\n if (stepsjson !== '') {\n steps = JSON.parse(stepsjson);\n }\n return steps;\n }\n\n /**\n * Updates the steps stored in the hidden form field\n *\n * @param {object} steps the steps to store\n */\n function setCurrentFormSteps(steps) {\n $('[name=stepjson]').val(JSON.stringify(steps));\n // Set the flag field that indicates there was a change to the steps.\n $('[name=isstepschanged]').val(1);\n }\n\n /**\n * Updates the body of the modal window.\n *\n * @private\n */\n function updateModalBody() {\n var formdata = {};\n var params = {jsonformdata: JSON.stringify(formdata)};\n modalObj.setBody(spinner);\n modalObj.setBody(Fragment.loadFragment('tool_trigger', 'new_base_form', contextid, params));\n }\n\n /**\n * Updates the table with the new step data.\n *\n * @param {array} stepData The steps to update in the table\n */\n function updateTable(stepData) {\n // Format data for template.\n // Filter out only the fields we want for each step, and make sure the \"steporder\" values\n // are correct.\n var rows = stepData.map(\n function(step, stepidx) {\n return {\n name: step.name,\n typedesc: step.typedesc,\n stepdesc: step.stepdesc,\n steporder: stepidx\n };\n }\n );\n var tableData = {'rows': rows};\n Templates.render(\n 'tool_trigger/workflow_steps',\n tableData\n ).then(function(html) {\n $('#steps-table').html(html);\n setupTableHandlers();\n }).fail(function() {\n Notification.exception({message: 'Error updating steps table'});\n });\n }\n\n /**\n * Updates Moodle form with selected information.\n *\n * @param {event} e the event emitted from the modal close.\n * @private\n */\n function processModalForm(e) {\n e.preventDefault(); // Stop modal from closing.\n\n // Form data.\n var $stepform = modalObj.getRoot().find('form');\n // Use jQuery().serializeArray() to collect the values of all the form fields.\n // Then convert from its array-of-objects output format into a single object.\n var curstep = $stepform.serializeArray().reduce(\n function(finalobj, field) {\n // If field ends with [], the form el was an array.\n if (field.name.endsWith('[]')) {\n var fieldname = field.name.substring(0, field.name.length - 2);\n if (finalobj[fieldname] === undefined) {\n finalobj[fieldname] = [field.value];\n } else {\n finalobj[fieldname].push(field.value);\n }\n } else if (field.name !== 'sesskey'\n && !field.name.startsWith('_qf__')\n && !field.value.startsWith('_qf__')) {\n // Filter out the sesskey and formslib system fields.\n finalobj[field.name] = field.value;\n }\n\n return finalobj;\n },\n {}\n );\n\n // Add the description string for the step class and type, in order to make later rendering\n // easier...\n curstep['stepdesc'] = $('[name=stepclass] option:selected').text();\n curstep['typedesc'] = $('[name=type] option:selected').text();\n\n // Submit form via ajax to do server side validation.\n ajax.call([{\n methodname: 'tool_trigger_validate_form',\n args: {\n stepclass: curstep['stepclass'],\n jsonformdata: JSON.stringify($stepform.serialize())\n },\n }])[0].done(function() {\n\n // Validation succeeded! Update the parent form's hidden steps data, and update\n // the table.\n var steps = getParentFormSteps();\n\n if (curstep.steporder >= 0) {\n // If we were editing an existing step, swap it into place in the list.\n steps[curstep.steporder] = curstep;\n } else {\n // If we were creating a new step, add it to the end of the list.\n steps.push(curstep);\n curstep.steporder = steps.length - 1;\n }\n setCurrentFormSteps(steps); // Update steps in hidden form field\n updateTable(steps); // Update table in workflow form.\n modalObj.hide(); // Hide the modal.\n\n }).fail(function() {\n\n // Validation failed! Don't close the modal, don't update anything on the parent\n // form.\n renderStepForm(curstep['type'], curstep['stepclass'], '', $stepform.serialize());\n });\n }\n\n /**\n * Updates the step list in the step modal edit form,\n * with only the steps that correspond to the selected\n * step type.\n *\n * @param {array} events Array of steps to update selection with.\n */\n function updateStepOptions(events) {\n\n // First clear the existing options in the select element.\n $('[name=stepclass]').empty().append($('', {\n value: '',\n text : 'Choose...'\n }));\n\n // Update the select with applicable events.\n $.each(events, function (i, event) {\n $('[name=stepclass]').append($(' ', {\n value: event.class,\n text : event.name\n }));\n });\n }\n\n /**\n * Gets a list of filtered steps based on the selected step type.\n * Triggers updating of the form step select element.\n *\n * @param {string} valfilter The filter area.\n */\n function getStepsOfType(valfilter) {\n ajax.call([\n { methodname: 'tool_trigger_step_by_type', args: {'steptype': valfilter} },\n ])[0].done(function(response) {\n updateStepOptions(response);\n });\n }\n\n /**\n * Get the event name that triggers this workflow.\n *\n * @return {string} The event name.\n */\n function getEventName() {\n var eventname = $('[name=eventtomonitor]').val();\n\n return eventname;\n }\n\n /**\n * Render the correct form for a particular step (or type of step)\n *\n * @param {string} steptype The step category (triggers, filters, lookups)\n * @param {string} stepclass The step class (\\tool_trigger\\steps\\triggers\\http_post_trigger_step, ...)\n * @param {Object} formdefaults Default values to display in a new form\n * @param {string} formsubmission Serialized (via jQuery().serialize()) form submission values to load\n * into the form, when re-displaying a form that has failed validation.\n * @param {int} steporder the order of this step.\n */\n function renderStepForm(steptype, stepclass, formdefaults, formsubmission, steporder) {\n if (formdefaults === undefined) {\n formdefaults = '';\n }\n\n if (formsubmission === undefined) {\n formsubmission = '';\n }\n\n if (steporder === undefined) {\n steporder = 0;\n }\n\n modalObj.setBody(spinner);\n modalObj.setBody(\n Fragment.loadFragment(\n 'tool_trigger',\n 'new_step_form',\n contextid,\n {\n 'steptype' : steptype,\n 'stepclass' : stepclass,\n 'defaults': JSON.stringify(formdefaults),\n 'ajaxformdata': formsubmission,\n 'event': getEventName(),\n 'existingsteps': JSON.stringify(getParentFormSteps()),\n 'steporder': steporder,\n }\n )\n );\n }\n\n /**\n * Set the handlers for the Modal changing.\n */\n function setupModalChangeHandlers() {\n // Add event listener for step type select onchange.\n $('body').on('change', '[name=type]', function() {\n getStepsOfType(this.value);\n });\n\n // Add event listener for step select onchange.\n $('body').on('change', '[name=stepclass]', function() {\n var steptype = $('[name=type]').val();\n var stepclass = this.value;\n renderStepForm(steptype, stepclass, '', '', -1);\n });\n }\n\n /**\n * Swaps the position of 2 steps\n *\n * @param {object} steps the current steps\n * @param {int} pos1 the first position to swap\n * @param {int} pos2 the second position to swap\n */\n function swapSteps(steps, pos1, pos2){\n // Milliseconds of animation.\n var duration = 400;\n\n // Swap the steps in the JSON list.\n var step1 = steps[pos1];\n var step2 = steps[pos2];\n step1.steporder = pos2;\n step2.steporder = pos2;\n steps[pos1] = step2;\n steps[pos2] = step1;\n\n var $rows = $('tr.tool-trigger-step-table-row');\n var $row1 = $rows.eq(pos1);\n var $row2 = $rows.eq(pos2);\n var row1height = $row1.height();\n var row2height = $row2.height();\n var row1color = $row1.find('th').css('background-color');\n var row2color = $row2.find('th').css('background-color');\n\n $([$row1, $row2]).each(function(idx, row){\n row[0].style.position = 'relative';\n row[0].style.top = '0';\n row[0].style.transition = 'top ' + duration + 'ms';\n row.find('th,td').each(function(idx, cell){\n cell.style.transition = 'background-color ' + duration + 'ms';\n });\n });\n window.setTimeout(function(){\n $row1[0].style.top = row2height + \"px\";\n $row1.find('td,th').each(function(idx, cell){\n cell.style.backgroundColor = row2color;\n });\n $row2[0].style.top = (-1 * row1height) + \"px\";\n $row2.find('td,th').each(function(idx, cell){\n cell.style.backgroundColor = row1color;\n });\n });\n\n // Also fade out the up/down buttons on the rows, because otherwise it can\n // be visually confusing.\n $row1.find('.tool-trigger-step-movedown').fadeTo(duration, 0.1);\n // Replace the form once the animation is finished.\n $row2.find('.tool-trigger-step-moveup')\n .fadeTo(duration, 0.1)\n .promise()\n .always(\n function(){\n setCurrentFormSteps(steps);\n updateTable(steps);\n }\n );\n\n }\n\n /**\n * Display the action icons for the steps table, and set up\n * handlers on them to make them clickable.\n */\n function setupTableHandlers() {\n $('.tool-trigger-step-moveup')\n // Don't show an up arrow for the top row of the table.\n .slice(1)\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n var steps = getParentFormSteps();\n\n // Already at the top. Can't move any higher!\n var steporder = $(this).data('steporder');\n if (steporder === 0) {\n return true;\n }\n\n swapSteps(steps, steporder - 1 , steporder);\n\n return true;\n });\n\n $('.tool-trigger-step-movedown')\n // Don't show a down arrow for the bottom row of the table.\n .slice(0, -1)\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n var steps = getParentFormSteps();\n\n // Already at the end. Can't move any further!\n var steporder = $(this).data('steporder');\n if (steporder >= steps.length - 1) {\n return true;\n }\n\n swapSteps(steps, steporder, steporder + 1);\n\n return true;\n });\n\n $('.tool-trigger-step-edit')\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n\n modalObj.setBody(spinner);\n modalObj.show();\n var steps = getParentFormSteps();\n var steporder = $(this).data('steporder');\n var step = steps[steporder];\n\n renderStepForm(\n step['type'],\n step['stepclass'],\n step,\n undefined,\n steporder\n );\n });\n\n $('.tool-trigger-step-delete')\n .removeClass('tool-trigger-initial-hidden')\n .on('click', function() {\n var steps = getParentFormSteps();\n\n // Remove it from the array.\n var steporder = $(this).data('steporder');\n steps.splice(steporder, 1);\n // Adjust the steporder of all subsequent steps.\n if (steporder <= steps.length) {\n steps.slice(steporder).forEach(\n function(step) {\n step.steporder = step.steporder - 1;\n }\n );\n }\n\n setCurrentFormSteps(steps);\n $(this).closest('tr').fadeOut(\n function(){\n updateTable(steps);\n }\n );\n\n return true;\n });\n }\n\n /**\n * Initialise the class.\n *\n * @param {int} context the context id for the function from PHP.\n * @public\n */\n StepSelect.init = function(context) {\n // Save the context ID in a closure variable.\n contextid = context;\n\n // Get the Title String.\n Str.get_string('modaltitle', 'tool_trigger').then(function(title) {\n // Create the Modal.\n ModalFactory.create({\n type: ModalFactory.types.SAVE_CANCEL,\n title: title,\n body: spinner,\n large: true\n }, $('#id_stepmodalbutton'))\n .done(function(modal) {\n modalObj = modal;\n modalObj.getRoot().on(ModalEvents.save, processModalForm);\n modalObj.getRoot().on(ModalEvents.hidden, updateModalBody);\n setupModalChangeHandlers();\n updateModalBody();\n });\n });\n\n // Setup click handlers on the edit/delete icons in the steps table.\n setupTableHandlers();\n };\n\n return StepSelect;\n });"],"names":["define","$","Str","ModalFactory","ModalEvents","Templates","ajax","Fragment","Notification","contextid","modalObj","StepSelect","spinner","getParentFormSteps","stepsjson","val","steps","JSON","parse","setCurrentFormSteps","stringify","updateModalBody","params","jsonformdata","setBody","loadFragment","updateTable","stepData","tableData","map","step","stepidx","name","typedesc","stepdesc","steporder","render","then","html","setupTableHandlers","fail","exception","message","processModalForm","e","preventDefault","$stepform","getRoot","find","curstep","serializeArray","reduce","finalobj","field","endsWith","fieldname","substring","length","undefined","value","push","startsWith","text","call","methodname","args","stepclass","serialize","done","hide","renderStepForm","getStepsOfType","valfilter","response","events","empty","append","each","i","event","class","steptype","formdefaults","formsubmission","swapSteps","pos1","pos2","step1","step2","$rows","$row1","eq","$row2","row1height","height","row2height","row1color","css","row2color","idx","row","style","position","top","transition","cell","window","setTimeout","backgroundColor","fadeTo","promise","always","slice","removeClass","on","this","data","show","splice","forEach","closest","fadeOut","init","context","get_string","title","create","type","types","SAVE_CANCEL","body","large","modal","save","hidden"],"mappings":"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"}
\ No newline at end of file
diff --git a/amd/src/import_workflow.js b/amd/src/import_workflow.js
index bdbe1e0..fc6740a 100644
--- a/amd/src/import_workflow.js
+++ b/amd/src/import_workflow.js
@@ -17,7 +17,6 @@
* Workflow step select javascript.
*
* @module tool_trigger/workflow
- * @package tool_trigger
* @class Workflow
* @copyright 2018 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -53,6 +52,8 @@ define(
/**
* Updates Moodle form with selected information.
+ *
+ * @param {event} e The event from the modal submitting.
* @private
*/
function processModalForm(e) {
@@ -99,6 +100,7 @@ define(
/**
* Initialise the class.
*
+ * @param {int} context the context id from PHP.
* @public
*/
ImportWorkflow.init = function(context) {
diff --git a/amd/src/step_select.js b/amd/src/step_select.js
index 0b1d7ea..d96f90d 100644
--- a/amd/src/step_select.js
+++ b/amd/src/step_select.js
@@ -17,7 +17,6 @@
* Workflow step select javascript.
*
* @module tool_trigger/workflow
- * @package tool_trigger
* @class Workflow
* @copyright 2018 Matt Porritt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
@@ -53,6 +52,8 @@ define(
/**
* Updates the steps stored in the hidden form field
+ *
+ * @param {object} steps the steps to store
*/
function setCurrentFormSteps(steps) {
$('[name=stepjson]').val(JSON.stringify(steps));
@@ -72,6 +73,11 @@ define(
modalObj.setBody(Fragment.loadFragment('tool_trigger', 'new_base_form', contextid, params));
}
+ /**
+ * Updates the table with the new step data.
+ *
+ * @param {array} stepData The steps to update in the table
+ */
function updateTable(stepData) {
// Format data for template.
// Filter out only the fields we want for each step, and make sure the "steporder" values
@@ -100,6 +106,8 @@ define(
/**
* Updates Moodle form with selected information.
+ *
+ * @param {event} e the event emitted from the modal close.
* @private
*/
function processModalForm(e) {
@@ -113,7 +121,7 @@ define(
function(finalobj, field) {
// If field ends with [], the form el was an array.
if (field.name.endsWith('[]')) {
- let fieldname = field.name.substring(0, field.name.length - 2);
+ var fieldname = field.name.substring(0, field.name.length - 2);
if (finalobj[fieldname] === undefined) {
finalobj[fieldname] = [field.value];
} else {
@@ -174,7 +182,7 @@ define(
* with only the steps that correspond to the selected
* step type.
*
- * @param array events Array of steps to update selection with.
+ * @param {array} events Array of steps to update selection with.
*/
function updateStepOptions(events) {
@@ -197,7 +205,7 @@ define(
* Gets a list of filtered steps based on the selected step type.
* Triggers updating of the form step select element.
*
- * @param string varfilter The filter area.
+ * @param {string} valfilter The filter area.
*/
function getStepsOfType(valfilter) {
ajax.call([
@@ -226,6 +234,7 @@ define(
* @param {Object} formdefaults Default values to display in a new form
* @param {string} formsubmission Serialized (via jQuery().serialize()) form submission values to load
* into the form, when re-displaying a form that has failed validation.
+ * @param {int} steporder the order of this step.
*/
function renderStepForm(steptype, stepclass, formdefaults, formsubmission, steporder) {
if (formdefaults === undefined) {
@@ -260,7 +269,7 @@ define(
}
/**
- *
+ * Set the handlers for the Modal changing.
*/
function setupModalChangeHandlers() {
// Add event listener for step type select onchange.
@@ -276,6 +285,13 @@ define(
});
}
+ /**
+ * Swaps the position of 2 steps
+ *
+ * @param {object} steps the current steps
+ * @param {int} pos1 the first position to swap
+ * @param {int} pos2 the second position to swap
+ */
function swapSteps(steps, pos1, pos2){
// Milliseconds of animation.
var duration = 400;
@@ -422,6 +438,7 @@ define(
/**
* Initialise the class.
*
+ * @param {int} context the context id for the function from PHP.
* @public
*/
StepSelect.init = function(context) {
diff --git a/classes/event_processor.php b/classes/event_processor.php
index 00c2f71..322a03f 100644
--- a/classes/event_processor.php
+++ b/classes/event_processor.php
@@ -27,8 +27,6 @@
use tool_trigger\helper\processor_helper;
use tool_trigger\task\process_workflows;
-defined('MOODLE_INTERNAL') || die();
-
/**
* Process trigger system events.
*
@@ -213,6 +211,7 @@ private function process_realtime_workflow($workflow, $evententry) {
$stepresults = [];
$success = false;
$prevstep = null;
+ $errored = false;
foreach ($steps as $step) {
try {
$outertransaction = $DB->is_transaction_started();
@@ -234,8 +233,16 @@ private function process_realtime_workflow($workflow, $evententry) {
} catch (\Exception $e) {
if (!($e->getMessage() === 'debounce')) {
debugging('Error execute workflow step: ' . $step->id . ', ' . $step->stepclass . ' ' . $e->getMessage());
+ $errored = true;
+ }
+ } finally {
+ if (!$outertransaction && $DB->is_transaction_started()) {
+ $DB->force_transaction_rollback();
}
+ }
+ // Now that all transactions at the step level are concluded, we can safely log + retry.
+ if ($errored) {
// Record step fail if debugging enabled.
if (!empty($runid)) {
self::record_step_trigger($step, $prevstep, $runid, ['error' => $e->getMessage()]);
@@ -254,13 +261,9 @@ private function process_realtime_workflow($workflow, $evententry) {
$this->insert_queue_records([$queuerecord]);
$success = true;
break;
- } finally {
- if (!$outertransaction && $DB->is_transaction_started()) {
- $DB->force_transaction_rollback();
- }
}
}
- if (!$success) {
+ if (!$success && !$errored) {
debugging('Error execute workflow: ' . $workflow->id . ' failed and will not be rerun.');
}
} catch (\Exception $exception) {
diff --git a/classes/helper/datafield_manager.php b/classes/helper/datafield_manager.php
index 38814a5..71fa316 100644
--- a/classes/helper/datafield_manager.php
+++ b/classes/helper/datafield_manager.php
@@ -25,8 +25,6 @@
namespace tool_trigger\helper;
-defined('MOODLE_INTERNAL') || die;
-
/**
* A lookup step that takes a user's ID and adds standard data about the
* user.
@@ -40,6 +38,9 @@ trait datafield_manager {
protected $datafields = [];
+ /** @var string regex to determine data fields - should ideally be readonly */
+ protected $datafieldregex = '/\{([-_A-Za-z0-9]+)\}/u';
+
/**
* Get the data fields.
*
@@ -98,7 +99,12 @@ public function update_datafields($event, $stepresults) {
if (isset($newfields['other']) && is_array($newfields['other'])) {
foreach ($newfields['other'] as $key => $value) {
if (is_scalar($value) || is_null($value)) {
- $newfields["other_{$key}"] = $value;
+ // Retrieve ID from eventid in other data.
+ if ($key == 'eventid') {
+ $newfields['id'] = $value;
+ } else {
+ $newfields["other_{$key}"] = $value;
+ }
}
}
unset($newfields['other']);
@@ -162,7 +168,7 @@ public function render_datafields($templatestr, $event = null, $stepresults = nu
};
return preg_replace_callback(
- '/\{([-_A-Za-z0-9]+)\}/u',
+ $this->datafieldregex,
$callback,
$templatestr
);
diff --git a/classes/helper/processor_helper.php b/classes/helper/processor_helper.php
index a602c35..6b4cf0f 100644
--- a/classes/helper/processor_helper.php
+++ b/classes/helper/processor_helper.php
@@ -25,8 +25,6 @@
namespace tool_trigger\helper;
-defined('MOODLE_INTERNAL') || die();
-
trait processor_helper {
/**
@@ -45,6 +43,12 @@ public function restore_event(\stdClass $data) {
if ($data['other'] === false) {
$data['other'] = array();
}
+
+ // Insert eventid into other data.
+ if (isset($data['id'])) {
+ $data['other']['eventid'] = $data['id'];
+ }
+
unset($data['origin']);
unset($data['ip']);
unset($data['realuserid']);
diff --git a/classes/json/json_export.php b/classes/json/json_export.php
index f62e7d8..1971fc7 100644
--- a/classes/json/json_export.php
+++ b/classes/json/json_export.php
@@ -24,8 +24,6 @@
namespace tool_trigger\json;
-defined('MOODLE_INTERNAL') || die();
-
/**
* Process trigger system events.
*
diff --git a/classes/learn_process.php b/classes/learn_process.php
index 15e300e..9d11b9a 100644
--- a/classes/learn_process.php
+++ b/classes/learn_process.php
@@ -23,7 +23,6 @@
*/
namespace tool_trigger;
-defined('MOODLE_INTERNAL') || die();
/**
* Process learnt events.
*
diff --git a/classes/output/manageworkflows/renderer.php b/classes/output/manageworkflows/renderer.php
index 603ddb2..7741dfb 100644
--- a/classes/output/manageworkflows/renderer.php
+++ b/classes/output/manageworkflows/renderer.php
@@ -24,8 +24,6 @@
namespace tool_trigger\output\manageworkflows;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Renderer class for manage rules page.
*
diff --git a/classes/output/workflowhistory/renderer.php b/classes/output/workflowhistory/renderer.php
index a851828..d8e5a57 100644
--- a/classes/output/workflowhistory/renderer.php
+++ b/classes/output/workflowhistory/renderer.php
@@ -104,7 +104,7 @@ public function render_workflowhistory_table($workflowid, $searchparams = [], $d
$statuswhere = [];
if (!empty($searchparams['filterpassed'])) {
$sqlparams['filterpassed'] = $searchparams['filterpassed'];
- $statuswhere[] = ' ((tfh.failedstep = 0 OR tfh.failedstep IS NULL) AND (tfh.errorstep = 0 OR tfh.errorstep IS NULL))';
+ $statuswhere[] = ' ((tfh.failedstep IS NULL) AND (tfh.errorstep IS NULL))';
}
if (!empty($searchparams['filtercancelled'])) {
@@ -119,12 +119,12 @@ public function render_workflowhistory_table($workflowid, $searchparams = [], $d
if (!empty($searchparams['filtererrored'])) {
$sqlparams['filtererrored'] = $searchparams['filtererrored'];
- $statuswhere[] = ' (tfh.errorstep > 0) ';
+ $statuswhere[] = ' (tfh.errorstep IS NOT NULL) ';
}
if (!empty($searchparams['filterfailed'])) {
$sqlparams['filterfailed'] = $searchparams['filterfailed'];
- $statuswhere[] = ' (tfh.failedstep > 0) ';
+ $statuswhere[] = ' (tfh.failedstep IS NOT NULL) ';
}
if (count($statuswhere) > 0) {
diff --git a/classes/output/workflowhistory/workflow.php b/classes/output/workflowhistory/workflow.php
index 9467178..075da94 100644
--- a/classes/output/workflowhistory/workflow.php
+++ b/classes/output/workflowhistory/workflow.php
@@ -135,7 +135,7 @@ public function col_runstatus($run) {
global $DB;
// Return a badge for the status.
- if (!empty($run->errorstep)) {
+ if (isset($run->errorstep)) {
$string = get_string('errorstep', 'tool_trigger', $run->errorstep + 1);
$spanclass = 'badge badge-warning';
$maxretries = get_config('tool_trigger', 'autorerunmaxtries');
@@ -144,13 +144,13 @@ public function col_runstatus($run) {
$string .= get_string('errorstepretrypending', 'tool_trigger', $run->attemptnum);
}
// Handle debounce statuses.
- } else if (!empty($run->failedstep) && ((int) $run->failedstep === \tool_trigger\task\process_workflows::STATUS_CANCELLED)) {
+ } else if (isset($run->failedstep) && ((int) $run->failedstep === \tool_trigger\task\process_workflows::STATUS_CANCELLED)) {
$string = get_string('cancelled');
$spanclass = 'badge badge-info';
- } else if (!empty($run->failedstep) && ((int) $run->failedstep === \tool_trigger\task\process_workflows::STATUS_DEFERRED)) {
+ } else if (isset($run->failedstep) && ((int) $run->failedstep === \tool_trigger\task\process_workflows::STATUS_DEFERRED)) {
$string = get_string('deferred', 'tool_trigger');
$spanclass = 'badge badge-info';
- } else if (!empty($run->failedstep)) {
+ } else if (isset($run->failedstep)) {
$string = get_string('failedstep', 'tool_trigger', $run->failedstep + 1);
$spanclass = 'badge badge-danger';
} else {
diff --git a/classes/privacy/provider.php b/classes/privacy/provider.php
index 310526c..fcaf22a 100644
--- a/classes/privacy/provider.php
+++ b/classes/privacy/provider.php
@@ -24,8 +24,6 @@
*/
namespace tool_trigger\privacy;
-defined('MOODLE_INTERNAL') || die();
-
use core_privacy\local\metadata\collection;
use core_privacy\local\request\contextlist;
use core_privacy\local\request\approved_contextlist;
diff --git a/classes/steps/actions/assign_cohort_action_step.php b/classes/steps/actions/assign_cohort_action_step.php
index c920747..faff968 100644
--- a/classes/steps/actions/assign_cohort_action_step.php
+++ b/classes/steps/actions/assign_cohort_action_step.php
@@ -27,8 +27,8 @@
defined('MOODLE_INTERNAL') || die;
require_once($CFG->dirroot.'/cohort/lib.php');
-class assign_cohort_action_step extends base_action_step
-{
+class assign_cohort_action_step extends base_action_step {
+
use \tool_trigger\helper\datafield_manager;
diff --git a/classes/steps/actions/base_action_step.php b/classes/steps/actions/base_action_step.php
index 0718dff..bb8a5c5 100644
--- a/classes/steps/actions/base_action_step.php
+++ b/classes/steps/actions/base_action_step.php
@@ -29,8 +29,6 @@
use tool_trigger\steps\base\base_step;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Base action step class.
*
diff --git a/classes/steps/actions/email_action_step.php b/classes/steps/actions/email_action_step.php
index b68bc8a..1ccdb9c 100644
--- a/classes/steps/actions/email_action_step.php
+++ b/classes/steps/actions/email_action_step.php
@@ -25,8 +25,6 @@
namespace tool_trigger\steps\actions;
-defined('MOODLE_INTERNAL') || die;
-
/**
* email action step class.
*
diff --git a/classes/steps/actions/http_post_action_step.php b/classes/steps/actions/http_post_action_step.php
index d7f5390..2b6c2c8 100644
--- a/classes/steps/actions/http_post_action_step.php
+++ b/classes/steps/actions/http_post_action_step.php
@@ -15,7 +15,7 @@
// along with Moodle. If not, see .
/**
- * HTTP Post action step class.
+ * HTTP action step class.
*
* @package tool_trigger
* @copyright Matt Porritt
@@ -24,10 +24,8 @@
namespace tool_trigger\steps\actions;
-defined('MOODLE_INTERNAL') || die;
-
/**
- * HTTP Post action step class.
+ * HTTP action step class.
*
* @package tool_trigger
* @copyright Matt Porritt
@@ -37,9 +35,22 @@ class http_post_action_step extends base_action_step {
use \tool_trigger\helper\datafield_manager;
+ /**
+ * Supported HTTP methods.
+ */
+ const SUPPORTED_HTTP_METHODS = [
+ 'POST' => 'POST',
+ 'GET' => 'GET',
+ 'PUT' => 'PUT',
+ 'DELETE' => 'DELETE',
+ 'PATCH' => 'PATCH',
+ ];
+
protected $url;
+ protected $httpmethod;
protected $headers;
protected $params;
+ private $httphandler = null;
/**
* The fields supplied by this step.
@@ -54,6 +65,7 @@ class http_post_action_step extends base_action_step {
protected function init() {
$this->url = $this->data['url'];
+ $this->httpmethod = !empty($this->data['httpmethod']) ? $this->data['httpmethod'] : 'POST';
$this->headers = $this->data['httpheaders'];
$this->params = $this->data['httpparams'];
$this->jsonencode = $this->data['jsonencode'];
@@ -83,8 +95,6 @@ public static function get_step_desc() {
return get_string('httppostactionstepdesc', 'tool_trigger');
}
- private $httphandler = null;
-
/**
* Kinda hacky... unit testing requires us to specify a different http handler for guzzle to use.
* That's really the only reason we need this method!
@@ -145,7 +155,7 @@ public function execute($step, $trigger, $event, $stepresults) {
$params = json_encode($output);
}
- $request = new \GuzzleHttp\Psr7\Request('POST', $url, $headers, $params);
+ $request = new \GuzzleHttp\Psr7\Request($this->httpmethod, $url, $headers, $params);
$client = $this->get_http_client();
try {
@@ -156,12 +166,14 @@ public function execute($step, $trigger, $event, $stepresults) {
$stepresults['http_response_status_code'] = $response->getStatusCode();
$stepresults['http_response_status_message'] = $response->getReasonPhrase();
- $stepresults['http_response_body'] = $response->getBody();
+ $stepresults['http_response_body'] = $response->getBody()->getContents();
if ($response->getStatusCode() != $this->expectedresponse) {
// If we weren't expecting this response, throw an exception.
// The error will be caught and rerun.
- throw new \invalid_response_exception("HTTP Response code expected was {$this->expectedresponse}, received {$response->getStatusCode()}");
+ throw new \invalid_response_exception(
+ "HTTP Response code expected was {$this->expectedresponse}, received {$response->getStatusCode()}"
+ );
}
return array(true, $stepresults);
@@ -182,6 +194,11 @@ public function form_definition_extra($form, $mform, $customdata) {
$mform->addRule('url', get_string('required'), 'required');
$mform->addHelpButton('url', 'httpostactionurl', 'tool_trigger');
+ // HTTP method.
+ $mform->addElement('select', 'httpmethod', get_string ('httpostmethod', 'tool_trigger'), self::SUPPORTED_HTTP_METHODS);
+ $mform->setType('httpmethod', PARAM_TEXT);
+ $mform->addHelpButton('httpmethod', 'httpostmethod', 'tool_trigger');
+
// Headers.
$attributes = array('cols' => '50', 'rows' => '2');
$mform->addElement('textarea', 'httpheaders', get_string ('httpostactionheaders', 'tool_trigger'), $attributes);
diff --git a/classes/steps/actions/logdump_action_step.php b/classes/steps/actions/logdump_action_step.php
index 216b50f..4c2f009 100644
--- a/classes/steps/actions/logdump_action_step.php
+++ b/classes/steps/actions/logdump_action_step.php
@@ -16,8 +16,6 @@
namespace tool_trigger\steps\actions;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Action step that just does a var_dump to the logs.
*
diff --git a/classes/steps/actions/role_assign_action_step.php b/classes/steps/actions/role_assign_action_step.php
index 3136308..8df3caa 100644
--- a/classes/steps/actions/role_assign_action_step.php
+++ b/classes/steps/actions/role_assign_action_step.php
@@ -24,8 +24,6 @@
namespace tool_trigger\steps\actions;
-defined('MOODLE_INTERNAL') || die;
-
class role_assign_action_step extends base_action_step {
use \tool_trigger\helper\datafield_manager;
diff --git a/classes/steps/actions/role_unassign_action_step.php b/classes/steps/actions/role_unassign_action_step.php
index e7e4fd9..7d18110 100644
--- a/classes/steps/actions/role_unassign_action_step.php
+++ b/classes/steps/actions/role_unassign_action_step.php
@@ -25,8 +25,6 @@
namespace tool_trigger\steps\actions;
-defined('MOODLE_INTERNAL') || die;
-
class role_unassign_action_step extends base_action_step {
use \tool_trigger\helper\datafield_manager;
diff --git a/classes/steps/actions/roles_unassign_action_step.php b/classes/steps/actions/roles_unassign_action_step.php
index c9b5dd8..0c17d2a 100644
--- a/classes/steps/actions/roles_unassign_action_step.php
+++ b/classes/steps/actions/roles_unassign_action_step.php
@@ -24,8 +24,6 @@
namespace tool_trigger\steps\actions;
-defined('MOODLE_INTERNAL') || die;
-
/**
* HTTP Post action step class.
*
diff --git a/classes/steps/actions/webservice_action_step.php b/classes/steps/actions/webservice_action_step.php
new file mode 100644
index 0000000..7eeee3a
--- /dev/null
+++ b/classes/steps/actions/webservice_action_step.php
@@ -0,0 +1,329 @@
+.
+
+namespace tool_trigger\steps\actions;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once($CFG->libdir . '/externallib.php');
+
+/**
+ * Webservice action step class.
+ *
+ * @package tool_trigger
+ * @author Kevin Pham
+ * @copyright Catalyst IT, 2022
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class webservice_action_step extends base_action_step {
+
+ use \tool_trigger\helper\datafield_manager;
+
+ /** @var string $functionname Name of the function to be called */
+ protected $functionname;
+
+ /** @var int $username The user in which this action will be actioned in the context of */
+ protected $username;
+
+ /** @var int $params parameters that will be used in the corresponding web service function call */
+ protected $params;
+
+ /**
+ * The fields supplied by this step.
+ *
+ * @var array
+ */
+ private static $stepfields = [
+ 'data',
+ 'error',
+ 'exception',
+ ];
+
+ protected function init() {
+ $this->functionname = $this->data['functionname'];
+ $this->username = $this->data['username'];
+ $this->params = $this->data['params'];
+ }
+
+ /**
+ * Returns the step name.
+ *
+ * @return string human readable step name.
+ */
+ public static function get_step_name() {
+ return get_string('webserviceactionstepname', 'tool_trigger');
+ }
+
+ /**
+ * Returns the step name.
+ *
+ * @return string human readable step name.
+ */
+ public static function get_step_desc() {
+ return get_string('webserviceactionstepdesc', 'tool_trigger');
+ }
+
+ /**
+ * Returns the user that should execute the webservice function
+ *
+ * @uses webservice_action_step::$username
+ * @return \stdClass user object
+ */
+ private function get_user() {
+ global $DB;
+ $username = $this->render_datafields($this->username);
+
+ if (empty($username)) {
+ // If {username} is not set, then default it to the main admin user.
+ $user = get_admin();
+ } else {
+ // Assume the role of the provided user given their {username}.
+ $user = $DB->get_record('user', ['username' => $username], '*', MUST_EXIST);
+ }
+
+ // This bypasses the sesskey check for the external api call.
+ $user->ignoresesskey = true;
+
+ return $user;
+ }
+
+ /**
+ * Prepare and run the function set in the config and return the results.
+ *
+ * @uses webservice_action_step::$functionname
+ * @uses webservice_action_step::$params
+ * @return array results of the function run
+ */
+ private function run_function() {
+ // Passing any data from previous steps through by applying template magic.
+ $functionname = $this->render_datafields($this->functionname);
+ $params = $this->render_datafields($this->params);
+
+ // Execute the provided function name passing with the given parameters.
+ $response = \external_api::call_external_function($functionname, json_decode($params, true));
+ return $response;
+ }
+
+ /**
+ * Runs the configured step.
+ *
+ * @param $trigger
+ * @param $event
+ * @param $stepresults - result of previousstep to include in processing this step.
+ * @return array if execution was succesful and the response from the execution.
+ */
+ public function execute($step, $trigger, $event, $stepresults) {
+ global $DB, $SESSION, $USER;
+
+ $outertransaction = $DB->is_transaction_started();
+
+ $this->update_datafields($event, $stepresults);
+
+ // Store the previous user and session, setting it back once the step is finished.
+ $previoususer = $USER;
+ $session = $SESSION;
+
+ // Set the configured user as the one who will run the function.
+ $user = $this->get_user();
+ \core\session\manager::init_empty_session();
+ \core\session\manager::set_user($user);
+ set_login_session_preferences();
+
+ // Fake it till you make it - set the the lastaccess in advance to avoid
+ // this value being updated in the database via user_accesstime_log() as
+ // we are not actually logging in and accessing the site as this user.
+ $USER->lastaccess = time();
+
+ // Run the function and parse the response to a step result.
+ // This entire block is wrapped in a generic handler, so no matter what the correct user is always restored.
+ try {
+ $response = $this->run_function();
+ if ($response['error']) {
+ // Throw an exception to be propagated for proper error capture.
+ throw new \coding_exception(json_encode($response['exception']));
+ }
+
+ $status = [true, $response];
+ } catch (\Throwable $e) {
+ // Restore the previous user to avoid any side-effects occuring in later steps / code.
+ \core\session\manager::set_user($previoususer);
+ $SESSION = $session;
+
+ // We also need to make sure any transactions that were opened by the Webservice function are terminated.
+ // This logic mirrors the event processor loop transaction logic, just another layer down.
+ if (!$outertransaction && $DB->is_transaction_started()) {
+ $DB->force_transaction_rollback();
+ }
+
+ // Now rethrow for the event processor to register as an error.
+ throw $e;
+ }
+
+ // Restore the previous user to avoid any side-effects occuring in later steps / code.
+ \core\session\manager::set_user($previoususer);
+ $SESSION = $session;
+
+ // Return the function call response as is. The shape is already normalised.
+ return $status;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @see \tool_trigger\steps\base\base_step::add_extra_form_fields()
+ */
+ public function form_definition_extra($form, $mform, $customdata) {
+ // URL.
+ $attributes = ['size' => '50', 'placeholder' => 'my_function_name'];
+ $mform->addElement('text', 'functionname', get_string('webserviceactionfunctionname', 'tool_trigger'), $attributes);
+ $mform->setType('functionname', PARAM_RAW_TRIMMED);
+ $mform->addRule('functionname', get_string('required'), 'required');
+ $mform->addHelpButton('functionname', 'webserviceactionfunctionname', 'tool_trigger');
+
+ // Who.
+ $attributes = ['placeholder' => 'username'];
+ $mform->addElement('text', 'username', get_string('webserviceactionusername', 'tool_trigger'), $attributes);
+ $mform->setType('username', PARAM_ALPHANUMEXT);
+ $mform->addHelpButton('username', 'webserviceactionusername', 'tool_trigger');
+
+ // Params.
+ $attributes = ['cols' => '50', 'rows' => '5'];
+ $mform->addElement('textarea', 'params', get_string('webserviceactionparams', 'tool_trigger'), $attributes);
+ $mform->setType('params', PARAM_RAW_TRIMMED);
+ $mform->addHelpButton('params', 'webserviceactionparams', 'tool_trigger');
+ }
+
+ /**
+ * {@inheritDoc}
+ * @see \tool_trigger\steps\base\base_step::add_privacy_metadata()
+ */
+ public static function add_privacy_metadata($collection, $privacyfields) {
+ return $collection->add_external_location_link(
+ 'webservice_action_step',
+ $privacyfields,
+ 'step_action_webservice:privacy:desc'
+ );
+ }
+
+ /**
+ * Get a list of fields this step provides.
+ *
+ * @return array $stepfields The fields this step provides.
+ */
+ public static function get_fields() {
+ return self::$stepfields;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @see \tool_trigger\steps\base\base_step::form_validation()
+ */
+ public function form_validation($data, $files) {
+ global $DB;
+
+ $errors = [];
+
+ // Check if the username links to a valid user, if set.
+ if (!empty($data['username'])) {
+ try {
+ $DB->get_record('user', ['username' => $data['username']], '*', MUST_EXIST);
+ } catch (\Throwable $e) {
+ $errors['username'] = $e->getMessage();
+ }
+ }
+
+ // Check if the provided function (name) is a valid callable function.
+ if (!empty($data['functionname'])) {
+ try {
+ $errorfield = 'functionname';
+ $function = \external_api::external_function_info($data['functionname']);
+
+ $errorfield = 'params';
+
+ // Fill template fields with a number.
+ $transformcallback = function() {
+ return 0;
+ };
+
+ // Cannot use redner_datafields since we need to know of the
+ // datafields in advance. Will need to apply the change
+ // manually.
+ $params = preg_replace_callback(
+ $this->datafieldregex,
+ $transformcallback,
+ $data['params']
+ );
+
+ // Check if this is valid JSON before doing the function's validate_parameters check.
+ $preparedparams = [];
+ if (!empty($params)) {
+ $preparedparams = json_decode($params, true);
+ if (is_null($preparedparams)) {
+ throw new \Exception('Invalid Syntax');
+ }
+ }
+
+ // Execute the provided function name passing with the given parameters.
+ // $response = \external_api::call_external_function($functionname, json_decode($params, true));
+ // Check if the provided function parameters are valid.
+ call_user_func(
+ [$function->classname, 'validate_parameters'],
+ $function->parameters_desc,
+ $preparedparams
+ );
+ } catch (\Throwable $e) {
+ // Most usually a response saying the function name provided doesn't exist.
+ $errors[$errorfield] = $e->getMessage();
+ }
+ }
+
+ return $errors;
+ }
+
+
+ /**
+ * {@inheritDoc}
+ * @see \tool_trigger\steps\base\base_step::transform_form_data()
+ */
+ public function transform_form_data($data) {
+ // Prettify the JSON data in params, if there is content there.
+ if (!empty($data['params'])) {
+ // Fill template fields with a number.
+ $replacemap = [];
+ $start = PHP_INT_MIN; // Unlikely numerical conflict.
+ $transformcallback = function($matches) use(&$replacemap, &$start) {
+ $replacemap[$start] = $matches[0];
+ return $start++;
+ };
+
+ // Replace all matches with markable values, so they can be swapped back later on to their template forms.
+ $params = preg_replace_callback(
+ $this->datafieldregex,
+ $transformcallback,
+ $data['params']
+ );
+
+ // Pretty print the JSON value so it's formatted.
+ $params = json_encode(json_decode($params), JSON_PRETTY_PRINT);
+
+ // THEN, replace the temporary values with the original template variables.
+ $params = str_replace(array_keys($replacemap), array_values($replacemap), $params);
+
+ // Update the params key in $data to apply the changes as part of the render.
+ $data['params'] = $params;
+ }
+ return $data;
+ }
+}
diff --git a/classes/steps/base/base_form.php b/classes/steps/base/base_form.php
index 242476e..95dcf19 100644
--- a/classes/steps/base/base_form.php
+++ b/classes/steps/base/base_form.php
@@ -124,7 +124,6 @@ public function get_trigger_fields($eventname, $stepclass, $existingsteps, $step
if (!$isfirst) {
foreach ($existingsteps as $step) {
-
// Don't show fields for steps that may exist after this one.
if ($step['steporder'] >= $steporder && $steporder != -1) {
break;
@@ -235,4 +234,18 @@ public function definition() {
}
}
+ /**
+ * Custom validation that will be run if it exists in each step.
+ *
+ * @author Kevin Pham
+ * @copyright Catalyst IT, 2022
+ */
+ public function validation($data, $files) {
+ $errors = parent::validation($data, $files);
+ return array_merge(
+ $errors,
+ $this->step->form_validation($data, $files)
+ );
+ }
+
}
diff --git a/classes/steps/base/base_step.php b/classes/steps/base/base_step.php
index eadc93d..d5d5b16 100644
--- a/classes/steps/base/base_step.php
+++ b/classes/steps/base/base_step.php
@@ -24,8 +24,6 @@
namespace tool_trigger\steps\base;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Base step class.
*
@@ -180,4 +178,30 @@ public static function get_fields() {
throw new \Exception('Not implemented');
}
+
+ /**
+ * Custom validation of the form that could be configured per step as required.
+ *
+ * @param array $data — array of ("fieldname"=>value) of submitted data
+ * @param array $files — array of uploaded files "element_name"=>tmp_file_path
+ * @return array of "element_name"=>"error_description" if there are errors,
+ * or an empty array if everything is OK (true allowed for backwards compatibility too).
+ */
+ public function form_validation($data, $files) {
+ return [];
+ }
+
+ /**
+ * Transform / process form data, if required.
+ *
+ * An example of when you might want this is when prettifying JSON inputs.
+ * Any step using this should override this to apply the transformations as
+ * needed.
+ *
+ * @param array $data — array of ("fieldname"=>value) of submitted data
+ * @return array $data — array of ("fieldname"=>value) of transformed - if required - data
+ */
+ public function transform_form_data($data) {
+ return $data;
+ }
}
diff --git a/classes/steps/debounce/debounce_step.php b/classes/steps/debounce/debounce_step.php
index e08f019..dbca148 100644
--- a/classes/steps/debounce/debounce_step.php
+++ b/classes/steps/debounce/debounce_step.php
@@ -31,8 +31,6 @@
use xmldb_table;
use xmldb_field;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Debounce step class.
*
diff --git a/classes/steps/filters/base_filter_step.php b/classes/steps/filters/base_filter_step.php
index 7d8f4ab..0f84804 100644
--- a/classes/steps/filters/base_filter_step.php
+++ b/classes/steps/filters/base_filter_step.php
@@ -29,8 +29,6 @@
use tool_trigger\steps\base\base_step;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Base filter step class.
*
diff --git a/classes/steps/filters/fail_filter_step.php b/classes/steps/filters/fail_filter_step.php
index ad6284d..75a8d9a 100644
--- a/classes/steps/filters/fail_filter_step.php
+++ b/classes/steps/filters/fail_filter_step.php
@@ -27,8 +27,6 @@
namespace tool_trigger\steps\filters;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Fail filter step class.
*
diff --git a/classes/steps/filters/numcompare_filter_step.php b/classes/steps/filters/numcompare_filter_step.php
index e21cc13..62aa1fc 100644
--- a/classes/steps/filters/numcompare_filter_step.php
+++ b/classes/steps/filters/numcompare_filter_step.php
@@ -27,8 +27,6 @@
namespace tool_trigger\steps\filters;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Base filter step class.
*
diff --git a/classes/steps/filters/stringcompare_filter_step.php b/classes/steps/filters/stringcompare_filter_step.php
index d186e68..85bf557 100644
--- a/classes/steps/filters/stringcompare_filter_step.php
+++ b/classes/steps/filters/stringcompare_filter_step.php
@@ -27,8 +27,6 @@
namespace tool_trigger\steps\filters;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Base filter step class.
*
@@ -112,6 +110,11 @@ protected function init() {
$this->field2 = $this->data['field2'];
$this->operator = $this->data['operator'];
$this->wantmatch = (bool) $this->data['wantmatch'];
+ $this->erroronfail = false;
+
+ if (!empty($this->data['erroronfail'])) {
+ $this->erroronfail = (bool) $this->data['erroronfail'];
+ }
}
/**
@@ -161,6 +164,11 @@ public function execute($step, $trigger, $event, $stepresults) {
// Check whether they wanted the pattern to match, or not match.
$result = ($ismatch == $this->wantmatch);
+ // Check if we want it to error on failure, and the result was not true.
+ if (($this->erroronfail == true) && !$result) {
+ throw new \moodle_exception('erroronfail for stringcompare', 'tool_trigger');
+ }
+
return [$result, $stepresults];
}
@@ -212,6 +220,13 @@ public function form_definition_extra($form, $mform, $customdata) {
$mform->addGroup($fields, 'stringcomparegroup', '', [' '], false);
$mform->addRule('stringcomparegroup', get_string('required'), 'required');
+
+ // Error instead of failure.
+ $mform->addElement('advcheckbox', 'erroronfail', get_string ('erroronfail', 'tool_trigger'),
+ 'Enable', array(), array(0, 1));
+ $mform->setType('erroronfail', PARAM_INT);
+ $mform->addHelpButton('erroronfail', 'erroronfail', 'tool_trigger');
+ $mform->setDefault('erroronfail', 0);
}
/**
diff --git a/classes/steps/lookups/base_lookup_step.php b/classes/steps/lookups/base_lookup_step.php
index 4fc328f..abd40f5 100644
--- a/classes/steps/lookups/base_lookup_step.php
+++ b/classes/steps/lookups/base_lookup_step.php
@@ -30,8 +30,6 @@
use tool_trigger\steps\base\base_step;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Base lookup step class.
*
diff --git a/classes/steps/lookups/cohort_lookup_step.php b/classes/steps/lookups/cohort_lookup_step.php
index 97d0ad9..483076f 100644
--- a/classes/steps/lookups/cohort_lookup_step.php
+++ b/classes/steps/lookups/cohort_lookup_step.php
@@ -16,7 +16,6 @@
namespace tool_trigger\steps\lookups;
-defined('MOODLE_INTERNAL') || die;
/**
* A lookup step that takes a user's ID and returns a string of
* all the cohorts that the user is currently assigned to
@@ -26,8 +25,8 @@
* @copyright Catalyst IT, 2018
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-class cohort_lookup_step extends base_lookup_step
-{
+class cohort_lookup_step extends base_lookup_step {
+
use \tool_trigger\helper\datafield_manager;
diff --git a/classes/steps/lookups/course_cat_lookup_step.php b/classes/steps/lookups/course_cat_lookup_step.php
index aeafb0f..a50cab0 100644
--- a/classes/steps/lookups/course_cat_lookup_step.php
+++ b/classes/steps/lookups/course_cat_lookup_step.php
@@ -16,8 +16,6 @@
namespace tool_trigger\steps\lookups;
-defined('MOODLE_INTERNAL') || die;
-
/**
* A lookup step that takes a category ID and adds all data about the category.
*
diff --git a/classes/steps/lookups/course_lookup_step.php b/classes/steps/lookups/course_lookup_step.php
index bffba3d..1b3f324 100644
--- a/classes/steps/lookups/course_lookup_step.php
+++ b/classes/steps/lookups/course_lookup_step.php
@@ -16,8 +16,6 @@
namespace tool_trigger\steps\lookups;
-defined('MOODLE_INTERNAL') || die;
-
/**
* A lookup step that takes a course's ID and adds standard data about the
* course.
diff --git a/classes/steps/lookups/roles_lookup_step.php b/classes/steps/lookups/roles_lookup_step.php
index c85f7ce..2db19c2 100644
--- a/classes/steps/lookups/roles_lookup_step.php
+++ b/classes/steps/lookups/roles_lookup_step.php
@@ -16,8 +16,6 @@
namespace tool_trigger\steps\lookups;
-defined('MOODLE_INTERNAL') || die;
-
/**
* A lookup step that takes a user's ID and adds roles of the user.
*
@@ -76,7 +74,7 @@ public function execute($step, $trigger, $event, $stepresults) {
foreach ($userroles as $role) {
foreach ($role as $key => $value) {
if (is_scalar($role->roleid)) {
- $stepresults[$this->outputprefix . 'roles'][ $role->id][$key] = $value;
+ $stepresults[$this->outputprefix . 'roles'][$role->id][$key] = $value;
}
}
}
diff --git a/classes/task/cleanup.php b/classes/task/cleanup.php
index e53ed39..6de5e12 100644
--- a/classes/task/cleanup.php
+++ b/classes/task/cleanup.php
@@ -25,7 +25,6 @@
namespace tool_trigger\task;
-defined('MOODLE_INTERNAL') || die();
/**
* Task to cleanup old queue.
*/
diff --git a/classes/task/cleanup_history.php b/classes/task/cleanup_history.php
index d63d069..0f9d6d4 100644
--- a/classes/task/cleanup_history.php
+++ b/classes/task/cleanup_history.php
@@ -25,12 +25,14 @@
namespace tool_trigger\task;
-defined('MOODLE_INTERNAL') || die();
/**
* Task to cleanup old queue.
*/
class cleanup_history extends \core\task\scheduled_task {
+ /** @var int Maximum number parameters to use in the SQL IN statement. */
+ const MAX_PARAM_IN = 10000;
+
/**
* Get a descriptive name for this task.
*
@@ -65,7 +67,12 @@ public function execute() {
) = 0";
$ids = $DB->get_fieldset_sql($sql, ['lookback1' => $lookback, 'lookback2' => $lookback]);
if ($ids) {
- $DB->delete_records_list('tool_trigger_run_hist', 'id', $ids);
+ $parts = array_chunk($ids, self::MAX_PARAM_IN);
+ foreach ($parts as $chunk) {
+ mtrace("Deleting: " . count($chunk) . " records from tool_trigger_run_hist.");
+ $DB->delete_records_list('tool_trigger_run_hist', 'id', $chunk);
+ }
}
+ mtrace("Deleted total: " . count($ids) . " records from tool_trigger_run_hist.");
}
}
diff --git a/classes/task/learn.php b/classes/task/learn.php
index 2afb7ba..7698c10 100644
--- a/classes/task/learn.php
+++ b/classes/task/learn.php
@@ -24,7 +24,6 @@
namespace tool_trigger\task;
-defined('MOODLE_INTERNAL') || die();
/**
* Task to learn from processed events.
*/
diff --git a/classes/task/process_workflows.php b/classes/task/process_workflows.php
index ad6cc9d..1809383 100644
--- a/classes/task/process_workflows.php
+++ b/classes/task/process_workflows.php
@@ -26,7 +26,6 @@
use tool_trigger\helper\processor_helper;
-defined('MOODLE_INTERNAL') || die();
/**
* Simple task to rocess queued workflows.
*/
@@ -62,9 +61,6 @@ class process_workflows extends \core\task\scheduled_task {
*/
const STATUS_FINISHED = 40;
- /** Max number of tasks to try and process in a queue. */
- const LIMITQUEUE = 500;
-
/** Max processing time for a queue in seconds. */
const MAXTIME = 60;
@@ -136,7 +132,7 @@ private function process_queue($starttime) {
'time' => time(),
'autorerunmaxtries' => get_config('tool_trigger', 'autorerunmaxtries')
];
- $queue = $DB->get_recordset_sql($sql, $params, 0, self::LIMITQUEUE);
+ $queue = $DB->get_recordset_sql($sql, $params, 0, get_config('tool_trigger', 'queuelimit'));
foreach ($queue as $q) {
mtrace('Executing workflow: ' . $q->workflowid);
diff --git a/classes/task/update_trigger_helper_task.php b/classes/task/update_trigger_helper_task.php
index 650422c..e1a3ff3 100644
--- a/classes/task/update_trigger_helper_task.php
+++ b/classes/task/update_trigger_helper_task.php
@@ -25,8 +25,6 @@
namespace tool_trigger\task;
-defined('MOODLE_INTERNAL') || die();
-
/**
* Helper task to offload upgrade processing
diff --git a/classes/workflow.php b/classes/workflow.php
index 176474d..b5adbff 100644
--- a/classes/workflow.php
+++ b/classes/workflow.php
@@ -24,8 +24,6 @@
namespace tool_trigger;
-defined('MOODLE_INTERNAL') || die();
-
/**
* Worklfow class.
*
diff --git a/classes/workflow_manager.php b/classes/workflow_manager.php
index 1e84012..69dd2fb 100644
--- a/classes/workflow_manager.php
+++ b/classes/workflow_manager.php
@@ -24,8 +24,6 @@
namespace tool_trigger;
-defined('MOODLE_INTERNAL') || die();
-
/**
* Workflow manager class.
*
diff --git a/classes/workflow_process.php b/classes/workflow_process.php
index a7ac552..66228c0 100644
--- a/classes/workflow_process.php
+++ b/classes/workflow_process.php
@@ -24,8 +24,6 @@
namespace tool_trigger;
-defined('MOODLE_INTERNAL') || die();
-
/**
* Process workflow form.
*
diff --git a/db/install.php b/db/install.php
index adf4ca2..1a77f17 100644
--- a/db/install.php
+++ b/db/install.php
@@ -23,8 +23,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die;
-
/**
* Add events fields from fixture file to database.
*/
diff --git a/db/install.xml b/db/install.xml
index eb2e779..799eace 100644
--- a/db/install.xml
+++ b/db/install.xml
@@ -1,5 +1,5 @@
-
@@ -161,6 +161,9 @@
+
+
+
-
\ No newline at end of file
+
diff --git a/db/upgrade.php b/db/upgrade.php
index 869517e..3000918 100644
--- a/db/upgrade.php
+++ b/db/upgrade.php
@@ -23,8 +23,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
/**
* Upgrade the plugin.
*
@@ -337,5 +335,35 @@ function xmldb_tool_trigger_upgrade($oldversion) {
upgrade_plugin_savepoint(true, 2021030403, 'tool', 'trigger');
}
+ if ($oldversion < 2021030404) {
+
+ // Define key stepconfigid (foreign) to be added to tool_trigger_run_hist.
+ $table = new xmldb_table('tool_trigger_run_hist');
+ $key = new xmldb_key('stepconfigid', XMLDB_KEY_FOREIGN, ['stepconfigid'], 'tool_trigger_steps', ['id']);
+
+ // Launch add key stepconfigid.
+ if (!$table->getKey($key->getName())) {
+ $dbman->add_key($table, $key);
+ }
+
+ // Trigger savepoint reached.
+ upgrade_plugin_savepoint(true, 2021030404, 'tool', 'trigger');
+ }
+
+ if ($oldversion < 2021030408) {
+
+ // Define index workflowid-number (not unique) to be added to tool_trigger_workflow_hist.
+ $table = new xmldb_table('tool_trigger_workflow_hist');
+ $index = new xmldb_index('workflowid-number', XMLDB_INDEX_NOTUNIQUE, ['workflowid', 'number']);
+
+ // Conditionally launch add index workflowid-number.
+ if (!$dbman->index_exists($table, $index)) {
+ $dbman->add_index($table, $index);
+ }
+
+ // Trigger savepoint reached.
+ upgrade_plugin_savepoint(true, 2021030408, 'tool', 'trigger');
+ }
+
return true;
}
diff --git a/lang/en/tool_trigger.php b/lang/en/tool_trigger.php
index b16e039..e689ae9 100644
--- a/lang/en/tool_trigger.php
+++ b/lang/en/tool_trigger.php
@@ -86,6 +86,8 @@
$string['emailcontent_help'] = 'The content to use in the email';
$string['emailactionstepname'] = 'Email';
$string['emailactionstepdesc'] = 'A step to allow an e-mail to be sent';
+$string['erroronfail'] = 'Error on failure';
+$string['erroronfail_help'] = 'Set the step to error instead of fail';
$string['event'] = 'Event';
$string['eventdescription'] = 'Event description';
$string['eventfields'] = 'Event fields';
@@ -118,11 +120,13 @@
$string['historysettingsdesc'] = 'These settings provide control over how the history of a workflow is stored.';
$string['httpostactionurl'] = 'URL';
$string['httpostactionurl_help'] = 'The URL to post the data to.';
+$string['httpostmethod'] = 'HTTP method';
+$string['httpostmethod_help'] = 'HTTP method for the given request.';
$string['httpostactionheaders'] = 'Headers';
$string['httpostactionheaders_help'] = 'The requests headers to send.';
$string['httpostactionparams'] = 'Parameters';
$string['httpostactionparams_help'] = 'The parameters to send with the request.';
-$string['httppostactionstepname'] = 'HTTP Post';
+$string['httppostactionstepname'] = 'HTTP request';
$string['httppostactionstepdesc'] = 'A step to allow Moodle workflows to send data to a HTTP/S endpoint.';
$string['importmodaltitle'] = 'Import workflow from file';
$string['importworkflow'] = 'Import a workflow';
@@ -184,6 +188,10 @@
$string['privacy:metadata:workflowhistory'] = 'This table stores historical data of trigger runs, in order to allow for replaying trigger runs.';
$string['privacy:metadata:workflowhistory:event'] = 'An encoded event entry that triggered the trigger run.';
$string['privacy:metadata:workflowhistory:timecreated'] = 'The time that the trigger run was executed.';
+$string['queuelimit'] = 'Queue limit';
+$string['queuelimitdesc'] = 'Max number of tasks to try and process in a queue.';
+$string['queuesettings'] = 'Workflow queue settings';
+$string['queuesettingsdesc'] = 'These settings control how the queue is managed.';
$string['realtime'] = 'Real time';
$string['rerunallcurr'] = 'Rerun all errored runs with current configuration';
$string['rerunallcurrconfirm'] = 'Are you sure you wish to re-run all errored runs using the current workflow configuration?';
@@ -246,6 +254,7 @@
$string['step_action_logdump_name'] = 'Cron log';
$string['step_action_role_assign_useridfield'] = 'User id data field';
$string['step_action_role_unassign_useridfield'] = 'User id data field';
+$string['step_action_webservice:privacy:desc'] = 'This plugin may be configured to call webservice functions directly and so may handle data from Moodle depending on the function called.';
$string['useridfield'] = 'User id data field';
$string['useridfield_help'] = 'You can use user id as a number or as a filed name from the workflow data';
$string['step_action_role_assign_roleidfield'] = 'Role id data field';
@@ -280,6 +289,16 @@
$string['timetocleanup_help'] = 'This setting sets the time sucessfully executed workflows remain in the Moodle database prior to being removed.';
$string['update_trigger_helper_task'] = 'Adhoc task to offload upgrade processing work.';
$string['warningdebugging'] = 'Debug mode is disabled for the current workflow. To be able to record the history, you should enable debugging for the workflow .';
+
+$string['webserviceactionfunctionname'] = 'Function';
+$string['webserviceactionfunctionname_help'] = 'The webservice function to be called. See the API Documentation ';
+$string['webserviceactionusername'] = 'Who';
+$string['webserviceactionusername_help'] = 'The user (username) who this step will be performed in the context of. This defaults to the main admin user if not explicitly set';
+$string['webserviceactionparams'] = 'Parameters';
+$string['webserviceactionparams_help'] = 'The function parameters - currently with support for JSON.';
+$string['webserviceactionstepname'] = 'Webservice Function';
+$string['webserviceactionstepdesc'] = 'A step allowing the workflow to trigger web service functions.';
+
$string['workflowactive'] = 'Workflow active';
$string['workflowactive_help'] = 'Only active workflows will be processed when an event is triggered.';
$string['workflowrealtime'] = 'Real time processing';
diff --git a/lib.php b/lib.php
index 0deabb9..5724fea 100644
--- a/lib.php
+++ b/lib.php
@@ -25,8 +25,6 @@
use tool_trigger\steps\base\base_form;
use tool_trigger\import_form;
-defined('MOODLE_INTERNAL') || die;
-
/**
* Renders the top part of the "new workflow step" modal form. (The part with
* the "Step type" and "Step" menus.
@@ -88,6 +86,9 @@ function tool_trigger_output_fragment_new_step_form($args) {
if (!empty($args['ajaxformdata'])) {
// Don't need to clean/validate these, because formslib will do that.
parse_str($args['ajaxformdata'], $ajaxformdata);
+
+ // Apply any data transforms - determined in each step's class.
+ $ajaxformdata = $stepclassobj->transform_form_data($ajaxformdata);
}
$mform = $stepclassobj->make_form($customdata, $ajaxformdata);
@@ -113,6 +114,9 @@ function tool_trigger_output_fragment_new_step_form($args) {
$data['debounceduration']['timeunit'] = $data['debounceduration[timeunit]'];
}
+ // Apply any data transforms - determined in each step's class.
+ $data = $stepclassobj->transform_form_data($data);
+
$mform->set_data($data);
}
diff --git a/settings.php b/settings.php
index 98482ad..4aaf60d 100644
--- a/settings.php
+++ b/settings.php
@@ -49,6 +49,16 @@
get_string('learning', 'tool_trigger'),
get_string('learning_help', 'tool_trigger'), 0));
+ // Workflow Queue settings.
+ $settings->add(new admin_setting_heading('tool_trigger/queuesettings',
+ get_string('queuesettings', 'tool_trigger'),
+ get_string('queuesettingsdesc', 'tool_trigger')));
+
+ $settings->add(new admin_setting_configtext('tool_trigger/queuelimit',
+ get_string('queuelimit', 'tool_trigger'),
+ get_string('queuelimitdesc', 'tool_trigger'), 500, PARAM_INT));
+
+ // Workflow history settings.
$settings->add(new admin_setting_heading('tool_trigger/historysettings',
get_string('historysettings', 'tool_trigger'),
get_string('historysettingsdesc', 'tool_trigger')));
@@ -57,6 +67,7 @@
get_string('historyduration', 'tool_trigger'),
get_string('historydurationdesc', 'tool_trigger'), 1 * WEEKSECS, WEEKSECS));
+ // Auto re-run.
$settings->add(new admin_setting_heading('tool_trigger/autorerunsettings',
get_string('autorerunsettings', 'tool_trigger'),
get_string('autorerunsettingsdesc', 'tool_trigger')));
diff --git a/tests/datafield_manager_test.php b/tests/datafield_manager_test.php
index dd4bd48..e04826c 100644
--- a/tests/datafield_manager_test.php
+++ b/tests/datafield_manager_test.php
@@ -51,6 +51,12 @@ public function test_get_datafields() {
$dfprovider->update_datafields($this->event, $stepdata);
$datafields = $dfprovider->get_datafields();
+ // Check event id.
+ $this->assertEquals(
+ 1,
+ $datafields['id']
+ );
+
// A field from the event object.
$this->assertEquals(
$this->user2->id,
diff --git a/tests/fixtures/user_event_fixture.php b/tests/fixtures/user_event_fixture.php
index 1651325..7829df8 100644
--- a/tests/fixtures/user_event_fixture.php
+++ b/tests/fixtures/user_event_fixture.php
@@ -62,7 +62,8 @@ public function setup_user_event() {
'other' => [
'courseid' => $this->course->id,
'courseshortname' => $this->course->shortname,
- 'coursefullname' => $this->course->fullname
+ 'coursefullname' => $this->course->fullname,
+ 'eventid' => 1,
]
]);
@@ -101,4 +102,4 @@ public function add_user_custom_profile_field($shortname, $datatype, $forceuniqu
return $data;
}
-}
\ No newline at end of file
+}
diff --git a/tests/http_post_action_step_test.php b/tests/http_post_action_step_test.php
index 5f0a767..24add51 100644
--- a/tests/http_post_action_step_test.php
+++ b/tests/http_post_action_step_test.php
@@ -62,12 +62,65 @@ private function make_mock_http_handler($response) {
return $stack;
}
+ /**
+ * Test supported HTTP methods.
+ */
+ public function test_supported_http_methods() {
+ $expected = [
+ 'POST' => 'POST',
+ 'GET' => 'GET',
+ 'PUT' => 'PUT',
+ 'DELETE' => 'DELETE',
+ 'PATCH' => 'PATCH',
+ ];
+ $this->assertSame($expected, \tool_trigger\steps\actions\http_post_action_step::SUPPORTED_HTTP_METHODS);
+ }
+
+ /**
+ * Test that POST method is set as default if no httpmethod set for the step class.
+ * This is to make sure that steps created before httpmethod was introduced will get it by default.
+ */
+ public function test_if_httpmethod_is_not_set_post_method_set_as_default() {
+ $stepsettings = [
+ 'url' => 'http://http_post_action_step.example.com',
+ 'httpheaders' => 'My-Special-Header: {headervalue}',
+ 'httpparams' => '',
+ 'jsonencode' => '0'
+ ];
+
+ $step = new \tool_trigger\steps\actions\http_post_action_step(json_encode($stepsettings));
+
+ $reflector = new \ReflectionClass(\tool_trigger\steps\actions\http_post_action_step::class);
+ $property = $reflector->getProperty('httpmethod');
+ $property->setAccessible(true);
+
+ $this->assertEquals('POST', $property->getValue($step));
+ }
+
+ /**
+ * Data provider for all supported HTTP methods.
+ * @return array[]
+ */
+ public function http_methods_data_provider(): array {
+ return [
+ ['POST'],
+ ['GET'],
+ ['PUT'],
+ ['DELETE'],
+ ['PATCH'],
+ ];
+ }
+
/**
* Simple test, with a successful response
+ *
+ * @dataProvider http_methods_data_provider
+ * @param string $httpmethod
*/
- public function test_execute_200() {
+ public function test_execute_200(string $httpmethod) {
$stepsettings = [
'url' => 'http://http_post_action_step.example.com',
+ 'httpmethod' => $httpmethod,
'httpheaders' => 'My-Special-Header: {headervalue}',
'httpparams' => '',
'jsonencode' => '0'
@@ -89,10 +142,14 @@ public function test_execute_200() {
/**
* Test that we properly handle a 404 response. Guzzle will throw an exception in this
* case, but the action step should catch the exception and handle it.
+ *
+ * @dataProvider http_methods_data_provider
+ * @param string $httpmethod
*/
- public function test_execute_404() {
+ public function test_execute_404(string $httpmethod) {
$stepsettings = [
'url' => 'http://http_post_action_step.example.com/badurl',
+ 'httpmethod' => $httpmethod,
'httpheaders' => 'My-Special-Header: {headervalue}',
'httpparams' => '',
'jsonencode' => '0',
diff --git a/tests/privacy_test.php b/tests/privacy_test.php
index 44f8411..3d2fb9e 100644
--- a/tests/privacy_test.php
+++ b/tests/privacy_test.php
@@ -23,8 +23,6 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
-defined('MOODLE_INTERNAL') || die();
-
use \tool_trigger\privacy\provider;
use \core_privacy\local\request\approved_contextlist;
use \core_privacy\local\request\approved_userlist;
diff --git a/tests/processor_helper_test.php b/tests/processor_helper_test.php
index 3504926..d3551bf 100644
--- a/tests/processor_helper_test.php
+++ b/tests/processor_helper_test.php
@@ -53,6 +53,7 @@ public function setup():void {
*/
public function test_restore_event() {
$data = (object) [
+ 'id' => 1,
'eventname' => '\\core\\event\\user_loggedin',
'component' => 'core',
'action' => 'loggedin',
@@ -98,6 +99,8 @@ public function test_restore_event() {
$this->assertEquals($expectedevent->userid, $actual->userid);
$this->assertEquals($expectedevent->objectid, $actual->objectid);
$this->assertEquals($expectedevent->get_username(), $actual->get_username());
+ // Tool trigger event id
+ $this->assertEquals(1, $actual->other['eventid']);
}
/**
diff --git a/tests/webservice_action_step_test.php b/tests/webservice_action_step_test.php
new file mode 100644
index 0000000..ffeae2c
--- /dev/null
+++ b/tests/webservice_action_step_test.php
@@ -0,0 +1,134 @@
+.
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once(__DIR__.'/fixtures/user_event_fixture.php');
+
+/**
+ * Test of the Webservice action step.
+ *
+ * @package tool_trigger
+ * @author Kevin Pham
+ * @copyright Catalyst IT, 2022
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class tool_trigger_webservice_action_step_testcase extends \advanced_testcase {
+ use \tool_trigger_user_event_fixture;
+
+ /**
+ * Create a "user_profile_viewed" event, of user1 viewing user2's
+ * profile. And then run everything else as the cron user.
+ */
+ public function setup(): void {
+ $this->setup_user_event();
+ }
+
+ /**
+ * Simple test, with a successful result.
+ */
+ public function test_with_valid_call_to_enrol_user() {
+ global $DB;
+
+ $adminuser = get_admin();
+ $stepsettings = [
+ 'username' => $adminuser->username,
+ 'functionname' => 'enrol_manual_enrol_users',
+ 'params' =>
+ '{"enrolments":{"0":{"roleid":"5","userid":' . $this->user1->id . ',"courseid":' . $this->course->id . '}}}',
+ ];
+
+ // Ensure the user provided by the username is not actually 'logged in'
+ // to perform the required actions.
+ $this->assertEquals(0, $adminuser->lastaccess);
+ $this->assertEquals(0, $adminuser->lastlogin);
+
+ // Check if user is NOT enrolled yet.
+ $context = context_course::instance($this->course->id);
+ $enrolled = is_enrolled($context, $this->user1->id);
+ $this->assertFalse($enrolled);
+
+ $step = new \tool_trigger\steps\actions\webservice_action_step(json_encode($stepsettings));
+ list($status, $stepresults) = $step->execute(null, null, $this->event, []);
+ $this->assertTrue($status);
+ $this->assertNotNull($stepresults);
+ $this->assertArrayHasKey('data', $stepresults);
+ $this->assertArrayNotHasKey('exception', $stepresults);
+
+ // Check if user is now enrolled as expected, showing the call did indeed work as expected.
+ $context = context_course::instance($this->course->id);
+ $enrolled = is_enrolled($context, $this->user1->id);
+ $this->assertTrue($enrolled);
+
+ $user = $DB->get_record('user', ['id' => $adminuser->id, 'deleted' => 0]);
+ $this->assertEquals(0, $user->lastaccess);
+ $this->assertEquals(0, $user->lastlogin);
+ }
+
+ /**
+ * Test when the username is not valid, so the step fails with an exception.
+ */
+ public function test_with_invalid_username() {
+ $stepsettings = [
+ 'username' => 'tool_trigger_invalid_username',
+ 'functionname' => 'enrol_manual_enrol_users',
+ 'params' =>
+ '{"enrolments":{"0":{"roleid":"5","userid":' . $this->user1->id . ',"courseid":' . $this->course->id . '}}}',
+ ];
+ $step = new \tool_trigger\steps\actions\webservice_action_step(json_encode($stepsettings));
+ $this->expectException(dml_missing_record_exception::class);
+ $step->execute(null, null, $this->event, []);
+ }
+
+ /**
+ * Test with non_existent function
+ */
+ public function test_with_non_existent_function() {
+ $adminuser = get_admin();
+ $stepsettings = [
+ 'username' => $adminuser->username,
+ 'functionname' => 'tool_trigger_function_does_not_exist',
+ 'params' =>
+ '{"enrolments":{"0":{"roleid":"5","userid":' . $this->user1->id . ',"courseid":' . $this->course->id . '}}}',
+ ];
+ $step = new \tool_trigger\steps\actions\webservice_action_step(json_encode($stepsettings));
+
+ // Manually catch the exception to check the message
+ $this->expectException('dml_missing_record_exception');
+ $this->expectExceptionMessageRegExp('/external_functions/');
+ $step->execute(null, null, $this->event, []);
+ }
+
+ /**
+ * Test with invalid function parameters
+ */
+ public function test_with_invalid_function_parameters() {
+ $adminuser = get_admin();
+ $stepsettings = [
+ 'username' => $adminuser->username,
+ 'functionname' => 'enrol_manual_enrol_users',
+ 'params' =>
+ '{"not_enrolments":{"0":{"roleid":"5","userid":' . $this->user1->id . ',"courseid":' . $this->course->id . '}}}',
+ ];
+ $step = new \tool_trigger\steps\actions\webservice_action_step(json_encode($stepsettings));
+
+ $this->expectException('coding_exception');
+ // Setup the expectations for exception format.
+ $this->expectExceptionMessageRegExp('/errorcode.*invalidparameter.*debuginfo.*enrolments/');
+ $step->execute(null, null, $this->event, []);
+ }
+}
diff --git a/version.php b/version.php
index 72f6273..c618efc 100755
--- a/version.php
+++ b/version.php
@@ -25,8 +25,9 @@
defined('MOODLE_INTERNAL') || die();
$plugin->component = 'tool_trigger';
-$plugin->release = 2021030403;
-$plugin->version = 2021030403;
+$plugin->release = 2021030409;
+$plugin->version = 2021030409;
$plugin->requires = 2016052300;
+$plugin->supported = [35, 310];
$plugin->maturity = MATURITY_STABLE;
$plugin->dependencies = array('tool_monitor' => 2015051101);