diff --git a/.travis.yml b/.travis.yml
index 607c80cf19e42a72abd2b946d5f60c72e477973c..1e94b2598ea3da1ed7f773415bbde0b30b89875a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -22,21 +22,20 @@ php:
 env:
   jobs:
     - DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE
-    - DB=pgsql MOODLE_BRANCH=MOODLE_37_STABLE
     - DB=pgsql MOODLE_BRANCH=MOODLE_38_STABLE
     - DB=pgsql MOODLE_BRANCH=MOODLE_39_STABLE
+    - DB=pgsql MOODLE_BRANCH=MOODLE_310_STABLE
     - DB=pgsql MOODLE_BRANCH=master
     - DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE
-    - DB=mysqli MOODLE_BRANCH=MOODLE_37_STABLE
     - DB=mysqli MOODLE_BRANCH=MOODLE_38_STABLE
     - DB=mysqli MOODLE_BRANCH=MOODLE_39_STABLE
+    - DB=mysqli MOODLE_BRANCH=MOODLE_310_STABLE
     - DB=mysqli MOODLE_BRANCH=master
 
 before_install:
   - phpenv config-rm xdebug.ini
-  - nvm install 14
   - cd ../..
-  - composer create-project -n --no-dev --prefer-dist blackboard-open-source/moodle-plugin-ci ci dev-master
+  - composer create-project -n --no-dev --prefer-dist moodlehq/moodle-plugin-ci ci ^3
   - export PATH="$(cd ci/bin; pwd):$(cd ci/vendor/bin; pwd):$PATH"
 
 jobs:
@@ -45,7 +44,7 @@ jobs:
     # Prechecks against latest Moodle stable only.
     - stage: static
       php: 7.4
-      env: DB=mysqli MOODLE_BRANCH=MOODLE_39_STABLE
+      env: DB=mysqli MOODLE_BRANCH=MOODLE_310_STABLE
       install:
         - moodle-plugin-ci install --no-init
       script:
@@ -61,7 +60,7 @@ jobs:
     # Smaller build matrix for development builds
     - stage: develop
       php: 7.4
-      env: DB=mysqli MOODLE_BRANCH=MOODLE_39_STABLE
+      env: DB=mysqli MOODLE_BRANCH=MOODLE_310_STABLE
   exclude:
     - php: 7.3
       env: DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE
@@ -71,14 +70,9 @@ jobs:
       env: DB=pgsql MOODLE_BRANCH=MOODLE_35_STABLE
     - php: 7.4
       env: DB=mysqli MOODLE_BRANCH=MOODLE_35_STABLE
-    - php: 7.4
-      env: DB=pgsql MOODLE_BRANCH=MOODLE_37_STABLE
-    - php: 7.4
-      env: DB=mysqli MOODLE_BRANCH=MOODLE_37_STABLE
 
 # Unit tests and behat tests against full matrix.
 install:
-  - docker run -d -p 127.0.0.1:4444:4444 --net=host -v /dev/shm:/dev/shm selenium/standalone-firefox:2.53.1
   - moodle-plugin-ci install
 script:
   - moodle-plugin-ci phpunit --coverage-clover
diff --git a/adminlib.php b/adminlib.php
index dbbe59051681a12e86d0689be061f07b6d71ea70..76afb02e086d0f681dcfa10108e9f9cd03b484f3 100644
--- a/adminlib.php
+++ b/adminlib.php
@@ -266,7 +266,7 @@ class admin_settings {
 
     /**
      * This is the entry point for this controller class.
-     * @param string $action Action string (see {@link action}).
+     * @param string $action Action string (see {@see action}).
      * @param int $workflowid Id of the workflow.
      * @throws \coding_exception
      * @throws \dml_exception
diff --git a/classes/local/manager/settings_manager.php b/classes/local/manager/settings_manager.php
index b6ca7de23f84f7de61341e5934ff4c78614764e7..7e76238a773697f5c1f81892b320b42a34fb6277 100644
--- a/classes/local/manager/settings_manager.php
+++ b/classes/local/manager/settings_manager.php
@@ -122,7 +122,7 @@ class settings_manager {
     /**
      * Returns an array of local subplugin settings for a given instance id
      * @param int $instanceid id of the step instance
-     * @param string $type Type of the setting (see {@link settings_type}).
+     * @param string $type Type of the setting (see {@see settings_type}).
      * @return array|null settings key-value pairs
      * @throws \coding_exception
      * @throws \dml_exception
@@ -164,7 +164,7 @@ class settings_manager {
     /**
      * Removes all local settings for a given instance id
      * @param int $instanceid id of the step instance
-     * @param string $type Type of the setting (see {@link settings_type}).
+     * @param string $type Type of the setting (see {@see settings_type}).
      * @throws \coding_exception
      * @throws \dml_exception
      */
@@ -179,7 +179,7 @@ class settings_manager {
 
     /**
      * Validates the type param for the two possibilities 'step' and 'trigger'.
-     * @param string $type Type of the setting (see {@link settings_type}).
+     * @param string $type Type of the setting (see {@see settings_type}).
      * @throws \coding_exception
      */
     private static function validate_type($type) {
diff --git a/classes/local/table/delayed_courses_table.php b/classes/local/table/delayed_courses_table.php
index 95cb68bae9452a3c08d2dd7ac401c7729ad9a0cf..a201fad149a6b9ce89a8bde9851de9409e024d02 100644
--- a/classes/local/table/delayed_courses_table.php
+++ b/classes/local/table/delayed_courses_table.php
@@ -67,25 +67,31 @@ class delayed_courses_table extends \table_sql {
         }
 
         if ($selectseperatedelays) {
-            $fields .= 'dw.workflowid, w.title as workflow, dw.delayeduntil AS workflowdelay, maxtable.wfcount AS workflowcount, ';
+            $fields .= 'wfdelay.workflowid, wfdelay.workflow, wfdelay.workflowdelay, wfdelay.workflowcount, ';
         } else {
             $fields .= 'null as workflowid, null as workflow, null AS workflowdelay, null AS workflowcount, ';
         }
 
-        $params = [];
-
         if ($selectglobaldelays) {
             $fields .= 'd.delayeduntil AS globaldelay';
         } else {
             $fields .= 'null AS globaldelay';
         }
 
+        $params = [];
+        $where = ["TRUE"];
+
         if ($selectglobaldelays && !$selectseperatedelays) {
             $from = '{tool_lifecycle_delayed} d ' .
                     'JOIN {course} c ON c.id = d.courseid ' .
                     'JOIN {course_categories} cat ON c.category = cat.id';
         } else {
-            $from = '(' .
+            $from = '{course} c ' .
+                    // For every course, add information about delays per workflow.
+                    'LEFT JOIN (' .
+                    'SELECT dw.courseid, dw.workflowid, w.title as workflow, ' .
+                    'dw.delayeduntil as workflowdelay,maxtable.wfcount as workflowcount ' .
+                    'FROM ( ' .
                     'SELECT courseid, MAX(dw.id) AS maxid, COUNT(*) AS wfcount ' .
                     'FROM {tool_lifecycle_delayed_workf} dw ' .
                     'JOIN {tool_lifecycle_workflow} w ON dw.workflowid = w.id ' . // To make sure no outdated delays are counted.
@@ -101,35 +107,36 @@ class delayed_courses_table extends \table_sql {
             $from .= 'GROUP BY courseid ' .
                     ') maxtable ' .
                     'JOIN {tool_lifecycle_delayed_workf} dw ON maxtable.maxid = dw.id ' .
-                    'JOIN {tool_lifecycle_workflow} w ON dw.workflowid = w.id ';
+                    'JOIN {tool_lifecycle_workflow} w ON dw.workflowid = w.id ' .
+                ') wfdelay ON wfdelay.courseid = c.id ';
 
             if ($selectglobaldelays) {
-                $from .= 'FULL JOIN (' .
+                // For every course, add information about global delay.
+                $from .= 'LEFT JOIN (' .
                         'SELECT * FROM {tool_lifecycle_delayed} d ' .
                         'WHERE d.delayeduntil > :time2 ' .
-                        ') d ON dw.courseid = d.courseid ' .
-                        'JOIN {course} c ON c.id = COALESCE(dw.courseid, d.courseid) ';
-
+                        ') d ON c.id = d.courseid ';
                 $params['time2'] = time();
+                // Only include course in resultset, if it has a delay of some kind (global or per wf).
+                $where[] = 'COALESCE(wfdelay.courseid, d.courseid) IS NOT NULL';
             } else {
-                $from .= 'JOIN {course} c ON c.id = dw.courseid ';
+                $where[] = 'wfdelay.courseid IS NOT NULL';
             }
 
             $from .= 'JOIN {course_categories} cat ON c.category = cat.id';
         }
 
-        $where = 'true ';
-
         if ($filterdata && $filterdata->category) {
-            $where .= 'AND cat.id = :catid ';
+            $where[] = 'cat.id = :catid ';
             $params['catid'] = $filterdata->category;
         }
 
         if ($filterdata && $filterdata->coursename) {
             global $DB;
-            $where .= 'AND c.fullname LIKE :cname';
+            $where[] = 'c.fullname LIKE :cname';
             $params['cname'] = '%' . $DB->sql_like_escape($filterdata->coursename) . '%';
         }
+        $where = join(" AND ", $where);
 
         $this->set_sql($fields, $from, $where, $params);
         $this->column_nosort = ['workflow', 'tools'];
diff --git a/tests/active_workflow_is_manual_test.php b/tests/active_workflow_is_manual_test.php
index 0ae330dccc176c98010fbeb7b1d039cfc07499f1..508a91feebcd1d60bf3f56a3cffe70916892dbf7 100644
--- a/tests/active_workflow_is_manual_test.php
+++ b/tests/active_workflow_is_manual_test.php
@@ -57,7 +57,7 @@ class tool_lifecycle_workflow_is_manual_testcase extends \advanced_testcase {
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         global $USER;
         $this->resetAfterTest(true);
         $generator = $this->getDataGenerator()->get_plugin_generator('tool_lifecycle');
diff --git a/tests/backup_and_restore_workflow_test.php b/tests/backup_and_restore_workflow_test.php
index ed8135613129e3d0e20b32efb84bdfe3508c543b..2e08e1a5339b299d0ee53340dc9e2a499b5bc3dd 100644
--- a/tests/backup_and_restore_workflow_test.php
+++ b/tests/backup_and_restore_workflow_test.php
@@ -56,7 +56,7 @@ class tool_lifecycle_backup_and_restore_workflow_testcase extends \advanced_test
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $generator = $this->getDataGenerator()->get_plugin_generator('tool_lifecycle');
         $this->workflow = $generator->create_workflow(['startdatedelay', 'categories'], ['email', 'createbackup', 'deletecourse']);
diff --git a/tests/backup_manager_test.php b/tests/backup_manager_test.php
index 9e9865a6dced9c842a01969921630f4e7cae5504..6e09ec2cafc4eccebef5a81aff8e4f8e2e818c9b 100644
--- a/tests/backup_manager_test.php
+++ b/tests/backup_manager_test.php
@@ -42,7 +42,7 @@ class tool_lifecycle_backup_manager_testcase extends \advanced_testcase {
     /**
      * Setup the testcase.
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(false);
         $this->course = $this->getDataGenerator()->create_course();
     }
diff --git a/tests/manual_trigger_tools_test.php b/tests/manual_trigger_tools_test.php
index 81ba80a03ab0358bef9f1e25f77edd8c6c851274..c609ddec4bcea95afc79a91af89d8230f60203d1 100644
--- a/tests/manual_trigger_tools_test.php
+++ b/tests/manual_trigger_tools_test.php
@@ -62,7 +62,7 @@ class tool_lifecycle_manual_trigger_tools_testcase extends \advanced_testcase {
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         global $USER;
         $this->resetAfterTest(true);
         $generator = $this->getDataGenerator()->get_plugin_generator('tool_lifecycle');
diff --git a/tests/manually_triggered_process_test.php b/tests/manually_triggered_process_test.php
index 5251f4064c31d85cc1644257b5759a9ad9c16e6f..6e7c4a180f2498307c6f237e371afce106352915 100644
--- a/tests/manually_triggered_process_test.php
+++ b/tests/manually_triggered_process_test.php
@@ -65,7 +65,7 @@ class tool_lifecycle_manually_triggered_process_testcase extends \advanced_testc
      * @throws coding_exception
      * @throws moodle_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         global $USER;
 
         // We do not need a sesskey check in theses tests.
diff --git a/tests/persistence/persist_process_data_test.php b/tests/persistence/persist_process_data_test.php
index 2803171ca199ec6d270ab90efb4273d0f334dedb..462d2d913029727b7e9cbf59b304fe12d1b282c6 100644
--- a/tests/persistence/persist_process_data_test.php
+++ b/tests/persistence/persist_process_data_test.php
@@ -54,7 +54,7 @@ class tool_lifecycle_persist_process_data_testcase extends \advanced_testcase {
      * @throws coding_exception
      * @throws dml_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $generator = $this->getDataGenerator()->get_plugin_generator('tool_lifecycle');
 
diff --git a/tests/persistence/persist_process_test.php b/tests/persistence/persist_process_test.php
index 08a7cfaf6399aaa69ebe9416728a259f90a4dca3..cf985dd0066baeca3cb3515adb84d67dac7c18e0 100644
--- a/tests/persistence/persist_process_test.php
+++ b/tests/persistence/persist_process_test.php
@@ -50,7 +50,7 @@ class tool_lifecycle_persist_process_testcase extends \advanced_testcase {
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $generator = $this->getDataGenerator()->get_plugin_generator('tool_lifecycle');
 
diff --git a/tests/persistence/persist_step_test.php b/tests/persistence/persist_step_test.php
index 0432a6734a06157bdbda47aa5e227b02be25f50b..e87f466eaf2da5009af4718c337ad7e55a97840f 100644
--- a/tests/persistence/persist_step_test.php
+++ b/tests/persistence/persist_step_test.php
@@ -51,7 +51,7 @@ class tool_lifecycle_persist_step_testcase extends \advanced_testcase {
      * @throws coding_exception
      * @throws moodle_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $this->generator = $this->getDataGenerator()->get_plugin_generator('tool_lifecycle');
 
diff --git a/tests/persistence/persist_workflow_test.php b/tests/persistence/persist_workflow_test.php
index 57dc4c0c0464a90c9dc05a7fa4ed1e34baa64418..a68aae36de1e845c2312a90c3f83eb704714b307 100644
--- a/tests/persistence/persist_workflow_test.php
+++ b/tests/persistence/persist_workflow_test.php
@@ -45,7 +45,7 @@ class tool_lifecycle_persist_workflow_testcase extends \advanced_testcase {
     /**
      * Setup the testcase.
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $record = new stdClass();
         $record->id = null;
diff --git a/tests/privacy_test.php b/tests/privacy_test.php
index 8ffe855a725dfac9b6d0029ebda2fa6993fd115f..07aada6fb096d4b53fc314a11e116ea9502b27e5 100644
--- a/tests/privacy_test.php
+++ b/tests/privacy_test.php
@@ -74,7 +74,7 @@ class tool_lifecycle_privacy_test extends provider_testcase {
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         global $USER;
 
         // We do not need a sesskey check in theses tests.
diff --git a/tests/process_status_message_test.php b/tests/process_status_message_test.php
index 1a9105c9e0fc5f9e8784b26594eb6895d166425d..b7b313ffec136e1a7c8291496bbcdfe1d9f90ff4 100644
--- a/tests/process_status_message_test.php
+++ b/tests/process_status_message_test.php
@@ -54,7 +54,7 @@ class tool_lifecycle_process_status_message_testcase extends \advanced_testcase
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         global $USER;
 
         // We do not need a sesskey check in theses tests.
diff --git a/tests/settings_manager_test.php b/tests/settings_manager_test.php
index 4e5c770fe838e614d3cdb1754afe98eb32c448f4..204dd41fac0e3e765caed162866070893c68e5eb 100644
--- a/tests/settings_manager_test.php
+++ b/tests/settings_manager_test.php
@@ -59,7 +59,7 @@ class tool_lifecycle_settings_manager_testcase extends \advanced_testcase {
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(false);
         $generator = $this->getDataGenerator()->get_plugin_generator('tool_lifecycle');
 
diff --git a/tests/workflow_actions_testcase.php b/tests/workflow_actions_testcase.php
index cc9ee82fd31477058ba2981fc148d6e11c039a6b..06b8780fe6aafd324d8f45aaecdbfb38e0a52751 100644
--- a/tests/workflow_actions_testcase.php
+++ b/tests/workflow_actions_testcase.php
@@ -50,7 +50,7 @@ abstract class workflow_actions_testcase extends \advanced_testcase {
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         global $USER;
         // We do not need a sesskey check in theses tests.
         $USER->ignoresesskey = true;
diff --git a/trigger/categories/tests/trigger_test.php b/trigger/categories/tests/trigger_test.php
index 3c92c857e68d237d03ed0fa0444e5aae2170c55e..4012ff69afc65cee71307ea5fb2660c2a27e3376 100644
--- a/trigger/categories/tests/trigger_test.php
+++ b/trigger/categories/tests/trigger_test.php
@@ -60,7 +60,7 @@ class tool_lifecycle_trigger_categories_testcase extends \advanced_testcase {
      * Setup the testcase.
      * @throws \moodle_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $this->setAdminUser();
 
diff --git a/trigger/delayedcourses/tests/trigger_test.php b/trigger/delayedcourses/tests/trigger_test.php
index 6560af542e44f7ed67a43ae69850f4cef6cdfe29..6238981fd6feaefaee162f86bdfa33dca149cedd 100644
--- a/trigger/delayedcourses/tests/trigger_test.php
+++ b/trigger/delayedcourses/tests/trigger_test.php
@@ -57,7 +57,7 @@ class tool_lifecycle_trigger_delayedcourses_testcase extends \advanced_testcase
     /** @var workflow Workflow delaying processes for all workflows */
     private $workflowdealayingallworkflows;
 
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $this->setAdminUser();
 
diff --git a/trigger/sitecourse/tests/trigger_test.php b/trigger/sitecourse/tests/trigger_test.php
index 1cec1ea8ae39e167589378cff02b2b6d22577f69..d9babbbf4ebf77aba518eb8c84faea6ad496effb 100644
--- a/trigger/sitecourse/tests/trigger_test.php
+++ b/trigger/sitecourse/tests/trigger_test.php
@@ -49,7 +49,7 @@ class tool_lifecycle_trigger_sitecourse_testcase extends \advanced_testcase {
      * Setup the testcase.
      * @throws coding_exception
      */
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $this->setAdminUser();
 
diff --git a/trigger/startdatedelay/tests/trigger_test.php b/trigger/startdatedelay/tests/trigger_test.php
index eeab387b7e0b9b79121b41479d56fd868b178eb9..04ac2aea5039f93a3deba29b321b988d00bb4a4f 100644
--- a/trigger/startdatedelay/tests/trigger_test.php
+++ b/trigger/startdatedelay/tests/trigger_test.php
@@ -48,7 +48,7 @@ class tool_lifecycle_trigger_startdatedelay_testcase extends \advanced_testcase
     /** @var $processor processor Instance of the lifecycle processor. */
     private $processor;
 
-    public function setUp() {
+    public function setUp() : void {
         $this->resetAfterTest(true);
         $this->setAdminUser();
 
diff --git a/version.php b/version.php
index 947b0b6b96c9ff1de0c093f0f9a32f3560fc30af..8fb2ec44298b80f6c88a4e2e35505dbb4db140a2 100644
--- a/version.php
+++ b/version.php
@@ -25,7 +25,7 @@
 defined('MOODLE_INTERNAL') || die;
 
 $plugin->maturity = MATURITY_BETA;
-$plugin->version  = 2020091800;
+$plugin->version  = 2020111600;
 $plugin->component = 'tool_lifecycle';
 $plugin->requires = 2017111300; // Require Moodle 3.4 (or above).
-$plugin->release = 'v3.9-r1';
+$plugin->release = 'v3.10-r1';