';
+ echo '
';
+ echo \html_writer::div('0', 'totalrows badge badge-primary badge-pill mr-2',
+ ['id' => 'adminapprove_totalrows']);
+ $button = new \single_button(new moodle_url($PAGE->url, ['act' => ROLLBACK_ALL]),
+ get_string(ROLLBACK_ALL, 'lifecyclestep_adminapprove'));
+ echo $OUTPUT->render($button);
$button = new \single_button(new moodle_url($PAGE->url, ['act' => PROCEED_ALL]),
- get_string(PROCEED_ALL, 'lifecyclestep_adminapprove'));
+ get_string(PROCEED_ALL, 'lifecyclestep_adminapprove'), 'post', 'primary');
echo $OUTPUT->render($button);
-
- $button = new \single_button(new moodle_url($PAGE->url, ['act' => ROLLBACK_ALL]),
- get_string(ROLLBACK_ALL, 'lifecyclestep_adminapprove'));
+ $button = new \single_button(new moodle_url($PAGE->url, ['act' => ROLLBACK]),
+ get_string('rollbackselected', 'lifecyclestep_adminapprove'), 'post', 'secondary');
+ echo $OUTPUT->render($button);
+ $button = new \single_button(new moodle_url($PAGE->url, ['act' => PROCEED]),
+ get_string('proceedselected', 'lifecyclestep_adminapprove'), 'post', 'primary');
echo $OUTPUT->render($button);
echo '
';
- $PAGE->requires->js_call_amd('lifecyclestep_adminapprove/init', 'init', [sesskey(), $PAGE->url->out()]);
+
+ echo '
';
+
+ $PAGE->requires->js_call_amd('lifecyclestep_adminapprove/init', 'init',
+ [sesskey(), $PAGE->url->out(), $table->totalrows]);
} else {
echo get_string('no_courses_waiting', 'lifecyclestep_adminapprove',
['step' => $step->instancename, 'workflow' => $workflow->title]);
diff --git a/step/adminapprove/classes/decision_table.php b/step/adminapprove/classes/decision_table.php
index 2b811068..9c8eae3c 100644
--- a/step/adminapprove/classes/decision_table.php
+++ b/step/adminapprove/classes/decision_table.php
@@ -24,6 +24,8 @@
namespace lifecyclestep_adminapprove;
+use core_date;
+
defined('MOODLE_INTERNAL') || die();
global $CFG;
@@ -34,17 +36,6 @@
*/
class decision_table extends \table_sql {
- /**
- * @var coursecategory
- */
- private $category;
-
- /**
- * @var string pattern for the coursename.
- */
- private $coursename;
-
-
/**
* Constructs the table.
* @param int $stepid
@@ -55,8 +46,6 @@ class decision_table extends \table_sql {
*/
public function __construct($stepid, $courseid, $category, $coursename) {
parent::__construct('lifecyclestep_adminapprove-decisiontable');
- $this->category = $category;
- $this->coursename = $coursename;
$this->define_baseurl("/admin/tool/lifecycle/step/adminapprove/approvestep.php?stepid=$stepid");
$this->define_columns(['checkbox', 'courseid', 'course', 'category', 'startdate', 'tools']);
$this->define_headers(
@@ -90,10 +79,10 @@ public function __construct($stepid, $courseid, $category, $coursename) {
$where .= "AND c.fullname LIKE :cname ";
$params['cname'] = '%' . $DB->sql_like_escape($coursename) . '%';
}
+
$this->set_sql($fields, $from, $where, $params);
}
-
/**
* Column of checkboxes.
* @param object $row
@@ -137,9 +126,12 @@ public function col_category($row) {
* @return string human readable date
*/
public function col_startdate($row) {
+ global $USER;
+
if ($row->startdate) {
$dateformat = get_string('strftimedate', 'langconfig');
- return userdate($row->startdate, $dateformat);
+ return userdate($row->startdate, $dateformat,
+ core_date::get_user_timezone($USER));
} else {
return "-";
}
@@ -153,14 +145,14 @@ public function col_startdate($row) {
*/
public function col_tools($row) {
$output = \html_writer::start_div('singlebutton mr-1');
- $output .= \html_writer::tag('button', get_string('proceed', 'lifecyclestep_adminapprove'),
- ['class' => 'btn btn-secondary adminapprove-action', 'data-action' => 'proceed', 'data-content' => $row->id,
- 'type' => 'button']);
+ $output .= \html_writer::tag('button', get_string('rollback', 'lifecyclestep_adminapprove'),
+ ['class' => 'btn btn-secondary adminapprove-action', 'data-action' => 'rollback', 'data-content' => $row->id,
+ 'type' => 'button']);
$output .= \html_writer::end_div();
$output .= \html_writer::start_div('singlebutton mr-1 ml-0 mt-1');
- $output .= \html_writer::tag('button', get_string('rollback', 'lifecyclestep_adminapprove'),
- ['class' => 'btn btn-secondary adminapprove-action', 'data-action' => 'rollback', 'data-content' => $row->id,
- 'type' => 'button']);
+ $output .= \html_writer::tag('button', get_string('proceed', 'lifecyclestep_adminapprove'),
+ ['class' => 'btn btn-primary adminapprove-action', 'data-action' => 'proceed', 'data-content' => $row->id,
+ 'type' => 'button']);
$output .= \html_writer::end_div();
return $output;
}
diff --git a/step/adminapprove/index.php b/step/adminapprove/index.php
index 5383b8e1..9211fa8a 100644
--- a/step/adminapprove/index.php
+++ b/step/adminapprove/index.php
@@ -23,15 +23,15 @@
*/
use tool_lifecycle\tabs;
+use tool_lifecycle\urls;
require_once(__DIR__ . '/../../../../../config.php');
require_once($CFG->libdir . '/adminlib.php');
-require_login();
-require_capability('moodle/site:config', context_system::instance());
+require_admin();
$PAGE->set_context(context_system::instance());
-$PAGE->set_url(new \moodle_url("/admin/tool/lifecycle/step/adminapprove/index.php"));
+$PAGE->set_url(new \moodle_url(urls::SUBPLUGINS));
$PAGE->set_pagetype('admin-setting-' . 'tool_lifecycle');
$PAGE->set_pagelayout('admin');
diff --git a/step/adminapprove/lib.php b/step/adminapprove/lib.php
index 8862fe57..c53fb3b7 100644
--- a/step/adminapprove/lib.php
+++ b/step/adminapprove/lib.php
@@ -44,11 +44,12 @@ class adminapprove extends libbase {
private static $newcourses = 0;
/**
- * Provess a single course.
+ * Process a single course.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
+ * @throws \dml_exception
*/
public function process_course($processid, $instanceid, $course) {
global $DB;
@@ -85,7 +86,7 @@ public function get_subpluginname() {
* Process a course which is waiting.
* @param int $processid
* @param int $instanceid
- * @param stdClass $course
+ * @param int $course
* @return step_response
* @throws \dml_exception
*/
@@ -137,7 +138,7 @@ public function post_processing_bulk_operation() {
*/
public function instance_settings() {
return [
- new instance_setting('statusmessage', PARAM_TEXT),
+ new instance_setting('statusmessage', PARAM_TEXT, true),
];
}
diff --git a/step/adminapprove/tests/admin_approve_test.php b/step/adminapprove/tests/admin_approve_test.php
index 3510f56e..2a831575 100644
--- a/step/adminapprove/tests/admin_approve_test.php
+++ b/step/adminapprove/tests/admin_approve_test.php
@@ -16,9 +16,8 @@
namespace lifecyclestep_adminapprove;
-namespace lifecyclestep_adminapprove;
-
defined('MOODLE_INTERNAL') || die();
+
require_once(__DIR__ . '/../../../tests/generator/lib.php');
use tool_lifecycle\local\manager\process_manager;
diff --git a/step/adminapprove/version.php b/step/adminapprove/version.php
index 8b87a7f5..3cfcf385 100644
--- a/step/adminapprove/version.php
+++ b/step/adminapprove/version.php
@@ -26,9 +26,6 @@
$plugin->version = 2025050400;
$plugin->component = 'lifecyclestep_adminapprove';
-$plugin->dependencies = [
- 'tool_lifecycle' => 2025050400,
-];
$plugin->requires = 2022112800; // Requires Moodle 4.1+.
$plugin->supported = [401, 405];
$plugin->release = 'v4.5-r1';
diff --git a/step/createbackup/lang/de/lifecyclestep_createbackup.php b/step/createbackup/lang/de/lifecyclestep_createbackup.php
index 9b9aa793..108e7f9d 100644
--- a/step/createbackup/lang/de/lifecyclestep_createbackup.php
+++ b/step/createbackup/lang/de/lifecyclestep_createbackup.php
@@ -23,7 +23,7 @@
*/
$string['maximumbackupspercron'] = 'Maximale Anzahl an Sicherungen per cron';
-$string['plugindescription'] = 'Stößt ein Backup der getriggerten Kursen an.';
+$string['plugindescription'] = 'Stößt ein Backup der getriggerten Kurse an.';
$string['pluginname'] = 'Kurssicherungs-Schritt';
$string['privacy:metadata'] = 'Dieses Subplugin speichert keine persönlichen Daten.';
diff --git a/step/createbackup/lib.php b/step/createbackup/lib.php
index 18724439..f95e9d82 100644
--- a/step/createbackup/lib.php
+++ b/step/createbackup/lib.php
@@ -23,6 +23,7 @@
*/
namespace tool_lifecycle\step;
+use stdClass;
use tool_lifecycle\local\manager\settings_manager;
use tool_lifecycle\local\response\step_response;
use tool_lifecycle\local\manager\backup_manager;
@@ -50,14 +51,14 @@ class createbackup extends libbase {
private static $numberofbackups = 0;
/**
- * Processes the course and returns a repsonse.
+ * Processes the course and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
* @throws \coding_exception
* @throws \dml_exception
@@ -78,7 +79,7 @@ public function process_course($processid, $instanceid, $course) {
* Simply call the process_course since it handles everything necessary for this plugin.
* @param int $processid
* @param int $instanceid
- * @param mixed $course
+ * @param stdClass $course
* @return step_response
* @throws \coding_exception
* @throws \dml_exception
diff --git a/step/deletecourse/lib.php b/step/deletecourse/lib.php
index 1509cdaf..3253efda 100644
--- a/step/deletecourse/lib.php
+++ b/step/deletecourse/lib.php
@@ -23,6 +23,7 @@
*/
namespace tool_lifecycle\step;
+use stdClass;
use tool_lifecycle\local\manager\settings_manager;
use tool_lifecycle\local\response\step_response;
use tool_lifecycle\settings_type;
@@ -44,14 +45,14 @@ class deletecourse extends libbase {
private static $numberofdeletions = 0;
/**
- * Processes the course and returns a repsonse.
+ * Processes the course and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
* @throws \coding_exception
* @throws \dml_exception
@@ -59,11 +60,16 @@ class deletecourse extends libbase {
public function process_course($processid, $instanceid, $course) {
global $CFG;
+ if ($course->id == 1) {
+ return step_response::rollback();
+ }
+
if (self::$numberofdeletions >= settings_manager::get_settings(
$instanceid, settings_type::STEP)['maximumdeletionspercron']) {
return step_response::waiting(); // Wait with further deletions til the next cron run.
}
- delete_course($course->id, true);
+
+ delete_course($course);
/* Fix 'delete & backup (other) course aftwerwards' error, which is created by moodle core issue
MDL-65228 (https://tracker.moodle.org/browse/MDL-65228) */
@@ -77,15 +83,17 @@ public function process_course($processid, $instanceid, $course) {
}
/**
- * Processes the course in status waiting and returns a repsonse.
+ * Processes the course in status waiting and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
+ * @throws \coding_exception
+ * @throws \dml_exception
*/
public function process_waiting_course($processid, $instanceid, $course) {
return $this->process_course($processid, $instanceid, $course);
diff --git a/step/duplicate/lib.php b/step/duplicate/lib.php
index d730b8df..d9abd38c 100644
--- a/step/duplicate/lib.php
+++ b/step/duplicate/lib.php
@@ -23,6 +23,7 @@
*/
namespace tool_lifecycle\step;
+use stdClass;
use tool_lifecycle\local\manager\process_manager;
use tool_lifecycle\local\manager\settings_manager;
use tool_lifecycle\local\response\step_response;
@@ -49,18 +50,19 @@ class duplicate extends libbase {
const PROC_DATA_COURSESHORTNAME = 'shortname';
/**
- * Processes the course and returns a repsonse.
+ * Processes the course and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
* @throws \dml_exception
*/
public function process_course($processid, $instanceid, $course) {
+ $course = get_course($course->id);
$fullname = process_data_manager::get_process_data($processid, $instanceid, self::PROC_DATA_COURSEFULLNAME);
$shortname = process_data_manager::get_process_data($processid, $instanceid, self::PROC_DATA_COURSESHORTNAME);
if (!empty($fullname) && !empty($shortname)) {
@@ -84,14 +86,14 @@ public function process_course($processid, $instanceid, $course) {
}
/**
- * Processes the course in status waiting and returns a repsonse.
+ * Processes the course in status waiting and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
* @throws \dml_exception
*/
diff --git a/step/email/interactionlib.php b/step/email/interactionlib.php
index 65054d19..c67cfd53 100644
--- a/step/email/interactionlib.php
+++ b/step/email/interactionlib.php
@@ -58,8 +58,8 @@ public function get_relevant_capability() {
}
/**
- * Returns an array of interaction tools to be displayed to be displayed on the view.php
- * Every entry is itself an array which consist of three elements:
+ * Returns an array of interaction tools to be displayed on view.php
+ * Every entry is itself an array that consists of two elements:
* 'action' => an action string, which is later passed to handle_action
* 'alt' => a string text of the button
* @param process $process process the action tools are requested for
@@ -90,10 +90,10 @@ public function get_status_message($process) {
* @param step_subplugin $step instance of the step the process is currently in.
* @param string $action action string
* @return step_interactive_response defines if the step still wants to process this course
- * - proceed: the step has finished and respective controller class can take over.
+ * - proceed: the step has finished and the respective controller class can take over.
* - stillprocessing: the step still wants to process the course and is responsible for rendering the site.
* - noaction: the action is not defined for the step.
- * - rollback: the step has finished and respective controller class should rollback the process.
+ * - rollback: the step has finished and the respective controller class should rollback the process.
*/
public function handle_interaction($process, $step, $action = 'default') {
if ($action == self::ACTION_KEEP) {
diff --git a/step/email/lib.php b/step/email/lib.php
index e4f5bd6b..d412f56b 100644
--- a/step/email/lib.php
+++ b/step/email/lib.php
@@ -44,14 +44,14 @@
class email extends libbase {
/**
- * Processes the course and returns a repsonse.
+ * Processes the course and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param object $course to be processed.
* @return step_response
* @throws \coding_exception
* @throws \dml_exception
@@ -72,14 +72,14 @@ public function process_course($processid, $instanceid, $course) {
}
/**
- * Processes the course in status waiting and returns a repsonse.
+ * Processes the course in status waiting and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param object $course to be processed.
* @return step_response
* @throws \coding_exception
* @throws \dml_exception
@@ -155,7 +155,7 @@ public function post_processing_bulk_operation() {
* @throws \dml_exception
* @throws \moodle_exception
*/
- private function replace_placeholders($strings, $user, $stepid, $mailentries) {
+ public function replace_placeholders($strings, $user, $stepid, $mailentries) {
global $CFG;
$patterns = [];
diff --git a/step/email/tests/lib_test.php b/step/email/tests/lib_test.php
index fba24188..0293e301 100644
--- a/step/email/tests/lib_test.php
+++ b/step/email/tests/lib_test.php
@@ -50,14 +50,10 @@ public function test_replace_placeholders(): void {
$course1 = $this->getDataGenerator()->create_course(['fullname' => 'Course 1', 'shortname' => 'C1']);
$course2 = $this->getDataGenerator()->create_course(['fullname' => 'Course 2', 'shortname' => 'C2']);
$lib = new email();
- $callreplaceplaceholders = function($strings, $user, $stepid, $mailentries) {
- return $this->replace_placeholders($strings, $user, $stepid, $mailentries);
- };
- $response = $callreplaceplaceholders->call($lib, [
+ $response = $lib->replace_placeholders([
"##firstname##\n##lastname##\n##courses##\n##shortcourses##",
"##firstname##
##lastname##
##courses-html##
##shortcourses-html##",
], $user1, 0, [(object) ['courseid' => $course1->id], (object) ['courseid' => $course2->id]]);
-
$this->assertCount(2, $response);
$this->assertEquals("Jane\nDoe\nCourse 1\nCourse 2\nC1\nC2", $response[0]);
$this->assertEquals("Jane
Doe
Course 1
Course 2
C1
C2", $response[1]);
diff --git a/step/lib.php b/step/lib.php
index 6c439f9d..5918c26d 100644
--- a/step/lib.php
+++ b/step/lib.php
@@ -24,6 +24,7 @@
*/
namespace tool_lifecycle\step;
+use stdClass;
use tool_lifecycle\local\entity\process;
use tool_lifecycle\local\manager\step_manager;
use tool_lifecycle\local\response\step_response;
@@ -41,27 +42,27 @@
abstract class libbase {
/**
- * Processes the course and returns a repsonse.
+ * Processes the course and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
*/
abstract public function process_course($processid, $instanceid, $course);
/**
- * Processes the course in status waiting and returns a repsonse.
+ * Processes the course in status waiting and returns a response.
* The response tells either
* - that the subplugin is finished processing.
* - that the subplugin is not yet finished processing.
* - that a rollback for this course is necessary.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
*/
public function process_waiting_course($processid, $instanceid, $course) {
@@ -150,7 +151,6 @@ public function extend_add_instance_form_validation(&$error, $data) {
public function abort_course($process) {
}
-
/**
* Ensure validity of settings upon backup restoration.
* @param array $settings
@@ -160,6 +160,30 @@ public function ensure_validity(array $settings): array {
return [];
}
+ /**
+ * Define description of the step.
+ * Allow subplugins to have custom description.
+ *
+ * @return string description of the trigger.
+ */
+ public function get_plugin_description() {
+ return get_string("pluginname", "lifecyclestep_" . $this->get_subpluginname());
+ }
+
+ /**
+ * Returns the settings of the step.
+ *
+ * @return void
+ */
+ public function get_plugin_settings() {
+ $step = $this->get_subpluginname();
+ $file = __DIR__ . "/$step/settings.php";
+
+ if (file_exists($file)) {
+ include($file);
+ }
+ }
+
}
/**
diff --git a/step/makeinvisible/lib.php b/step/makeinvisible/lib.php
index 46b59f7b..83737594 100644
--- a/step/makeinvisible/lib.php
+++ b/step/makeinvisible/lib.php
@@ -24,8 +24,10 @@
namespace tool_lifecycle\step;
+use stdClass;
use tool_lifecycle\local\manager\process_data_manager;
use tool_lifecycle\local\response\step_response;
+use function update_course;
defined('MOODLE_INTERNAL') || die();
@@ -45,7 +47,7 @@ class makeinvisible extends libbase {
*
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be processed.
+ * @param stdClass $course to be processed.
* @return step_response
*/
public function process_course($processid, $instanceid, $course) {
@@ -59,7 +61,8 @@ public function process_course($processid, $instanceid, $course) {
* Roll back the changes.
* @param int $processid of the respective process.
* @param int $instanceid of the step instance.
- * @param mixed $course to be rolled back.
+ * @param stdClass $course to be rolled back.
+ * @throws \moodle_exception
*/
public function rollback_course($processid, $instanceid, $course) {
global $CFG;
@@ -70,11 +73,11 @@ public function rollback_course($processid, $instanceid, $course) {
require_once($CFG->dirroot . '/course/lib.php');
$cat = \core_course_category::get($course->category, MUST_EXIST, true);
- $record = new \stdClass();
+ $record = new stdClass();
$record->id = $course->id;
$record->visibleold = (bool) process_data_manager::get_process_data($processid, $instanceid, 'visibleold');
$record->visible = $record->visibleold && (bool)$cat->visible;
- \update_course($record);
+ update_course($record);
}
/**
diff --git a/step/makeinvisible/version.php b/step/makeinvisible/version.php
index 94f34f4b..a16808a8 100644
--- a/step/makeinvisible/version.php
+++ b/step/makeinvisible/version.php
@@ -28,8 +28,5 @@
$plugin->requires = 2022112800; // Requires Moodle 4.1+.
$plugin->supported = [401, 405];
$plugin->component = 'lifecyclestep_makeinvisible';
-$plugin->dependencies = [
- 'tool_lifecycle' => 2025050400,
-];
$plugin->release = 'v4.5-r1';
$plugin->maturity = MATURITY_STABLE;
diff --git a/step/movecategory/classes/privacy/provider.php b/step/movecategory/classes/privacy/provider.php
new file mode 100644
index 00000000..f4b981fd
--- /dev/null
+++ b/step/movecategory/classes/privacy/provider.php
@@ -0,0 +1,39 @@
+.
+
+namespace lifecyclestep_movecategory\privacy;
+
+use core_privacy\local\metadata\null_provider;
+
+/**
+ * Privacy subsystem implementation for lifecyclestep_movecategory.
+ *
+ * @package lifecyclestep_movecategory
+ * @copyright 2023 Justus Dieckmann WWU Münster
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class provider implements null_provider {
+
+ /**
+ * Get the language string identifier with the component's language
+ * file to explain why this plugin stores no data.
+ *
+ * @return string
+ */
+ public static function get_reason(): string {
+ return 'privacy:metadata';
+ }
+}
diff --git a/step/movecategory/interactionlib.php b/step/movecategory/interactionlib.php
new file mode 100644
index 00000000..e3bc78d0
--- /dev/null
+++ b/step/movecategory/interactionlib.php
@@ -0,0 +1,106 @@
+.
+
+/**
+ * Interface for the interactions of the subplugintype step
+ *
+ * @package lifecyclestep_movecategory
+ * @copyright 2023 Norman Stulier, JMU Universität Würzburg Rechenzentrum.
+ * @copyright based on code from moodle-lifecyclestep_adminapprove by 2017 Tobias Reischmann WWU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+namespace tool_lifecycle\step;
+
+use tool_lifecycle\local\entity\process;
+use tool_lifecycle\local\entity\step_subplugin;
+use tool_lifecycle\local\manager\settings_manager;
+use tool_lifecycle\local\manager\step_manager;
+use tool_lifecycle\local\response\step_interactive_response;
+use tool_lifecycle\settings_type;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/../interactionlib.php');
+require_once(__DIR__ . '/lib.php');
+
+/**
+ * movecategory interactionlib
+ *
+ * @package lifecyclestep_movecategory
+ * @copyright 2023 Norman Stulier, JMU Universität Würzburg Rechenzentrum.
+ * @copyright based on code from moodle-lifecyclestep_adminapprove by 2017 Tobias Reischmann WWU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class interactionmovecategory extends interactionlibbase {
+
+
+ /**
+ * Returns the capability a user has to have to make decisions for a specific course.
+ * @return string capability string.
+ */
+ public function get_relevant_capability() {
+ return null;
+ }
+
+ /**
+ * Returns an array of interaction tools to be displayed to be displayed on the view.php for the given process
+ * Every entry is itself an array which consist of three elements:
+ * 'action' => an action string, which is later passed to handle_action
+ * 'alt' => a string text of the button
+ * @param process $process process the action tools are requested for
+ * @return array of action tools
+ */
+ public function get_action_tools($process) {
+ return [];
+ }
+
+ /**
+ * Returns the status message for the given process.
+ * @param process $process process the status message is requested for
+ * @return string status message
+ * @throws \coding_exception
+ */
+ public function get_status_message($process) {
+ $step = step_manager::get_step_instance_by_workflow_index($process->workflowid, $process->stepindex);
+ return settings_manager::get_settings($step->id, settings_type::STEP)['statusmessage'];
+ }
+
+ /**
+ * Returns the display name for the given action.
+ * Used for the past actions table in view.php.
+ * @param string $action Identifier of action
+ * @param string $userlink html-link with username as text that refers to the user profile.
+ * @return string action display name
+ */
+ public function get_action_string($action, $userlink) {
+ return "";
+ }
+
+ /**
+ * Called when a user triggered an action for a process instance.
+ * @param process $process instance of the process the action was triggered upon.
+ * @param step_subplugin $step instance of the step the process is currently in.
+ * @param string $action action string. The function is called with 'default', during interactive processing.
+ * @return step_interactive_response defines if the step still wants to process this course
+ * - proceed: the step has finished and respective controller class can take over.
+ * - stillprocessing: the step still wants to process the course and is responsible for rendering the site.
+ * - noaction: the action is not defined for the step.
+ * - rollback: the step has finished and respective controller class should rollback the process.
+ */
+ public function handle_interaction($process, $step, $action = 'default') {
+ return step_interactive_response::no_action();
+ }
+}
diff --git a/step/movecategory/lang/de/lifecyclestep_movecategory.php b/step/movecategory/lang/de/lifecyclestep_movecategory.php
new file mode 100644
index 00000000..db75a915
--- /dev/null
+++ b/step/movecategory/lang/de/lifecyclestep_movecategory.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Lang strings for movecategory step
+ *
+ * @package lifecyclestep_movecategory
+ * @copyright 2019 Yorick Reum JMU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['categorytomoveto'] = 'Zielkursbereich';
+$string['categorytomoveto_help'] = 'Bestimme den Kursbereich, in den die Kurse verschoben werden sollen.';
+$string['plugindescription'] = 'Verschiebt die ausgewählten Kurse in einen (anderen) Kursbereich.';
+$string['pluginname'] = 'Kurs verschieben - Schritt';
+$string['privacy:metadata'] = 'Dieses Subplugin speichert keine persönlichen Daten.';
diff --git a/step/movecategory/lang/en/lifecyclestep_movecategory.php b/step/movecategory/lang/en/lifecyclestep_movecategory.php
new file mode 100644
index 00000000..d1e95ff8
--- /dev/null
+++ b/step/movecategory/lang/en/lifecyclestep_movecategory.php
@@ -0,0 +1,29 @@
+.
+
+/**
+ * Lang strings for movecategory step
+ *
+ * @package lifecyclestep_movecategory
+ * @copyright 2019 Yorick Reum JMU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['categorytomoveto'] = 'Target Category';
+$string['categorytomoveto_help'] = 'Set the category which you want to move the courses to.';
+$string['plugindescription'] = 'Moves all triggered courses to a defined course category. ';
+$string['pluginname'] = 'Move Category Step';
+$string['privacy:metadata'] = 'The lifecyclestep_movecategory plugin does not store any personal data.';
diff --git a/step/movecategory/lib.php b/step/movecategory/lib.php
new file mode 100644
index 00000000..a2ac984f
--- /dev/null
+++ b/step/movecategory/lib.php
@@ -0,0 +1,114 @@
+.
+
+/**
+ * Interface for the subplugintype step
+ * It has to be implemented by all subplugins.
+ *
+ * @package lifecyclestep_movecategory
+ * @copyright 2019 Yorick Reum JMU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_lifecycle\step;
+
+use stdClass;
+use tool_lifecycle\local\manager\settings_manager;
+use tool_lifecycle\local\response\step_response;
+use tool_lifecycle\settings_type;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/../lib.php');
+
+/**
+ * Class for processing courses.
+ */
+class movecategory extends libbase {
+
+ /**
+ * Processes the course and returns a response.
+ * The response tells either
+ * - that the subplugin is finished processing.
+ * - that the subplugin is not yet finished processing.
+ * - that a rollback for this course is necessary.
+ *
+ * @param int $processid of the respective process.
+ * @param int $instanceid of the step instance.
+ * @param stdClass $course to be processed.
+ * @return step_response
+ * @throws \coding_exception
+ * @throws \dml_exception
+ */
+ public function process_course($processid, $instanceid, $course) {
+ $categoryid = settings_manager::get_settings(
+ $instanceid,
+ settings_type::STEP
+ )['categorytomoveto'];
+
+ $success = move_courses(
+ [$course->id], $categoryid
+ );
+
+ if ($success) {
+ return step_response::proceed();
+ } else {
+ return step_response::rollback();
+ }
+
+ }
+
+ /**
+ * Settings for the movecategory step
+ *
+ * @return instance_setting[] categorymoveto
+ */
+ public function instance_settings() {
+ return [
+ new instance_setting('categorytomoveto', PARAM_INT),
+ ];
+ }
+
+ /**
+ * Form elements for the instance.
+ *
+ * @param \MoodleQuickForm $mform
+ * @return void
+ * @throws \coding_exception
+ * @throws \dml_exception
+ */
+ public function extend_add_instance_form_definition($mform) {
+
+ $elementname = 'categorytomoveto';
+
+ // Fetch a complete list of courses and let it be shown in a flat hierarchical view with all parent branches.
+ $displaylist = \core_course_category::make_categories_list('moodle/course:changecategory');
+
+ $mform->addElement('autocomplete', $elementname,
+ get_string('categorytomoveto', 'lifecyclestep_movecategory'), $displaylist);
+ $mform->addHelpButton($elementname, 'categorytomoveto', 'lifecyclestep_movecategory');
+ $mform->setType($elementname, PARAM_INT);
+ }
+
+ /**
+ * Function to return subpluginname
+ *
+ * @return string subpluginname
+ */
+ public function get_subpluginname() {
+ return 'movecategory';
+ }
+}
diff --git a/step/movecategory/version.php b/step/movecategory/version.php
new file mode 100644
index 00000000..941b534a
--- /dev/null
+++ b/step/movecategory/version.php
@@ -0,0 +1,32 @@
+.
+
+/**
+ * Life Cycle Move Category Step
+ *
+ * @package lifecyclestep_movecategory
+ * @copyright 2019 Yorick Reum JMU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->version = 2025050400;
+$plugin->requires = 2022112800; // Requires Moodle 4.1+.
+$plugin->supported = [401, 405];
+$plugin->component = 'lifecyclestep_movecategory';
+$plugin->release = 'v4.5-r1';
+$plugin->maturity = MATURITY_STABLE;
diff --git a/step/pushbackuptask/README.md b/step/pushbackuptask/README.md
new file mode 100644
index 00000000..a3b3c70b
--- /dev/null
+++ b/step/pushbackuptask/README.md
@@ -0,0 +1,5 @@
+# Pushbackuptask (moodle-lifecyclestep_pushbackuptask)
+
+This is a step for [Life Cycle](https://github.com/learnweb/moodle-tool_lifecycle) which does basically the same as the createbackup step but asynchronously by utilizing an adhoc task. The step just pushes one adhoc task for each triggered course. The adhoc task does on execution the same as the createbackup step. That allows the backup processes to run in parallel when cron configured accordingly (see https://docs.moodle.org/401/en/Cron#Scaling_up_cron_with_multiple_processes)
+
+Creation of backups during execution of the adhoc task can be disable by setting `$CFG->custom_no_tool_lifecycle_adhoc_backups = true;` in `config.php`. Useful for staging environments.
diff --git a/step/pushbackuptask/classes/task/course_backup_task.php b/step/pushbackuptask/classes/task/course_backup_task.php
new file mode 100644
index 00000000..93bec1a1
--- /dev/null
+++ b/step/pushbackuptask/classes/task/course_backup_task.php
@@ -0,0 +1,74 @@
+.
+
+/**
+ * Life Cycle Admin push backup task step
+ *
+ * @package lifecyclestep_pushbackuptask
+ * @copyright 2024 Johannes Burk (HTW Berlin)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace lifecyclestep_pushbackuptask\task;
+
+use tool_lifecycle\local\manager\backup_manager;
+
+/**
+ * Task for adhoc backups of courses.
+ */
+class course_backup_task extends \core\task\adhoc_task {
+
+ /**
+ * Run the adhoc task and preform the backup.
+ */
+ public function execute() {
+ global $CFG, $DB;
+
+ if (!empty($CFG->custom_no_tool_lifecycle_adhoc_backups)) {
+ mtrace('Tool lifecycle adhoc backups are disabled via config.php ($CFG->custom_no_tool_lifecycle_adhoc_backups)');
+ return;
+ }
+
+ $lockfactory = \core\lock\lock_config::get_lock_factory('course_backup_adhoc');
+ $courseid = $this->get_custom_data()->courseid;
+
+ try {
+ $course = $DB->get_record('course', ['id' => $courseid], '*', MUST_EXIST);
+ } catch (\moodle_exception $e) {
+ mtrace('Invalid course id: ' . $courseid . ', task aborted.');
+ return;
+ }
+
+ if (!$lock = $lockfactory->get_lock('lifecyclestep_pushbackuptask_' . $courseid, 10)) {
+ mtrace('Backup adhoc task for: ' . $course->fullname . 'is already running.');
+ return;
+ } else {
+ mtrace('Processing backup for course: ' . $course->fullname);
+ }
+
+ try {
+ backup_manager::create_course_backup($courseid);
+ } catch (Exception $e) {
+ mtrace('Backup for course: ' . $course->fullname . ' encounters an error.');
+ mtrace('Exception: ' . $e->getMessage());
+ mtrace('Debug: ' . $e->debuginfo);
+ } finally {
+ // Everything is finished release lock.
+ $lock->release();
+ mtrace('Backup for course: ' . $course->fullname . ' completed.');
+ }
+ }
+}
diff --git a/step/pushbackuptask/lang/de/lifecyclestep_pushbackuptask.php b/step/pushbackuptask/lang/de/lifecyclestep_pushbackuptask.php
new file mode 100644
index 00000000..0a426a57
--- /dev/null
+++ b/step/pushbackuptask/lang/de/lifecyclestep_pushbackuptask.php
@@ -0,0 +1,27 @@
+.
+
+/**
+ * Lang strings for push backup task step
+ *
+ * @package lifecyclestep_pushbackuptask
+ * @copyright 2024 Johannes Burk (HTW Berlin)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['plugindescription'] = 'Ermöglicht augenblickliche Sicherungen von Kursen durch die Verwendung von ad hoc-Tasks.';
+$string['pluginname'] = 'Kurssicherungs-Adhoc-Schritt';
+$string['privacy:metadata'] = 'Dieses Subplugin speichert keine persönlichen Daten..';
diff --git a/step/pushbackuptask/lang/en/lifecyclestep_pushbackuptask.php b/step/pushbackuptask/lang/en/lifecyclestep_pushbackuptask.php
new file mode 100644
index 00000000..b48700ba
--- /dev/null
+++ b/step/pushbackuptask/lang/en/lifecyclestep_pushbackuptask.php
@@ -0,0 +1,28 @@
+.
+
+/**
+ * Lang strings for push backup task step
+ *
+ * @package lifecyclestep_pushbackuptask
+ * @copyright 2024 Johannes Burk (HTW Berlin)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+$string['plugindescription'] = 'Enforces the backup of scheduled courses immediately by using adhoc tasks.';
+$string['pluginname'] = 'Create Adhoc Backup Step';
+$string['privacy:metadata'] = 'This subplugin does not store any personal data.';
+
diff --git a/step/pushbackuptask/lib.php b/step/pushbackuptask/lib.php
new file mode 100644
index 00000000..577f4a03
--- /dev/null
+++ b/step/pushbackuptask/lib.php
@@ -0,0 +1,77 @@
+.
+
+/**
+ * Life Cycle Admin push backup task step
+ *
+ * @package lifecyclestep_pushbackuptask
+ * @copyright 2024 Johannes Burk (HTW Berlin)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+namespace tool_lifecycle\step;
+
+use lifecyclestep_pushbackuptask\task\course_backup_task;
+use stdClass;
+use tool_lifecycle\local\response\step_response;
+
+defined('MOODLE_INTERNAL') || die();
+
+require_once(__DIR__ . '/../lib.php');
+
+/**
+ * Class for processing courses.
+ */
+class pushbackuptask extends libbase {
+ /**
+ * Processes the course and returns a response.
+ * The response tells either
+ * - that the subplugin is finished processing.
+ * - that the subplugin is not yet finished processing.
+ * - that a rollback for this course is necessary.
+ * @param int $processid of the respective process.
+ * @param int $instanceid of the step instance.
+ * @param stdClass $course to be processed.
+ * @return step_response
+ */
+ public function process_course($processid, $instanceid, $course) {
+ $asynctask = new course_backup_task();
+ $asynctask->set_custom_data(['courseid' => $course->id]);
+ \core\task\manager::queue_adhoc_task($asynctask);
+ return step_response::proceed();
+ }
+
+ /**
+ * Simply call the process_course since it handles everything necessary for this plugin.
+ * @param int $processid
+ * @param int $instanceid
+ * @param stdClass $course
+ * @return step_response
+ * @throws \coding_exception
+ * @throws \dml_exception
+ */
+ public function process_waiting_course($processid, $instanceid, $course) {
+ return $this->process_course($processid, $instanceid, $course);
+ }
+
+ /**
+ * The return value should be equivalent with the name of the subplugin folder.
+ * @return string technical name of the subplugin
+ */
+ public function get_subpluginname() {
+ return 'pushbackuptask';
+ }
+}
diff --git a/step/pushbackuptask/version.php b/step/pushbackuptask/version.php
new file mode 100644
index 00000000..b4695f3d
--- /dev/null
+++ b/step/pushbackuptask/version.php
@@ -0,0 +1,33 @@
+.
+
+/**
+ * Life Cycle Admin push backup task step
+ *
+ * @package lifecyclestep_pushbackuptask
+ * @copyright 2025 Thomas Niedermaier University Münster
+ * @copyright 2024 Johannes Burk (HTW Berlin)
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die;
+
+$plugin->version = 2025050400;
+$plugin->requires = 2022112800; // Requires Moodle 4.1+.
+$plugin->supported = [401, 405];
+$plugin->component = 'lifecyclestep_pushbackuptask';
+$plugin->release = 'v4.5-r1';
+$plugin->maturity = MATURITY_STABLE;
diff --git a/styles.css b/styles.css
index 61b7dd2e..303962d8 100644
--- a/styles.css
+++ b/styles.css
@@ -32,7 +32,7 @@ span.tool_lifecycle-hint {
text-align: center;
border: 1px solid #aaa;
border-radius: 8px;
- min-width: 220px;
+ min-width: 260px;
background-color: white;
}
@@ -47,6 +47,7 @@ span.tool_lifecycle-hint {
#lifecycle-workflow-details .workflow-trigger {
max-width: 300px;
+ min-width: 260px;
margin: 8px;
}
@@ -110,12 +111,14 @@ span.tool_lifecycle-hint {
#lifecycle-workflow-details .courses-table {
flex: 45% 1 1;
border: 1px solid #bbb;
- margin-right: 2em;
+ border-radius: 8px;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
}
#lifecycle-workflow-details .bar {
- border: 1px solid #dee2e6;
- border-right: none;
+ border: 1px none #dee2e6;
+ border-bottom-style: solid;
}
#lifecycle-workflow-details .close-button {
@@ -149,3 +152,7 @@ span.tool_lifecycle-hint {
#lifecycle-workflow-details .dropdown-toggle::after {
content: none;
}
+
+#lifecycle-workflow-details .wf-wrapper a:hover {
+ text-decoration: none;
+}
diff --git a/subplugins.php b/subplugins.php
new file mode 100644
index 00000000..30f293aa
--- /dev/null
+++ b/subplugins.php
@@ -0,0 +1,94 @@
+.
+
+/**
+ * Displays the installed subplugins (steps and trigger).
+ *
+ * @package tool_lifecycle
+ * @copyright 2025 Thomas Niedermaier University Münster
+ * @copyright 2022 Justus Dieckmann WWU
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+use tool_lifecycle\tabs;
+use tool_lifecycle\urls;
+
+require_once(__DIR__ . '/../../../config.php');
+require_once($CFG->libdir . '/adminlib.php');
+
+require_admin();
+
+$syscontext = context_system::instance();
+$PAGE->set_url(new \moodle_url(urls::SUBPLUGINS));
+$PAGE->set_context($syscontext);
+
+$PAGE->set_pagetype('admin-setting-' . 'tool_lifecycle');
+$PAGE->set_pagelayout('admin');
+
+$renderer = $PAGE->get_renderer('tool_lifecycle');
+
+$heading = get_string('pluginname', 'tool_lifecycle')." / ".get_string('subplugins', 'tool_lifecycle');
+echo $renderer->header($heading);
+$tabrow = tabs::get_tabrow();
+$id = optional_param('id', 'subplugins', PARAM_TEXT);
+$renderer->tabs($tabrow, $id);
+
+echo html_writer::link('https://github.com/learnweb/moodle-tool_lifecycle/wiki/List-of-Installed-Subplugins',
+ get_string('documentationlink', 'tool_lifecycle'), ['target' => '_blank']);
+
+$triggers = core_component::get_plugin_list('lifecycletrigger');
+if ($triggers) {
+ echo html_writer::div(get_string('triggers_installed', 'tool_lifecycle'), 'h2 mt-2');
+ foreach ($triggers as $trigger => $path) {
+ echo html_writer::div(get_string('pluginname', 'lifecycletrigger_' . $trigger),
+ "font-weight-bold");
+ try {
+ $plugindescription = get_string('plugindescription', 'lifecycletrigger_' . $trigger);
+ } catch (Exception $e) {
+ $plugindescription = "";
+ }
+ if ($plugindescription) {
+ echo html_writer::start_div().$plugindescription;
+ if ($trigger == 'sitecourse' || $trigger == 'delayedcourses') {
+ echo html_writer::span(' Depracated. Will be removed with version 5.0.', 'text-danger');
+ }
+ echo html_writer::end_div();
+ }
+ }
+} else {
+ echo html_writer::div(get_string('adminsettings_notriggers', 'tool_lifecycle'));
+}
+
+$steps = core_component::get_plugin_list('lifecyclestep');
+if ($steps) {
+ echo html_writer::div(get_string('steps_installed', 'tool_lifecycle'), 'h2 mt-2');
+ foreach ($steps as $step => $path) {
+ echo html_writer::div(get_string('pluginname', 'lifecyclestep_' . $step),
+ "font-weight-bold");
+ try {
+ $plugindescription = get_string('plugindescription', 'lifecyclestep_' . $step);
+ } catch (Exception $e) {
+ $plugindescription = "";
+ }
+ if ($plugindescription) {
+ echo html_writer::div($plugindescription);
+ }
+ }
+} else {
+ echo html_writer::div(get_string('adminsettings_nosteps', 'tool_lifecycle'));
+}
+
+echo $renderer->footer();
diff --git a/templates/overview_processeslink.mustache b/templates/overview_processeslink.mustache
new file mode 100644
index 00000000..aa698a2a
--- /dev/null
+++ b/templates/overview_processeslink.mustache
@@ -0,0 +1,33 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see
.
+}}
+{{!
+ @template tool_lifecycle/overview_processeslink
+
+ Add trigger and add step select fields
+
+ Example context (json):
+ {
+ "url": "admin/tool/lifecycle/workflowoverview.php?wf=1&processes=4&showdetails=1",
+ "alt": "Active workflow processes and process errors",
+ "processes": "4"
+ }
+}}
+
+
+ {{processes}}
+
+
diff --git a/templates/overview_step.mustache b/templates/overview_step.mustache
index d2faf758..12fae71b 100644
--- a/templates/overview_step.mustache
+++ b/templates/overview_step.mustache
@@ -39,7 +39,13 @@