From 930377b039d4b147ca9d1ea2f80d549075d1b358 Mon Sep 17 00:00:00 2001 From: Nicholas Hoobin Date: Mon, 18 Oct 2021 11:03:51 +1100 Subject: [PATCH 01/23] Fix for the cleanup_history task with a large number of parameters PostgreSQL has a limit of 65535 parameters and this was exceeded causing the task to fail. MySQL has does not have a parameter limit, but there is a sysvar_max_allowed_packet which would fail out once exceeded. --- classes/task/cleanup_history.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/classes/task/cleanup_history.php b/classes/task/cleanup_history.php index d63d069..264de28 100644 --- a/classes/task/cleanup_history.php +++ b/classes/task/cleanup_history.php @@ -31,6 +31,9 @@ */ 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 +68,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."); } } From 0b13e57b7c2fe95e9b33ee03d014857fc7f8355c Mon Sep 17 00:00:00 2001 From: Nicholas Hoobin Date: Fri, 5 Nov 2021 14:49:32 +1100 Subject: [PATCH 02/23] Update HTTP post action step to correctly obtain body contents. --- classes/steps/actions/http_post_action_step.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/steps/actions/http_post_action_step.php b/classes/steps/actions/http_post_action_step.php index d7f5390..b802b11 100644 --- a/classes/steps/actions/http_post_action_step.php +++ b/classes/steps/actions/http_post_action_step.php @@ -156,7 +156,7 @@ 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. From f8edb18ab0492cc276c012486d9e752724bbd286 Mon Sep 17 00:00:00 2001 From: Nicholas Hoobin Date: Fri, 5 Nov 2021 14:50:14 +1100 Subject: [PATCH 03/23] Add option for stringcompare to error instead of fail. --- .../steps/filters/stringcompare_filter_step.php | 17 +++++++++++++++++ lang/en/tool_trigger.php | 2 ++ 2 files changed, 19 insertions(+) diff --git a/classes/steps/filters/stringcompare_filter_step.php b/classes/steps/filters/stringcompare_filter_step.php index d186e68..29267c6 100644 --- a/classes/steps/filters/stringcompare_filter_step.php +++ b/classes/steps/filters/stringcompare_filter_step.php @@ -112,6 +112,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 +166,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 +222,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/lang/en/tool_trigger.php b/lang/en/tool_trigger.php index b16e039..e435bef 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'; From c1f3f5a2049a03a40c5fed7b7b5e743716444b76 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Tue, 23 Nov 2021 12:15:21 +1100 Subject: [PATCH 04/23] Add missing foreign key for 'stepconfigid' upgrades Issue when upgrading from a lower version - started from 01950e69828864dad74891b8418a584a11f632ea --- db/upgrade.php | 15 +++++++++++++++ version.php | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/db/upgrade.php b/db/upgrade.php index 869517e..7a0066c 100644 --- a/db/upgrade.php +++ b/db/upgrade.php @@ -337,5 +337,20 @@ 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'); + } + return true; } diff --git a/version.php b/version.php index 72f6273..1eb1493 100755 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'tool_trigger'; -$plugin->release = 2021030403; -$plugin->version = 2021030403; +$plugin->release = 2021030404; +$plugin->version = 2021030404; $plugin->requires = 2016052300; $plugin->maturity = MATURITY_STABLE; $plugin->dependencies = array('tool_monitor' => 2015051101); From f6569bf2f6b19627e5018d4f97377c526cde82f1 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Fri, 26 Nov 2021 15:25:08 +1100 Subject: [PATCH 05/23] Add configurable task processing settings (#158) - Add new section for settings related to the workflow queue. - The queue limit has been moved out from being a constant, to a configurable admin setting. --- classes/task/process_workflows.php | 5 +---- lang/en/tool_trigger.php | 4 ++++ settings.php | 11 +++++++++++ version.php | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/classes/task/process_workflows.php b/classes/task/process_workflows.php index ad6cc9d..019dbd0 100644 --- a/classes/task/process_workflows.php +++ b/classes/task/process_workflows.php @@ -62,9 +62,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 +133,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/lang/en/tool_trigger.php b/lang/en/tool_trigger.php index e435bef..598fd64 100644 --- a/lang/en/tool_trigger.php +++ b/lang/en/tool_trigger.php @@ -186,6 +186,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?'; 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/version.php b/version.php index 1eb1493..3a64643 100755 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'tool_trigger'; -$plugin->release = 2021030404; -$plugin->version = 2021030404; +$plugin->release = 2021030405; +$plugin->version = 2021030405; $plugin->requires = 2016052300; $plugin->maturity = MATURITY_STABLE; $plugin->dependencies = array('tool_monitor' => 2015051101); From bd12195085b33e48abbc8f411504bf6edea05781 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Fri, 26 Nov 2021 16:00:55 +1100 Subject: [PATCH 06/23] Update check on error/failed steps to allow for zero values (#162) By default these are NULL, and when set they are a currently a string value, so this should still work as intended for expected values, and should fix up the display in the history table --- classes/output/workflowhistory/workflow.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 { From 0d432d8bbf6d27d30fce62e22c4839cf83a1e721 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Fri, 26 Nov 2021 16:22:11 +1100 Subject: [PATCH 07/23] Fix casing on queuelimit string for consistency --- lang/en/tool_trigger.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/en/tool_trigger.php b/lang/en/tool_trigger.php index 598fd64..4228009 100644 --- a/lang/en/tool_trigger.php +++ b/lang/en/tool_trigger.php @@ -186,7 +186,7 @@ $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['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.'; From 2dd3792ae6545c7f7f7c00e33cd001c6bc73c398 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Wed, 19 Jan 2022 22:52:11 +1100 Subject: [PATCH 08/23] [#164] Fix Unexpected Moodle Internal errors/warnings in CI --- classes/event_processor.php | 2 -- classes/helper/datafield_manager.php | 2 -- classes/helper/processor_helper.php | 2 -- classes/json/json_export.php | 2 -- classes/learn_process.php | 1 - classes/output/manageworkflows/renderer.php | 2 -- classes/privacy/provider.php | 2 -- classes/steps/actions/assign_cohort_action_step.php | 4 ++-- classes/steps/actions/base_action_step.php | 2 -- classes/steps/actions/email_action_step.php | 2 -- classes/steps/actions/http_post_action_step.php | 2 -- classes/steps/actions/logdump_action_step.php | 2 -- classes/steps/actions/role_assign_action_step.php | 2 -- classes/steps/actions/role_unassign_action_step.php | 2 -- classes/steps/actions/roles_unassign_action_step.php | 2 -- classes/steps/base/base_step.php | 2 -- classes/steps/debounce/debounce_step.php | 2 -- classes/steps/filters/base_filter_step.php | 2 -- classes/steps/filters/fail_filter_step.php | 2 -- classes/steps/filters/numcompare_filter_step.php | 2 -- classes/steps/filters/stringcompare_filter_step.php | 2 -- classes/steps/lookups/base_lookup_step.php | 2 -- classes/steps/lookups/cohort_lookup_step.php | 5 ++--- classes/steps/lookups/course_cat_lookup_step.php | 2 -- classes/steps/lookups/course_lookup_step.php | 2 -- classes/steps/lookups/roles_lookup_step.php | 2 -- classes/task/cleanup.php | 1 - classes/task/cleanup_history.php | 1 - classes/task/learn.php | 1 - classes/task/process_workflows.php | 1 - classes/task/update_trigger_helper_task.php | 2 -- classes/workflow.php | 2 -- classes/workflow_manager.php | 2 -- classes/workflow_process.php | 2 -- db/install.php | 2 -- db/upgrade.php | 2 -- lib.php | 2 -- tests/privacy_test.php | 2 -- 38 files changed, 4 insertions(+), 72 deletions(-) diff --git a/classes/event_processor.php b/classes/event_processor.php index 00c2f71..de8ee77 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. * diff --git a/classes/helper/datafield_manager.php b/classes/helper/datafield_manager.php index 38814a5..667e305 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. diff --git a/classes/helper/processor_helper.php b/classes/helper/processor_helper.php index a602c35..ca404ca 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 { /** 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/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 b802b11..aca43c0 100644 --- a/classes/steps/actions/http_post_action_step.php +++ b/classes/steps/actions/http_post_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/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/base/base_step.php b/classes/steps/base/base_step.php index eadc93d..1e0a629 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. * 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 29267c6..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. * 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..9c5394e 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. * 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 264de28..0f9d6d4 100644 --- a/classes/task/cleanup_history.php +++ b/classes/task/cleanup_history.php @@ -25,7 +25,6 @@ namespace tool_trigger\task; -defined('MOODLE_INTERNAL') || die(); /** * Task to cleanup old queue. */ 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 019dbd0..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. */ 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/upgrade.php b/db/upgrade.php index 7a0066c..1cf5bfb 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. * diff --git a/lib.php b/lib.php index 0deabb9..09e496e 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. 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; From ef86c7e1a104ecec58229291c431298596a25900 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Thu, 10 Mar 2022 11:38:59 +1100 Subject: [PATCH 09/23] Add webservice action step (#172) * Add support to run webservice functions internally - Will run on behalf of the designated user / in their context - Will currently accept a JSON blob in the parameters, similar to the HTTP POST action. * Add several adjustments to username handling and reducing side-effects - Add handling to set user back to previous after running the step which should avoid unintended side affects - Update handling of username so that if it's not provided, it uses the main admin user by default - Also refactored the code a bit to make it easier to see what's happening at a higher level (from the execute method) * Add custom validation support to all steps This should allow one to verify certain fields for steps to match what is expected, if the functionality cannot be done from the standard formlib checks alone * Adjust lang string & form to indicate behaviour of not setting username * Add custom validation in webservice action step - Validates username must exist for user selected, if set - Validates the function parameters that would be called. Note that due to templating, this will change the field templated to a ZERO, which might not be as expected. The only time this may not be what you want is probably if you are trying to add a field which is dynamic based on the previous input's value (possible but not common) * Fix PHPCS errors * Add tests for webservice action step * Fix more PHPCS issues (line too long) * Adjust for backwards compatibility with JSON_THROW_ON_ERROR This will now default to an array format (which is a valid input for a webservice function call) if the user did not provide parameters. Otherwise, it will attempt to decode the json and if it's still NULL, something went wrong. * Fix more unrelated PHPCS issues * Ensure parent form validation carries through also * Add handling to transform the data per step defined in each step class - In particular adding a transform for the webservice action step to prettify the 'params' (JSON input) so it is more readable/managable the next time it is loaded. * Fix tests using assertStringContainsString for backward compatibility * Version bump --- classes/helper/datafield_manager.php | 5 +- .../steps/actions/webservice_action_step.php | 301 ++++++++++++++++++ classes/steps/base/base_form.php | 15 +- classes/steps/base/base_step.php | 26 ++ classes/steps/lookups/roles_lookup_step.php | 2 +- lang/en/tool_trigger.php | 11 + lib.php | 6 + tests/fixtures/user_event_fixture.php | 2 +- tests/webservice_action_step_test.php | 124 ++++++++ version.php | 4 +- 10 files changed, 490 insertions(+), 6 deletions(-) create mode 100644 classes/steps/actions/webservice_action_step.php create mode 100644 tests/webservice_action_step_test.php diff --git a/classes/helper/datafield_manager.php b/classes/helper/datafield_manager.php index 667e305..5a6861b 100644 --- a/classes/helper/datafield_manager.php +++ b/classes/helper/datafield_manager.php @@ -38,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. * @@ -160,7 +163,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/steps/actions/webservice_action_step.php b/classes/steps/actions/webservice_action_step.php new file mode 100644 index 0000000..23693d3 --- /dev/null +++ b/classes/steps/actions/webservice_action_step.php @@ -0,0 +1,301 @@ +. + +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 $USER; + + $this->update_datafields($event, $stepresults); + + // Store the previous user, setting it back once the step is finished. + $previoususer = $USER; + + // Set the configured user as the one who will run the function. + $user = $this->get_user(); + \core\session\manager::set_user($user); + set_login_session_preferences(); + + // Run the function and parse the response to a step result. + $response = $this->run_function(); + if ($response['error']) { + return [false, $response['exception']]; + } + + // Restore the previous user to avoid any side-effects occuring in later steps / code. + \core\session\manager::set_user($previoususer); + set_login_session_preferences(); + + // Return the function call response as is. The shape is already normalised. + return [true, $response]; + } + + /** + * {@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 1e0a629..d5d5b16 100644 --- a/classes/steps/base/base_step.php +++ b/classes/steps/base/base_step.php @@ -178,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/lookups/roles_lookup_step.php b/classes/steps/lookups/roles_lookup_step.php index 9c5394e..2db19c2 100644 --- a/classes/steps/lookups/roles_lookup_step.php +++ b/classes/steps/lookups/roles_lookup_step.php @@ -74,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/lang/en/tool_trigger.php b/lang/en/tool_trigger.php index 4228009..5e61453 100644 --- a/lang/en/tool_trigger.php +++ b/lang/en/tool_trigger.php @@ -252,6 +252,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'; @@ -286,6 +287,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 09e496e..5724fea 100644 --- a/lib.php +++ b/lib.php @@ -86,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); @@ -111,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/tests/fixtures/user_event_fixture.php b/tests/fixtures/user_event_fixture.php index 1651325..ea6d867 100644 --- a/tests/fixtures/user_event_fixture.php +++ b/tests/fixtures/user_event_fixture.php @@ -101,4 +101,4 @@ public function add_user_custom_profile_field($shortname, $datatype, $forceuniqu return $data; } -} \ No newline at end of file +} diff --git a/tests/webservice_action_step_test.php b/tests/webservice_action_step_test.php new file mode 100644 index 0000000..1d991b0 --- /dev/null +++ b/tests/webservice_action_step_test.php @@ -0,0 +1,124 @@ +. + +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() { + $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 . '}}}', + ]; + + // 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); + } + + /** + * 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)); + $this->expectException(dml_missing_record_exception::class); + $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)); + list($status, $stepresults) = $step->execute(null, null, $this->event, []); + $this->assertFalse($status); + $this->assertNotNull($stepresults); + $this->assertObjectHasAttribute('errorcode', $stepresults); + $this->assertEquals('invalidparameter', $stepresults->errorcode); + $this->assertObjectHasAttribute('debuginfo', $stepresults); + // Alternative for assertStringContainsString used for earlier version compatibility. + $this->assertTrue(strpos($stepresults->debuginfo, 'Missing required key in single structure: enrolments') !== false); + $this->assertObjectNotHasAttribute('data', $stepresults); + } +} diff --git a/version.php b/version.php index 3a64643..1a908e4 100755 --- a/version.php +++ b/version.php @@ -25,8 +25,8 @@ defined('MOODLE_INTERNAL') || die(); $plugin->component = 'tool_trigger'; -$plugin->release = 2021030405; -$plugin->version = 2021030405; +$plugin->release = 2021030406; +$plugin->version = 2021030406; $plugin->requires = 2016052300; $plugin->maturity = MATURITY_STABLE; $plugin->dependencies = array('tool_monitor' => 2015051101); From a4d739ca995d80688544abc21a9f57e44cb0dde2 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Fri, 11 Mar 2022 14:13:31 +1100 Subject: [PATCH 10/23] chore: add reusable workflow for 3.5-3.10 Fixes #168 --- .github/workflows/ci.yml | 8 ++ .github/workflows/master.yml | 144 ----------------------------------- 2 files changed, 8 insertions(+), 144 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/master.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a1c9845 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,8 @@ +# .github/workflows/ci.yml +name: ci + +on: [push, pull_request] + +jobs: + test: + uses: catalyst/catalyst-moodle-workflows/.github/workflows/group-35-to-310-ci.yml@main 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 From cf22448c5b9a0e8804c0947c9bed674db08337f1 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Fri, 11 Mar 2022 14:13:45 +1100 Subject: [PATCH 11/23] docs: update supported branches in docs --- README.md | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a1c5128..144c38c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![Build Status](https://github.com/catalyst/moodle-tool_trigger/actions/workflows/master.yml/badge.svg?branch=master) +![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/catalyst/moodle-tool_trigger/ci/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 From 6a07b83e9c76c414f6976ac0fe544f93a4de0293 Mon Sep 17 00:00:00 2001 From: Kevin Pham Date: Fri, 11 Mar 2022 15:02:33 +1100 Subject: [PATCH 12/23] chore: disable behat for tests since there are no .feature test files --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a1c9845..fb130b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,3 +6,5 @@ on: [push, pull_request] jobs: test: uses: catalyst/catalyst-moodle-workflows/.github/workflows/group-35-to-310-ci.yml@main + with: + disable_behat: true From a2fa46cd7e39f71ab15c8bed094c231e18a4075a Mon Sep 17 00:00:00 2001 From: Peter Burnett Date: Thu, 21 Apr 2022 11:29:20 +1000 Subject: [PATCH 13/23] Improved anonymous execution user handling --- .../steps/actions/webservice_action_step.php | 23 +++++++++++++------ tests/webservice_action_step_test.php | 5 ++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/classes/steps/actions/webservice_action_step.php b/classes/steps/actions/webservice_action_step.php index 23693d3..971b61a 100644 --- a/classes/steps/actions/webservice_action_step.php +++ b/classes/steps/actions/webservice_action_step.php @@ -126,30 +126,39 @@ private function run_function() { * @return array if execution was succesful and the response from the execution. */ public function execute($step, $trigger, $event, $stepresults) { - global $USER; + global $SESSION, $USER; $this->update_datafields($event, $stepresults); - // Store the previous user, setting it back once the step is finished. + // 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(); // Run the function and parse the response to a step result. - $response = $this->run_function(); - if ($response['error']) { - return [false, $response['exception']]; + // 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']) { + $status = [false, $response['exception']]; + } else { + $status = [true, $response]; + } + } catch (\Throwable $e) { + $status = [false, $e->getMessage()]; } // Restore the previous user to avoid any side-effects occuring in later steps / code. \core\session\manager::set_user($previoususer); - set_login_session_preferences(); + $SESSION = $session; // Return the function call response as is. The shape is already normalised. - return [true, $response]; + return $status; } /** diff --git a/tests/webservice_action_step_test.php b/tests/webservice_action_step_test.php index 1d991b0..5245b5b 100644 --- a/tests/webservice_action_step_test.php +++ b/tests/webservice_action_step_test.php @@ -95,8 +95,9 @@ public function test_with_non_existent_function() { '{"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, []); + $result = $step->execute(null, null, $this->event, []); + $this->assertFalse($result[0]); + $this->assertTrue(strpos($result[1], get_string('invalidrecord', 'error', 'external_functions')) !== false); } /** From 065f3638ed75c4f33c6373c91483515935eff9e7 Mon Sep 17 00:00:00 2001 From: Peter Burnett Date: Thu, 21 Apr 2022 14:09:30 +1000 Subject: [PATCH 14/23] Fixed JS lint warnings and recompile --- .github/workflows/ci.yml | 2 +- amd/build/import_workflow.min.js | 14 ++++++++++++-- amd/build/import_workflow.min.js.map | 2 +- amd/build/step_select.min.js | 14 ++++++++++++-- amd/build/step_select.min.js.map | 2 +- amd/src/import_workflow.js | 4 +++- amd/src/step_select.js | 27 ++++++++++++++++++++++----- version.php | 1 + 8 files changed, 53 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fb130b1..64d9842 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,6 @@ on: [push, pull_request] jobs: test: - uses: catalyst/catalyst-moodle-workflows/.github/workflows/group-35-to-310-ci.yml@main + uses: catalyst/catalyst-moodle-workflows/.github/workflows/ci.yml@main with: disable_behat: true 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":"AA0BAA,OAAM,gCACJ,CAAC,QAAD,CAAW,UAAX,CAAuB,oBAAvB,CAA6C,mBAA7C,CAAiE,gBAAjE,CAAmF,WAAnF,CAAgG,eAAhG,CACI,mBADJ,CADI,CAGE,SAAUC,CAAV,CAAaC,CAAb,CAAkBC,CAAlB,CAAgCC,CAAhC,CAA6CC,CAA7C,CAAwDC,CAAxD,CAA8DC,CAA9D,CAAwEC,CAAxE,CAAsF,IAK9EC,CAAAA,CAAc,CAAG,EAL6D,CAM9EC,CAN8E,CAO9EC,CAP8E,CAQ9EC,CAAO,6HARuE,CAiBlF,QAASC,CAAAA,CAAT,EAA2B,IAEnBC,CAAAA,CAAM,CAAG,CAACC,YAAY,CAAEC,IAAI,CAACC,SAAL,CADb,EACa,CAAf,CAFU,CAGvBN,CAAQ,CAACO,OAAT,CAAiBN,CAAjB,EACAD,CAAQ,CAACO,OAAT,CAAiBX,CAAQ,CAACY,YAAT,CAAsB,cAAtB,CAAsC,iBAAtC,CAAyDT,CAAzD,CAAoEI,CAApE,CAAjB,CACH,CAMD,QAASM,CAAAA,CAAT,CAA0BC,CAA1B,CAA6B,CACzBA,CAAC,CAACC,cAAF,GAGA,GAAIC,CAAAA,CAAQ,CAAGZ,CAAQ,CAACa,OAAT,GAAmBC,IAAnB,CAAwB,MAAxB,EAAgCC,SAAhC,EAAf,CACAf,CAAQ,CAACO,OAAT,CAAiBN,CAAjB,EAGAN,CAAI,CAACqB,IAAL,CAAU,CAAC,CACPC,UAAU,CAAE,kCADL,CAEPC,IAAI,CAAE,CACFd,YAAY,CAAEC,IAAI,CAACC,SAAL,CAAeM,CAAf,CADZ,CAFC,CAAD,CAAV,EAKI,CALJ,EAKOO,IALP,CAKY,SAASC,CAAT,CAAuB,CAC/B,GAAIC,CAAAA,CAAW,CAAGhB,IAAI,CAACiB,KAAL,CAAWF,CAAX,CAAlB,CAEA,GAA6B,SAAzB,EAAAC,CAAW,CAACE,SAAhB,CAAwC,CAEpCC,QAAQ,CAACC,MAAT,IACH,CAHD,IAGO,CACHC,MAAM,CAACC,IAAP,CAAYN,CAAW,CAACO,OAAxB,EAAiCC,OAAjC,CAAyC,SAASC,CAAT,CAAc,CACnDjC,CAAY,CAACkC,eAAb,CAA6B,CACzBH,OAAO,CAAEP,CAAW,CAACO,OAAZ,CAAoBE,CAApB,CADgB,CAEzBE,IAAI,CAAE,OAFmB,CAA7B,CAIH,CALD,CAMH,CAEDhC,CAAQ,CAACiC,IAAT,EAEH,CAtBD,EAsBGC,IAtBH,CAsBQ,UAAW,CAEfrC,CAAY,CAACkC,eAAb,CAA6B,CACzBH,OAAO,CAAErC,CAAG,CAAC4C,UAAJ,CAAe,qBAAf,CAAsC,cAAtC,CADgB,CAEzBH,IAAI,CAAE,OAFmB,CAA7B,EAKAhC,CAAQ,CAACiC,IAAT,EACH,CA9BD,CA+BH,CAODnC,CAAc,CAACsC,IAAf,CAAsB,SAASC,CAAT,CAAkB,CAEpCtC,CAAS,CAAGsC,CAAZ,CAGA9C,CAAG,CAAC4C,UAAJ,CAAe,kBAAf,CAAmC,cAAnC,EAAmDG,IAAnD,CAAwD,SAASC,CAAT,CAAgB,CAEpE/C,CAAY,CAACgD,MAAb,CAAoB,CAChBR,IAAI,CAAExC,CAAY,CAACiD,KAAb,CAAmBC,WADT,CAEhBH,KAAK,CAAEA,CAFS,CAGhBI,IAAI,CAAE1C,CAHU,CAIhB2C,KAAK,GAJW,CAApB,CAKGtD,CAAC,CAAC,kBAAD,CALJ,EAMC6B,IAND,CAMM,SAAS0B,CAAT,CAAgB,CAClB7C,CAAQ,CAAG6C,CAAX,CACA7C,CAAQ,CAACa,OAAT,GAAmBiC,EAAnB,CAAsBrD,CAAW,CAACsD,IAAlC,CAAwCtC,CAAxC,EACAT,CAAQ,CAACa,OAAT,GAAmBiC,EAAnB,CAAsBrD,CAAW,CAACuD,MAAlC,CAA0C9C,CAA1C,EACAA,CAAe,EAClB,CAXD,CAYH,CAdD,CAgBH,CArBD,CAuBA,MAAOJ,CAAAA,CACV,CArGH,CAAN","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("