Skip to content
29 changes: 29 additions & 0 deletions step/uclcontextfreeze/lang/en/lifecyclestep_uclcontextfreeze.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
// 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 <http://www.gnu.org/licenses/>.

/**
* Lang strings for delete course step
*
* @package lifecyclestep_uclcontextfreeze
* @copyright 2025 UCL
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

$string['stepname'] = 'Archive/Freeze course step';
$string['plugindescription'] = 'Calls UCLs contextfreeze (block) to archive courses';
$string['pluginname'] = 'UCL contextfreeze';
$string['privacy:metadata'] = 'This subplugin does not store any personal data.';

83 changes: 83 additions & 0 deletions step/uclcontextfreeze/lib.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<?php
// 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 <http://www.gnu.org/licenses/>.

/**
* Step subplugin to freeze a course context using UCL block_lifecycle manager.
*
* @package lifecyclestep_uclcontextfreeze
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

namespace tool_lifecycle\step;

use stdClass;
use tool_lifecycle\local\response\step_response;

defined('MOODLE_INTERNAL') || die();

require_once(__DIR__ . '/../lib.php');

/**
* Step subplugin to freeze a course context using UCL block_lifecycle.
*
* @package lifecyclestep_uclcontextfreeze
*/
class uclcontextfreeze extends libbase {

/**
* Processes the course and returns a response.
*
* @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) {

if (!class_exists('\block_lifecycle\manager')) {
return step_response::rollback();
}

try {
\block_lifecycle\manager::freeze_course((int)$course->id);
} catch (\Exception $e) {
return step_response::rollback();
}

return step_response::proceed();
}

/**
* Processes the course in status waiting and returns a response.
*
* @param int $processid
* @param int $instanceid
* @param stdClass $course
* @return step_response
*/
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
*/
public function get_subpluginname() {
return 'uclcontextfreeze';
}
}
14 changes: 14 additions & 0 deletions step/uclcontextfreeze/version.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php
defined('MOODLE_INTERNAL') || die();

$plugin->component = 'lifecyclestep_uclcontextfreeze';
$plugin->version = 2025102300;
$plugin->requires = 2022112800; // Requires Moodle 4.1+.
$plugin->maturity = MATURITY_STABLE;
$plugin->release = '0.1';

// Requires UCL's lifecycle bloxk (so the manager class exists)
$plugin->dependencies = [
'block_lifecycle' => ANY_VERSION,
];

39 changes: 39 additions & 0 deletions trigger/coursedelete/classes/privacy/provider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php
// 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 <http://www.gnu.org/licenses/>.

namespace lifecycletrigger_coursedelete\privacy;

use core_privacy\local\metadata\null_provider;

/**
* Privacy subsystem implementation for lifecycletrigger_coursefreeze.
*
* @package lifecycletrigger_coursefreeze
* @copyright 2025 Gifty Wanzola (ccaewan)
* @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';
}
}
45 changes: 45 additions & 0 deletions trigger/coursedelete/lang/en/lifecycletrigger_coursedelete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?php
// 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 <http://www.gnu.org/licenses/>.

/**
* Lang strings for course freeze trigger
*
* @package lifecycletrigger_coursedelete
* @copyright 2025 Gifty (ccaewan)
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

$string['pluginname'] = 'Select long-term archived courses for deletion';
// Description shown on the workflow configuration page.

$string['plugindescription'] =
'Selects courses that have been archived (frozen), have not been accessed for a prolonged period, '
. 'and were created sufficiently long ago. These courses are considered end-of-life and may be '
. 'safely removed using a delete step in the workflow.';

$string['inactivitydelay'] = 'Last access threshold';
$string['inactivitydelay_help'] =
'Only delete courses where the most recent user activity is older than this period. '
. 'Set to 48 months by default to target courses with no access for at least 4 years.';

$string['creationdelay'] = 'Minimum course age';

$string['creationdelay_help'] =
'The minimum age of a course based on its creation date. '
. 'Courses created more recently than this threshold will not be selected for deletion.'
. 'Set to 60 months by default to target courses with older than at least 5 years.';

$string['privacy:metadata'] = 'The Course deletion trigger does not store or process personal data.';
154 changes: 154 additions & 0 deletions trigger/coursedelete/lib.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<?php
// 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 <http://www.gnu.org/licenses/>.

/**
* Trigger subplugin to delete frozen courses based on long inactivity + age.
*
* @package lifecycletrigger_coursedelete
* @copyright 2025
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
namespace tool_lifecycle\trigger;

use tool_lifecycle\local\manager\settings_manager;
use tool_lifecycle\local\response\trigger_response;
use tool_lifecycle\settings_type;
use tool_lifecycle\trigger\instance_setting;

defined('MOODLE_INTERNAL') || die();
require_once(__DIR__ . '/../lib.php');

/**
* Class which implements a trigger for deleting frozen courses.
*
* Logic (workaround while no "frozen since" timestamp exists):
* - Course context is locked (frozen): {context}.locked = 1 for course contextlevel 50
* - Last access (enrolled users) older than inactivitydelay (default 48 months)
* - Course creation older than creationdelay (default 60 months)
*/
class coursedelete extends base_automatic {

public function check_course($course, $triggerid) {
return trigger_response::trigger();
}

/**
* Instance settings for this trigger.
*
* @return instance_setting[]
*/
public function instance_settings() {
return [
// Last access must be older than this (default 48 months).
new instance_setting('inactivitydelay', PARAM_INT),

// Course creation must be older than this (default 60 months).
new instance_setting('creationdelay', PARAM_INT),
];
}

/**
* Returns the WHERE clause and params selecting courses to be deleted.
*
* @param int $triggerid
* @return array [string $where, array $params]
* @throws \coding_exception
* @throws \dml_exception
*/
public function get_course_recordset_where($triggerid) {
// Load instance settings.
$settings = settings_manager::get_settings($triggerid, settings_type::TRIGGER);

// Defaults (approx months as 365-day years for now):
// - 48 months β‰ˆ 4 years
// - 60 months β‰ˆ 5 years
$inactivitydelay = isset($settings['inactivitydelay']) ? (int)$settings['inactivitydelay'] : (4 * 365 * DAYSECS);
$creationdelay = isset($settings['creationdelay']) ? (int)$settings['creationdelay'] : (5 * 365 * DAYSECS);

$now = time();
$lastaccessthreshold = $now - $inactivitydelay;
$creationthreshold = $now - $creationdelay;

// Frozen courses: course context locked
// Inactive courses: most recent recorded course access is older than threshold
// Old courses: timecreated older than threshold
$where = "c.timecreated < :creationthreshold
AND EXISTS (
SELECT 1
FROM {context} ctx
WHERE ctx.contextlevel = 50
AND ctx.instanceid = c.id
AND ctx.locked = 1
)
AND c.id IN (
SELECT la.courseid
FROM {user_lastaccess} la
GROUP BY la.courseid
HAVING MAX(la.timeaccess) < :lastaccessthreshold
)";


$params = [
'creationthreshold' => $creationthreshold,
'lastaccessthreshold' => $lastaccessthreshold,
];

return [$where, $params];
}

/**
* Add instance settings elements to the add-instance form.
*
* @param \MoodleQuickForm $mform
* @return void
* @throws \coding_exception
*/
public function extend_add_instance_form_definition($mform) {

$elementname = 'inactivitydelay';
$mform->addElement('duration', $elementname, get_string($elementname, 'lifecycletrigger_coursedelete'));
$mform->addHelpButton($elementname, $elementname, 'lifecycletrigger_coursedelete');
$mform->setDefault($elementname, 4 * 365 * DAYSECS); // ~48 months.

$elementname = 'creationdelay';
$mform->addElement('duration', $elementname, get_string($elementname, 'lifecycletrigger_coursedelete'));
$mform->addHelpButton($elementname, $elementname, 'lifecycletrigger_coursedelete');
$mform->setDefault($elementname, 5 * 365 * DAYSECS); // ~60 months.
}

/**
* After data is loaded, set defaults from existing settings if present.
*
* @param \MoodleQuickForm $mform
* @param array $settings
* @return void
*/
public function extend_add_instance_form_definition_after_data($mform, $settings) {
if (!is_array($settings)) {
return;
}
if (array_key_exists('inactivitydelay', $settings)) {
$mform->setDefault('inactivitydelay', $settings['inactivitydelay']);
}
if (array_key_exists('creationdelay', $settings)) {
$mform->setDefault('creationdelay', $settings['creationdelay']);
}
}

public function get_subpluginname() {
return 'coursedelete';
}
}
Loading