From b76ff0b599297f24837f55c013b2ca08f339f2b4 Mon Sep 17 00:00:00 2001 From: Thomas Niedermaier Date: Mon, 1 Jun 2026 02:57:39 +0200 Subject: [PATCH 1/6] changes, version.php --- CHANGES.md | 4 ++++ version.php | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 8d960eab..aafae320 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ CHANGELOG ========= +5.2.0 (2026-06-01) +------------------ +* Moodle 5.2 compatible version + 5.1.5 (v5.1-r6, 2026-05-20) --------------------------- * [FEATURE] Introduce new step opencast diff --git a/version.php b/version.php index f577a563..c9b2f76c 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ $plugin->component = 'tool_lifecycle'; $plugin->maturity = MATURITY_STABLE; -$plugin->version = 2026012005; +$plugin->version = 2026060100; $plugin->requires = 2024100700; // Requires Moodle 4.5+. -$plugin->supported = [405, 501]; -$plugin->release = 'v5.1-r6'; +$plugin->supported = [405, 502]; +$plugin->release = 'v5.2-r1'; From fecc69eea3d5d0fc7b05f46941af857703700b2b Mon Sep 17 00:00:00 2001 From: Michael Schink Date: Tue, 23 Jun 2026 15:08:58 +0200 Subject: [PATCH 2/6] PRLMDL-858 Remove trigger opencast from upstream/original repo & replace it with our custom trigger opencast --- .../classes/local/courseFilterOpencast.php | 107 +++++++++ trigger/opencast/classes/privacy/provider.php | 23 +- trigger/opencast/index.php | 18 ++ .../lang/de/lifecycletrigger_opencast.php | 28 ++- .../lang/en/lifecycletrigger_opencast.php | 28 ++- trigger/opencast/lib.php | 203 +++++++----------- trigger/opencast/pix/icon.png | Bin 0 -> 214 bytes trigger/opencast/pix/monologo.png | Bin 0 -> 214 bytes trigger/opencast/version.php | 24 +-- 9 files changed, 248 insertions(+), 183 deletions(-) create mode 100755 trigger/opencast/classes/local/courseFilterOpencast.php mode change 100644 => 100755 trigger/opencast/classes/privacy/provider.php create mode 100755 trigger/opencast/index.php mode change 100644 => 100755 trigger/opencast/lang/de/lifecycletrigger_opencast.php mode change 100644 => 100755 trigger/opencast/lang/en/lifecycletrigger_opencast.php mode change 100644 => 100755 trigger/opencast/lib.php create mode 100755 trigger/opencast/pix/icon.png create mode 100755 trigger/opencast/pix/monologo.png mode change 100644 => 100755 trigger/opencast/version.php diff --git a/trigger/opencast/classes/local/courseFilterOpencast.php b/trigger/opencast/classes/local/courseFilterOpencast.php new file mode 100755 index 00000000..ba3b6905 --- /dev/null +++ b/trigger/opencast/classes/local/courseFilterOpencast.php @@ -0,0 +1,107 @@ +. + +/** + * Trigger subplugin, which triggers on specific jku needs only. + * + * @package lifecycletrigger_opencast + * @copyright 2024 Michael Schink JKU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace lifecycletrigger_opencast\local; + +defined('MOODLE_INTERNAL') || die(); + +class courseFilterOpencast { + // Test code + public function test() { + // Start Test + echo("
Test class: courseFilter (of lifecycle trigger: opencast)
"); + + // Get all courses & reduce them to an array with course ids + $course_ids = array_map(function($course) { return $course->id; }, get_courses()); + $tmp = count($course_ids); + echo("

All Courses: " . $tmp . ""); + + // Get courses with opencast block + $courses_with_opencast = $this->get_opencast_courses(); + echo("

Courses with opencast block: " . count($courses_with_opencast)); + foreach ($courses_with_opencast as $courseid) { + echo("
  id: " . $courseid); + } + + // Get courses without opencast block + $courses_without_opencast = $this->get_opencast_courses(true); + // Intersect course ids + $tmp = count($course_ids); + $course_ids = array_intersect($course_ids, $courses_without_opencast); + echo("

Courses without opencast block: " . count($courses_without_opencast)." (".count($course_ids)." of ".$tmp." courses filtered)"); + foreach ($courses_without_opencast as $courseid) { + if(in_array($courseid, $course_ids)) { echo("
  id: " . $courseid . ""); } + else { echo("
  id: " . $courseid); } + } + + // Unique & sorted course ids + //$course_ids = array_unique(array_intersect($course_ids, $no_meta_courses, $courses_with_roles, $courses_without_opencast, $kusss_courses_M, $visble_courses)); + $course_ids = array_unique($course_ids); + sort($course_ids); + echo(" +

+ Opencast Block Filter:
+ · Courses without opencast block
+ Intersect unique course ids: ".count($course_ids)." + "); + foreach ($course_ids as $courseid) { + echo("
  id: " . $courseid .""); + } + } + + // ###################################### Courses without OPENCAST ###################################### + + // Get courses with (or without) opencast block + public function get_opencast_courses($without = false) { + global $DB; + + // Get all courses + $courses = get_courses(); + + // Filter course + $courses = array_filter($courses, function($course) use($DB, $without) { + // Get context of course (by id) + $context = \context_course::instance($course->id, IGNORE_MISSING); + + // Get block instances of opencast + $block_instances = $DB->get_records('block_instances', ['parentcontextid' => $context->id, 'blockname' => 'opencast']); + if(count($block_instances)) { + if(count($block_instances) > 1) { mtrace('Error: Multiple opencast block instances found in course: '.$course->fullname.' (id: '.$course->id.', shortname: '.$course->shortname.').'); } + // Opencast block found in course + if (!$without) { return true; } + else { return false; } + } else { + // No opencast block in course + if (!$without) { return false; } + else { return true; } + } + }); + + // Reduce courses to an array with course ids (& sort it) + $courses = array_map(function($course) { return $course->id; }, $courses); + sort($courses); + + return $courses; + } +} diff --git a/trigger/opencast/classes/privacy/provider.php b/trigger/opencast/classes/privacy/provider.php old mode 100644 new mode 100755 index 7fe0bf1c..d363b453 --- a/trigger/opencast/classes/privacy/provider.php +++ b/trigger/opencast/classes/privacy/provider.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . namespace lifecycletrigger_opencast\privacy; use core_privacy\local\metadata\null_provider; +//use core_privacy\local\metadata\collection; -/** - * Privacy subsystem implementation for lifecycletrigger_opencast. - * - * @package lifecycletrigger_opencast - * @copyright 2025 Thomas Niedermaier University Münster - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ + /** + * Privacy subsystem implementation for lifecycletrigger_opencast. + * + * @package lifecycletrigger_opencast + * @copyright 2024 Michael Schink JKU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ class provider implements null_provider { + // This plugin does not store any personal user data. + //\core_privacy\local\metadata\null_provider { /** * Get the language string identifier with the component's language * file to explain why this plugin stores no data. * - * @return string the reason + * @return string */ public static function get_reason(): string { return 'privacy:metadata'; diff --git a/trigger/opencast/index.php b/trigger/opencast/index.php new file mode 100755 index 00000000..9f3aaaa1 --- /dev/null +++ b/trigger/opencast/index.php @@ -0,0 +1,18 @@ +test(); diff --git a/trigger/opencast/lang/de/lifecycletrigger_opencast.php b/trigger/opencast/lang/de/lifecycletrigger_opencast.php old mode 100644 new mode 100755 index 41a6ab72..9a61319c --- a/trigger/opencast/lang/de/lifecycletrigger_opencast.php +++ b/trigger/opencast/lang/de/lifecycletrigger_opencast.php @@ -15,23 +15,21 @@ // along with Moodle. If not, see . /** - * Lang strings for opencast trigger + * Plugin strings for lifecycletrigger_opencast. * * @package lifecycletrigger_opencast - * @copyright 2025 Thomas Niedermaier University Münster - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @category string + * @copyright 2024 Michael Schink + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['activity'] = 'Aktivität'; -$string['activity_help'] = 'Hier geben Sie an, ob Kurse getriggert werden sollen, die mindestens eine Episode oder Serie enthalten, die über die Opencast-Aktivität eingebunden ist.'; -$string['exclude'] = 'Ausschließen'; -$string['exclude_help'] = 'Falls ausgewählt, werden Kurse mit den definierten Opencast-Videos NICHT ausgelöst.'; -$string['lti'] = 'LTI'; -$string['lti_do_not_exist'] = 'Es gibt keine LTI-Tools mit den folgenden IDs: {$a}.'; -$string['lti_help'] = 'Inkludiere Kurse, die mindestens eine Opencast Episode und/oder Serie via LTI-Tool eingebunden haben.'; -$string['lti_noselection'] = 'Bitte wählen Sie mindestens einen LTI-Typ aus.'; -$string['ltitools'] = 'LTI-Tools'; -$string['ltitools_help'] = 'Wählen Sie die LTI-Tools, welche die Episoden und/oder Serien aus Opencast zur Verfügung stellen.'; -$string['plugindescription'] = 'Selektiert alle Kurse mit mindestens einer Opencast Video-Einbindung.'; -$string['pluginname'] = 'Opencast-Trigger'; +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'Opencast Block Trigger'; +$string['plugindescription'] = 'Opencast Block Trigger'; $string['privacy:metadata'] = 'Dieses Subplugin speichert keine persönlichen Daten.'; + +// For filtering opencast courses +$string['opencast_courses'] = 'Kursfilter per "Opencast Videos" Block'; +$string['no_opencast'] = 'Nur Kurse ohne "Opencast Videos" Block'; +$string['only_opencast'] = 'Nur Kurse mit "Opencast Videos" Block'; diff --git a/trigger/opencast/lang/en/lifecycletrigger_opencast.php b/trigger/opencast/lang/en/lifecycletrigger_opencast.php old mode 100644 new mode 100755 index b45c8452..19bda46f --- a/trigger/opencast/lang/en/lifecycletrigger_opencast.php +++ b/trigger/opencast/lang/en/lifecycletrigger_opencast.php @@ -15,23 +15,21 @@ // along with Moodle. If not, see . /** - * Lang strings for opencast trigger + * Plugin strings for lifecycletrigger_opencast. * * @package lifecycletrigger_opencast - * @copyright 2025 Thomas Niedermaier University Münster - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @category string + * @copyright 2024 Michael Schink + * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -$string['activity'] = 'Activity'; -$string['activity_help'] = 'Set this option to trigger courses with at least one Opencast episode or series integrated by an Opencast activity.'; -$string['exclude'] = 'Exclude'; -$string['exclude_help'] = 'If ticked, the triggered courses are excluded from selection instead.'; -$string['lti'] = 'LTI'; -$string['lti_do_not_exist'] = 'There are no LTI Tool with the following ids: {$a}.'; -$string['lti_help'] = 'Include courses with at least one Opencast episode and/or series provided by a LTI Tool.'; -$string['lti_noselection'] = 'Please choose at least one LTI Tool.'; -$string['ltitools'] = 'LTI Tools'; -$string['ltitools_help'] = 'Select the LTI Tools which connect to Opencast to provide episodes and/or series.'; -$string['plugindescription'] = 'Selects all courses with at least one Opencast video integration.'; -$string['pluginname'] = 'Opencast Trigger'; +defined('MOODLE_INTERNAL') || die(); + +$string['pluginname'] = 'Opencast Block Trigger'; +$string['plugindescription'] = 'Opencast Block Trigger'; $string['privacy:metadata'] = 'This subplugin does not store any personal data.'; + +// For filtering opencast courses +$string['opencast_courses'] = 'Course filter by "Opencast Videos" block'; +$string['no_opencast'] = 'Only courses without "Opencast Videos" block'; +$string['only_opencast'] = 'Only courses with "Opencast Videos" block'; diff --git a/trigger/opencast/lib.php b/trigger/opencast/lib.php old mode 100644 new mode 100755 index d08fb154..1187d1cc --- a/trigger/opencast/lib.php +++ b/trigger/opencast/lib.php @@ -15,102 +15,87 @@ // along with Moodle. If not, see . /** - * Trigger subplugin to include or exclude courses with certain opencast videos. + * Trigger subplugin, which triggers on specific jku needs only. * - * @package lifecycletrigger_opencast - * @copyright 2025 Thomas Niedermaier University Münster - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package lifecycletrigger_opencast + * @copyright 2024 Michael Schink JKU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace tool_lifecycle\trigger; +//use core_reportbuilder\local\aggregation\count; use tool_lifecycle\local\manager\settings_manager; +//use tool_lifecycle\local\manager\trigger_manager; use tool_lifecycle\local\response\trigger_response; use tool_lifecycle\settings_type; +// Import trigger's lib +use lifecycletrigger_opencast\local\courseFilterOpencast; defined('MOODLE_INTERNAL') || die(); require_once(__DIR__ . '/../lib.php'); require_once(__DIR__ . '/../../lib.php'); -require_once(__DIR__ . '/../../../../../mod/lti/locallib.php'); /** * Class which implements the basic methods necessary for a cleanyp courses trigger subplugin - * @package lifecycletrigger_opencast - * @copyright 2025 Thomas Niedermaier University Münster - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package lifecycletrigger_opencast + * @copyright 2024 Michael Schink JKU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ class opencast extends base_automatic { - /** - * If check_course_code() returns true, code to check the given course is placed here - * @param object $course - * @param int $triggerid + * Checks the course and returns a repsonse, which tells if the course should be further processed. + * @param object $course Course to be processed. + * @param int $triggerid Id of the trigger instance. * @return trigger_response */ public function check_course($course, $triggerid) { + // Everything is already in the sql statement. return trigger_response::trigger(); } /** - * Returns whether the lib function check_course contains particular selection code per course or not. - * @return bool - */ - public function check_course_code() { - return false; - } - - /** - * Return sql snippet for including (or excluding) the courses with defined opencast videos. + * Returns true or false, depending on if the current date is one of the specified days, + * at which the trigger should run. * @param int $triggerid Id of the trigger. * @return array A list containing the constructed sql fragment and an array of parameters. * @throws \coding_exception * @throws \dml_exception + * @throws \moodle_exception + * @throws \Exception */ public function get_course_recordset_where($triggerid) { global $DB; - $sql = " TRUE "; - $inparams = []; + // Create course filter + $courseFilter = new courseFilterOpencast(); - $exclude = false; - $activity = false; - $lti = false; + // Get all courses & reduce them to an array with course ids + $course_ids = array_map(function($course) { return $course->id; }, get_courses()); + // Get settings $settings = settings_manager::get_settings($triggerid, settings_type::TRIGGER); - if (isset($settings['exclude']) && $settings['exclude'] != false) { - $exclude = true; - } - if (isset($settings['activity']) && $settings['activity'] != false) { - $activity = true; + //mtrace(" Mike - Trigger opencast: Settings: ".print_r($settings, true)); + //echo("
SETTINGS:
".print_r($settings, true)."
"); + // throw new \moodle_exception("Error text: " . $var); + + // Filter courses with opencast block + $course_ids = $this->opencast_filter($course_ids, $settings, $courseFilter); + + // Create "where query" + //$course_ids = array_merge($course_ids, [2, 5, 9, 12, 25]); + if(count($course_ids) > 0) { + $course_ids = array_unique($course_ids); + list($insql, $inparams) = $DB->get_in_or_equal($course_ids, SQL_PARAMS_NAMED, 'courseid'); + $where = "{course}.id {$insql}"; + mtrace(" Course ids " . count($course_ids)); // . ": " . print_r($course_ids, true) . " -> where: " . $where . " + inparams: " . print_r($inparams, true)); + + // Return "where query" + return [$where, $inparams]; } - if (isset($settings['lti']) && $settings['lti'] != false) { - $lti = true; - } - - $not = $exclude ? 'NOT' : ''; - if ($activity) { - $sql = " c.id $not IN (SELECT DISTINCT(course) FROM {opencast}) "; - } - if ($lti) { - $ltitools = settings_manager::get_settings($triggerid, settings_type::TRIGGER)['ltitools']; - $ltitoolsarr = explode(",", $ltitools); - [$insql, $inparams] = $DB->get_in_or_equal($ltitoolsarr, SQL_PARAMS_NAMED); - if ($sql) { - if ($exclude) { - $sql = "($sql AND c.id $not IN (SELECT DISTINCT(l.course) FROM {lti} l where - l.typeid $insql))"; - } else { - $sql = "($sql OR c.id IN (SELECT DISTINCT(l.course) FROM {lti} l where - l.typeid $insql))"; - } - } else { - $sql = "c.id $not IN (SELECT DISTINCT(l.course) FROM {lti} l where - l.typeid $insql)"; - } - } - - $where = $sql; - return [$where, $inparams]; + // Return "true or false" + //return ['true', []]; + return ['false', []]; } /** @@ -127,93 +112,49 @@ public function get_subpluginname() { */ public function instance_settings() { return [ - new instance_setting('activity', PARAM_BOOL), - new instance_setting('lti', PARAM_BOOL), - new instance_setting('ltitools', PARAM_SEQUENCE), - new instance_setting('exclude', PARAM_BOOL), + // Add instance for courses with opencast block + new instance_setting('opencast_courses', PARAM_TEXT), ]; } /** - * This method can be overriden, to add form elements to the form_trigger_instance. + * This method can be overwritten, to add form elements to the form_step_instance. * It is called in definition(). * @param \MoodleQuickForm $mform * @throws \coding_exception - * @throws \dml_exception */ public function extend_add_instance_form_definition($mform) { - - $mform->addElement('advcheckbox', 'activity', - get_string('activity', 'lifecycletrigger_opencast')); - $mform->setType('activity', PARAM_BOOL); - $mform->addHelpButton('activity', 'activity', 'lifecycletrigger_opencast'); - - $ltitypes = lti_filter_get_types(false); - $ltis = []; - foreach ($ltitypes as $key => $type) { - $ltis[$key] = $type->name." (".$type->baseurl.")"; - } - if ($ltis) { - $mform->addElement('advcheckbox', 'lti', - get_string('lti', 'lifecycletrigger_opencast')); - $mform->setType('lti', PARAM_BOOL); - $mform->addHelpButton('lti', 'lti', 'lifecycletrigger_opencast'); - $options = [ - 'multiple' => true, - 'noselectionstring' => get_string('lti_noselection', 'lifecycletrigger_opencast'), - ]; - $mform->addElement('autocomplete', 'ltitools', "", $ltis, $options); - $mform->setType('ltitools', PARAM_SEQUENCE); - - // Hide lti tools unless lti checkbox is checked. - $mform->hideIf('ltitools', 'lti', 'notchecked'); - } - - $mform->addElement('advcheckbox', 'exclude', get_string('exclude', 'lifecycletrigger_opencast')); - $mform->addHelpButton('exclude', 'exclude', 'lifecycletrigger_opencast'); + // Add select for courses with opencast block + $options = [ + 'no_opencast' => get_string('no_opencast', 'lifecycletrigger_opencast'), + 'only_opencast' => get_string('only_opencast', 'lifecycletrigger_opencast'), + ]; + $mform->addElement('select', 'opencast_courses', get_string('opencast_courses', 'lifecycletrigger_opencast'), $options); + $mform->setDefault('opencast_courses', 'no_opencast'); } /** - * Since the rendering of frozen autocomplete elements is awful, we override it here. - * @param \MoodleQuickForm $mform - * @param array $settings array containing the settings from the db. + * Validate parsable dates. + * @param array $error Array containing all errors. + * @param array $data Data passed from the moodle form to be validated. * @throws \coding_exception */ - public function extend_add_instance_form_definition_after_data($mform, $settings) { - $type = $mform->getElementType('instancename'); - if (($type ?? "") != "text") { - if (is_array($settings) && array_key_exists('ltitools', $settings)) { - $triggerltitools = explode(",", $settings['ltitools']); - } else { - $triggerltitools = []; - } - $types = lti_filter_get_types(get_site()->id); - $configuredtools = lti_filter_tool_types($types, LTI_TOOL_STATE_CONFIGURED); - $ltitoolshtml = ""; - foreach ($configuredtools as $key => $tool) { - if (in_array($key, $triggerltitools)) { - $ltitoolshtml .= \html_writer::div($tool->name." (".$tool->baseurl.")", "badge badge-secondary mr-1"); - } - } - $mform->insertElementBefore($mform->createElement( - 'static', - 'ltitoolsstatic', - get_string('ltitools', 'lifecycletrigger_opencast'), - $ltitoolshtml), 'buttonar'); - $mform->insertElementBefore($mform->createElement( - 'advcheckbox', - 'exclude', - get_string('exclude', 'lifecycletrigger_opencast')), - 'buttonar'); - $mform->setType('exclude', PARAM_BOOL); - } + public function extend_add_instance_form_validation(&$error, $data) { + } - /** - * Returns the string of the specific icon for this trigger. - * @return string icon string - */ - public function get_icon() { - return 'e/insert_edit_video'; + // Filter courses with opencast block + public function opencast_filter($course_ids, $settings, $courseFilter) { + mtrace(" Mike - Trigger opencast: Filter opencast courses: ".$settings["opencast_courses"]); + if($settings["opencast_courses"] == "no_opencast") { $courses = $courseFilter->get_opencast_courses(true); } + else { $courses = $courseFilter->get_opencast_courses(false); } + mtrace(" Mike - Trigger opencast: Opencast courses result: ".count($courses)); + // Intersect course ids + $course_ids = array_intersect($course_ids, $courses); + mtrace(" Mike - Trigger opencast: Opencast courses intersected"); + // Sort course ids + sort($course_ids); + + return $course_ids; } } diff --git a/trigger/opencast/pix/icon.png b/trigger/opencast/pix/icon.png new file mode 100755 index 0000000000000000000000000000000000000000..3cc72552fe8b7ac2fcebd3d0c9da994c465a8eab GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^G9b(WBpAZe8a#m1Tu&FrkP5~v7dHwvDDbdexWBu@ z*`UMr@rB!w0T->fgd;XL@b=DARI;3~`D6I8z-ontrq+%g z2Mt{Yrib#MH7-0lRdw5IY3d!(scJJ9I=%flZ&qE#N6!eo{=gijiy1Pm)g8*qepGEs z^e}0;_8{0^ZkmhT%DQ@^v@Q M)78&qol`;+03~lw=Kufz literal 0 HcmV?d00001 diff --git a/trigger/opencast/pix/monologo.png b/trigger/opencast/pix/monologo.png new file mode 100755 index 0000000000000000000000000000000000000000..3cc72552fe8b7ac2fcebd3d0c9da994c465a8eab GIT binary patch literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^G9b(WBpAZe8a#m1Tu&FrkP5~v7dHwvDDbdexWBu@ z*`UMr@rB!w0T->fgd;XL@b=DARI;3~`D6I8z-ontrq+%g z2Mt{Yrib#MH7-0lRdw5IY3d!(scJJ9I=%flZ&qE#N6!eo{=gijiy1Pm)g8*qepGEs z^e}0;_8{0^ZkmhT%DQ@^v@Q M)78&qol`;+03~lw=Kufz literal 0 HcmV?d00001 diff --git a/trigger/opencast/version.php b/trigger/opencast/version.php old mode 100644 new mode 100755 index 5828bd04..98bd77ff --- a/trigger/opencast/version.php +++ b/trigger/opencast/version.php @@ -1,5 +1,5 @@ . +// along with Moodle. If not, see . /** - * Life Cycle Opencast Trigger + * Life Cycle Trigger lifecycletrigger_opencast. * - * @package lifecycletrigger_opencast - * @copyright 2025 Thomas Niedermaier University Münster - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @package lifecycletrigger_opencast + * @copyright 2024 Michael Schink JKU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -defined('MOODLE_INTERNAL') || die; +defined('MOODLE_INTERNAL') || die(); +$plugin->version = 2025071000; $plugin->component = 'lifecycletrigger_opencast'; -$plugin->maturity = MATURITY_STABLE; -$plugin->version = 2026012004; -$plugin->requires = 2024100700; // Requires Moodle 4.5+. -$plugin->supported = [405, 501]; -$plugin->release = 'v5.1-r5'; +$plugin->dependencies = array('tool_lifecycle' => 2022112400); +$plugin->requires = 2020061500; // Requires Moodle 3.9+. +$plugin->release = '0.1.0'; +$plugin->maturity = MATURITY_ALPHA; //MATURITY_STABLE From 9f42cd195a443517c0492fa67596fe6b442ae846 Mon Sep 17 00:00:00 2001 From: Michael Schink Date: Tue, 23 Jun 2026 16:13:08 +0200 Subject: [PATCH 3/6] PRLMDL-858 * Remove our custom trigger opencast & install it with ops as usual to prevent future failures * Remove our custom step deletebackup from ops, because upstream/original repo has adopted it with minor changes --- .../classes/local/courseFilterOpencast.php | 107 ------------ trigger/opencast/classes/privacy/provider.php | 42 ----- trigger/opencast/index.php | 18 -- .../lang/de/lifecycletrigger_opencast.php | 35 ---- .../lang/en/lifecycletrigger_opencast.php | 35 ---- trigger/opencast/lib.php | 160 ------------------ trigger/opencast/pix/icon.png | Bin 214 -> 0 bytes trigger/opencast/pix/monologo.png | Bin 214 -> 0 bytes trigger/opencast/version.php | 32 ---- 9 files changed, 429 deletions(-) delete mode 100755 trigger/opencast/classes/local/courseFilterOpencast.php delete mode 100755 trigger/opencast/classes/privacy/provider.php delete mode 100755 trigger/opencast/index.php delete mode 100755 trigger/opencast/lang/de/lifecycletrigger_opencast.php delete mode 100755 trigger/opencast/lang/en/lifecycletrigger_opencast.php delete mode 100755 trigger/opencast/lib.php delete mode 100755 trigger/opencast/pix/icon.png delete mode 100755 trigger/opencast/pix/monologo.png delete mode 100755 trigger/opencast/version.php diff --git a/trigger/opencast/classes/local/courseFilterOpencast.php b/trigger/opencast/classes/local/courseFilterOpencast.php deleted file mode 100755 index ba3b6905..00000000 --- a/trigger/opencast/classes/local/courseFilterOpencast.php +++ /dev/null @@ -1,107 +0,0 @@ -. - -/** - * Trigger subplugin, which triggers on specific jku needs only. - * - * @package lifecycletrigger_opencast - * @copyright 2024 Michael Schink JKU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace lifecycletrigger_opencast\local; - -defined('MOODLE_INTERNAL') || die(); - -class courseFilterOpencast { - // Test code - public function test() { - // Start Test - echo("
Test class: courseFilter (of lifecycle trigger: opencast)
"); - - // Get all courses & reduce them to an array with course ids - $course_ids = array_map(function($course) { return $course->id; }, get_courses()); - $tmp = count($course_ids); - echo("

All Courses: " . $tmp . ""); - - // Get courses with opencast block - $courses_with_opencast = $this->get_opencast_courses(); - echo("

Courses with opencast block: " . count($courses_with_opencast)); - foreach ($courses_with_opencast as $courseid) { - echo("
  id: " . $courseid); - } - - // Get courses without opencast block - $courses_without_opencast = $this->get_opencast_courses(true); - // Intersect course ids - $tmp = count($course_ids); - $course_ids = array_intersect($course_ids, $courses_without_opencast); - echo("

Courses without opencast block: " . count($courses_without_opencast)." (".count($course_ids)." of ".$tmp." courses filtered)"); - foreach ($courses_without_opencast as $courseid) { - if(in_array($courseid, $course_ids)) { echo("
  id: " . $courseid . ""); } - else { echo("
  id: " . $courseid); } - } - - // Unique & sorted course ids - //$course_ids = array_unique(array_intersect($course_ids, $no_meta_courses, $courses_with_roles, $courses_without_opencast, $kusss_courses_M, $visble_courses)); - $course_ids = array_unique($course_ids); - sort($course_ids); - echo(" -

- Opencast Block Filter:
- · Courses without opencast block
- Intersect unique course ids: ".count($course_ids)." - "); - foreach ($course_ids as $courseid) { - echo("
  id: " . $courseid .""); - } - } - - // ###################################### Courses without OPENCAST ###################################### - - // Get courses with (or without) opencast block - public function get_opencast_courses($without = false) { - global $DB; - - // Get all courses - $courses = get_courses(); - - // Filter course - $courses = array_filter($courses, function($course) use($DB, $without) { - // Get context of course (by id) - $context = \context_course::instance($course->id, IGNORE_MISSING); - - // Get block instances of opencast - $block_instances = $DB->get_records('block_instances', ['parentcontextid' => $context->id, 'blockname' => 'opencast']); - if(count($block_instances)) { - if(count($block_instances) > 1) { mtrace('Error: Multiple opencast block instances found in course: '.$course->fullname.' (id: '.$course->id.', shortname: '.$course->shortname.').'); } - // Opencast block found in course - if (!$without) { return true; } - else { return false; } - } else { - // No opencast block in course - if (!$without) { return false; } - else { return true; } - } - }); - - // Reduce courses to an array with course ids (& sort it) - $courses = array_map(function($course) { return $course->id; }, $courses); - sort($courses); - - return $courses; - } -} diff --git a/trigger/opencast/classes/privacy/provider.php b/trigger/opencast/classes/privacy/provider.php deleted file mode 100755 index d363b453..00000000 --- a/trigger/opencast/classes/privacy/provider.php +++ /dev/null @@ -1,42 +0,0 @@ -. - -namespace lifecycletrigger_opencast\privacy; - -use core_privacy\local\metadata\null_provider; -//use core_privacy\local\metadata\collection; - - /** - * Privacy subsystem implementation for lifecycletrigger_opencast. - * - * @package lifecycletrigger_opencast - * @copyright 2024 Michael Schink JKU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class provider implements null_provider { - // This plugin does not store any personal user data. - //\core_privacy\local\metadata\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/trigger/opencast/index.php b/trigger/opencast/index.php deleted file mode 100755 index 9f3aaaa1..00000000 --- a/trigger/opencast/index.php +++ /dev/null @@ -1,18 +0,0 @@ -test(); diff --git a/trigger/opencast/lang/de/lifecycletrigger_opencast.php b/trigger/opencast/lang/de/lifecycletrigger_opencast.php deleted file mode 100755 index 9a61319c..00000000 --- a/trigger/opencast/lang/de/lifecycletrigger_opencast.php +++ /dev/null @@ -1,35 +0,0 @@ -. - -/** - * Plugin strings for lifecycletrigger_opencast. - * - * @package lifecycletrigger_opencast - * @category string - * @copyright 2024 Michael Schink - * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['pluginname'] = 'Opencast Block Trigger'; -$string['plugindescription'] = 'Opencast Block Trigger'; -$string['privacy:metadata'] = 'Dieses Subplugin speichert keine persönlichen Daten.'; - -// For filtering opencast courses -$string['opencast_courses'] = 'Kursfilter per "Opencast Videos" Block'; -$string['no_opencast'] = 'Nur Kurse ohne "Opencast Videos" Block'; -$string['only_opencast'] = 'Nur Kurse mit "Opencast Videos" Block'; diff --git a/trigger/opencast/lang/en/lifecycletrigger_opencast.php b/trigger/opencast/lang/en/lifecycletrigger_opencast.php deleted file mode 100755 index 19bda46f..00000000 --- a/trigger/opencast/lang/en/lifecycletrigger_opencast.php +++ /dev/null @@ -1,35 +0,0 @@ -. - -/** - * Plugin strings for lifecycletrigger_opencast. - * - * @package lifecycletrigger_opencast - * @category string - * @copyright 2024 Michael Schink - * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$string['pluginname'] = 'Opencast Block Trigger'; -$string['plugindescription'] = 'Opencast Block Trigger'; -$string['privacy:metadata'] = 'This subplugin does not store any personal data.'; - -// For filtering opencast courses -$string['opencast_courses'] = 'Course filter by "Opencast Videos" block'; -$string['no_opencast'] = 'Only courses without "Opencast Videos" block'; -$string['only_opencast'] = 'Only courses with "Opencast Videos" block'; diff --git a/trigger/opencast/lib.php b/trigger/opencast/lib.php deleted file mode 100755 index 1187d1cc..00000000 --- a/trigger/opencast/lib.php +++ /dev/null @@ -1,160 +0,0 @@ -. - -/** - * Trigger subplugin, which triggers on specific jku needs only. - * - * @package lifecycletrigger_opencast - * @copyright 2024 Michael Schink JKU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -namespace tool_lifecycle\trigger; - -//use core_reportbuilder\local\aggregation\count; -use tool_lifecycle\local\manager\settings_manager; -//use tool_lifecycle\local\manager\trigger_manager; -use tool_lifecycle\local\response\trigger_response; -use tool_lifecycle\settings_type; -// Import trigger's lib -use lifecycletrigger_opencast\local\courseFilterOpencast; - -defined('MOODLE_INTERNAL') || die(); -require_once(__DIR__ . '/../lib.php'); -require_once(__DIR__ . '/../../lib.php'); - -/** - * Class which implements the basic methods necessary for a cleanyp courses trigger subplugin - * @package lifecycletrigger_opencast - * @copyright 2024 Michael Schink JKU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class opencast extends base_automatic { - /** - * Checks the course and returns a repsonse, which tells if the course should be further processed. - * @param object $course Course to be processed. - * @param int $triggerid Id of the trigger instance. - * @return trigger_response - */ - public function check_course($course, $triggerid) { - // Everything is already in the sql statement. - return trigger_response::trigger(); - } - - /** - * Returns true or false, depending on if the current date is one of the specified days, - * at which the trigger should run. - * @param int $triggerid Id of the trigger. - * @return array A list containing the constructed sql fragment and an array of parameters. - * @throws \coding_exception - * @throws \dml_exception - * @throws \moodle_exception - * @throws \Exception - */ - public function get_course_recordset_where($triggerid) { - global $DB; - - // Create course filter - $courseFilter = new courseFilterOpencast(); - - // Get all courses & reduce them to an array with course ids - $course_ids = array_map(function($course) { return $course->id; }, get_courses()); - - // Get settings - $settings = settings_manager::get_settings($triggerid, settings_type::TRIGGER); - //mtrace(" Mike - Trigger opencast: Settings: ".print_r($settings, true)); - //echo("
SETTINGS:
".print_r($settings, true)."
"); - // throw new \moodle_exception("Error text: " . $var); - - // Filter courses with opencast block - $course_ids = $this->opencast_filter($course_ids, $settings, $courseFilter); - - // Create "where query" - //$course_ids = array_merge($course_ids, [2, 5, 9, 12, 25]); - if(count($course_ids) > 0) { - $course_ids = array_unique($course_ids); - list($insql, $inparams) = $DB->get_in_or_equal($course_ids, SQL_PARAMS_NAMED, 'courseid'); - $where = "{course}.id {$insql}"; - mtrace(" Course ids " . count($course_ids)); // . ": " . print_r($course_ids, true) . " -> where: " . $where . " + inparams: " . print_r($inparams, true)); - - // Return "where query" - return [$where, $inparams]; - } - - // Return "true or false" - //return ['true', []]; - return ['false', []]; - } - - /** - * 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 'opencast'; - } - - /** - * Defines which settings each instance of the subplugin offers for the user to define. - * @return instance_setting[] containing settings keys and PARAM_TYPES - */ - public function instance_settings() { - return [ - // Add instance for courses with opencast block - new instance_setting('opencast_courses', PARAM_TEXT), - ]; - } - - /** - * This method can be overwritten, to add form elements to the form_step_instance. - * It is called in definition(). - * @param \MoodleQuickForm $mform - * @throws \coding_exception - */ - public function extend_add_instance_form_definition($mform) { - // Add select for courses with opencast block - $options = [ - 'no_opencast' => get_string('no_opencast', 'lifecycletrigger_opencast'), - 'only_opencast' => get_string('only_opencast', 'lifecycletrigger_opencast'), - ]; - $mform->addElement('select', 'opencast_courses', get_string('opencast_courses', 'lifecycletrigger_opencast'), $options); - $mform->setDefault('opencast_courses', 'no_opencast'); - } - - /** - * Validate parsable dates. - * @param array $error Array containing all errors. - * @param array $data Data passed from the moodle form to be validated. - * @throws \coding_exception - */ - public function extend_add_instance_form_validation(&$error, $data) { - - } - - // Filter courses with opencast block - public function opencast_filter($course_ids, $settings, $courseFilter) { - mtrace(" Mike - Trigger opencast: Filter opencast courses: ".$settings["opencast_courses"]); - if($settings["opencast_courses"] == "no_opencast") { $courses = $courseFilter->get_opencast_courses(true); } - else { $courses = $courseFilter->get_opencast_courses(false); } - mtrace(" Mike - Trigger opencast: Opencast courses result: ".count($courses)); - // Intersect course ids - $course_ids = array_intersect($course_ids, $courses); - mtrace(" Mike - Trigger opencast: Opencast courses intersected"); - // Sort course ids - sort($course_ids); - - return $course_ids; - } -} diff --git a/trigger/opencast/pix/icon.png b/trigger/opencast/pix/icon.png deleted file mode 100755 index 3cc72552fe8b7ac2fcebd3d0c9da994c465a8eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^G9b(WBpAZe8a#m1Tu&FrkP5~v7dHwvDDbdexWBu@ z*`UMr@rB!w0T->fgd;XL@b=DARI;3~`D6I8z-ontrq+%g z2Mt{Yrib#MH7-0lRdw5IY3d!(scJJ9I=%flZ&qE#N6!eo{=gijiy1Pm)g8*qepGEs z^e}0;_8{0^ZkmhT%DQ@^v@Q M)78&qol`;+03~lw=Kufz diff --git a/trigger/opencast/pix/monologo.png b/trigger/opencast/pix/monologo.png deleted file mode 100755 index 3cc72552fe8b7ac2fcebd3d0c9da994c465a8eab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214 zcmeAS@N?(olHy`uVBq!ia0vp^G9b(WBpAZe8a#m1Tu&FrkP5~v7dHwvDDbdexWBu@ z*`UMr@rB!w0T->fgd;XL@b=DARI;3~`D6I8z-ontrq+%g z2Mt{Yrib#MH7-0lRdw5IY3d!(scJJ9I=%flZ&qE#N6!eo{=gijiy1Pm)g8*qepGEs z^e}0;_8{0^ZkmhT%DQ@^v@Q M)78&qol`;+03~lw=Kufz diff --git a/trigger/opencast/version.php b/trigger/opencast/version.php deleted file mode 100755 index 98bd77ff..00000000 --- a/trigger/opencast/version.php +++ /dev/null @@ -1,32 +0,0 @@ -. - -/** - * Life Cycle Trigger lifecycletrigger_opencast. - * - * @package lifecycletrigger_opencast - * @copyright 2024 Michael Schink JKU - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -$plugin->version = 2025071000; -$plugin->component = 'lifecycletrigger_opencast'; -$plugin->dependencies = array('tool_lifecycle' => 2022112400); -$plugin->requires = 2020061500; // Requires Moodle 3.9+. -$plugin->release = '0.1.0'; -$plugin->maturity = MATURITY_ALPHA; //MATURITY_STABLE From b2570b23b7bebf3be1136a79c4fc28a878f39561 Mon Sep 17 00:00:00 2001 From: Michael Schink Date: Tue, 23 Jun 2026 17:30:40 +0200 Subject: [PATCH 4/6] PRLMDL-858 * Fixed again: Workflows with triggers, which have more than 65.535 parameters (or course IDs) throw an error (in all versions) * For tool_lifecycle version: 5.2-r1 * Only 1 line changed (since v5.0-r3) in method: get_course_recordset(...): $SESSION->debugtriggersql = $debugsql; -> $SESSION->debugprocesssql = $debugsql; * Adaptions for older moodle versions: -> Look at ELEARN-1158 for moodle 5.0 -> Look at ELEARN-1070 for moodle 4.4 & 4.5 --- classes/intersectedRecordset.php | 157 ++++++++++++++++++++++ classes/processor.php | 220 +++++++++++++++++++++++++++++++ 2 files changed, 377 insertions(+) create mode 100644 classes/intersectedRecordset.php diff --git a/classes/intersectedRecordset.php b/classes/intersectedRecordset.php new file mode 100644 index 00000000..29611f2e --- /dev/null +++ b/classes/intersectedRecordset.php @@ -0,0 +1,157 @@ +. + +/** + * Helper class which intersects multiple moodle record sets. + * + * @package tool_lifecycle + * @copyright 2025 Michael Schink JKU + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace tool_lifecycle\local; + +defined('MOODLE_INTERNAL') || die(); + +class intersectedRecordset implements \Iterator, \Countable { + private $records = []; + private $position = 0; + private $wasFilled = false; + + /** + * Constructor: Inits class & intersects passed recordsets. + * + * @param moodle_recordset|array|null $recordsets + * @param string $key + */ + public function __construct($recordsets = null, string $key = 'id') { + if($recordsets !== null) { + if(is_array($recordsets)) { + // For multiple recordsets + foreach($recordsets as $recordset) { + // If recordset is a chunked recordset + if(is_array($recordset)) { + //mtrace('Chunked recordset'); + // Create new array for chunked recordset + $chunkedRecords = []; + // For each chunked recordset + foreach($recordset as $chunk_recordset) { + // For each record in chunked recordset + foreach($chunk_recordset as $record) { + if(isset($record->$key)) { $chunkedRecords[$record->$key] = $record; } + } + } + // Add all records of chunked recordsets + $this->add($chunkedRecords, $key); + } else { + //mtrace('Normal recordset'); + $this->add($recordset, $key); + } + } + } else { $this->add($recordsets, $key); } + } + } + + /** + * Adds recordset & saves intersection of all recordsets. + * + * @param moodle_recordset $recordset + * @param string $key + */ + public function add($recordset, string $key = 'id'): void { + // Add new records to array with key + $newRecords = []; + foreach($recordset as $record) { + if(isset($record->$key)) { $newRecords[$record->$key] = $record; } + } + //mtrace(' Found '.count($newRecords).' records in recordset'); + //$recordset->close(); + + // Store new records without key, if no records were stored & return + if(empty($this->records) && !$this->wasFilled) { + $this->records = array_values($newRecords); + $this->wasFilled = true; + + return; + } + + // Add existing records to array with key + $existingRecords = []; + foreach($this->records as $record) { + if(isset($record->$key)) { $existingRecords[$record->$key] = $record; } + } + + // Intersect existing & new records by keys + $intersectionKeys = array_intersect_key($existingRecords, $newRecords); + // Clear existing records + $this->records = []; + // Store intersected records by keys + foreach($intersectionKeys as $keyValue => $record) { + $this->records[] = $existingRecords[$keyValue]; + } + //mtrace('Add - Intersected record sets: '.count($this->records)); + } + + /** + * Returns current recordset. + * + * @return mixed + */ + public function current(): mixed { + return $this->records[$this->position]; + } + + /** + * Returns current key (index). + * + * @return int + */ + public function key(): int { + return $this->position; + } + + /** + * Moves internal pointer to next recordset. + */ + public function next(): void { + $this->position++; + } + + /** + * Returns internal pointer to start. + */ + public function rewind(): void { + $this->position = 0; + } + + /** + * Checks if current pointer points to a valid recordset. + * + * @return bool + */ + public function valid(): bool { + return isset($this->records[$this->position]); + } + + /** + * Returns the amount of all recordsets. + * + * @return int + */ + public function count(): int { + return count($this->records); + } +} \ No newline at end of file diff --git a/classes/processor.php b/classes/processor.php index e2e656fd..eba0ca9f 100644 --- a/classes/processor.php +++ b/classes/processor.php @@ -345,6 +345,225 @@ public function process_course_interactive($processid) { * @throws \coding_exception * @throws \dml_exception */ + public function get_course_recordset($triggers, $nositecourse = true, $forcounting = false) { + global $DB, $SESSION; + + // Mike + $where = []; + $whereparams = []; + $recordsets = []; + $workflow = false; + foreach ($triggers as $trigger) { + $where_tmp2 = " TRUE "; + $whereparams_tmp2 = []; + if (!$workflow) { + $workflow = workflow_manager::get_workflow($trigger->workflowid); + $andor = ($workflow->andor ?? 0) == 0 ? 'AND' : 'OR'; + $where_tmp2 = $andor == 'AND' ? 'true ' : 'false '; + } + $lib = lib_manager::get_automatic_trigger_lib($trigger->subpluginname); + // Exclude specificdate trigger when counting. + if (!($forcounting && $lib->default_response() == trigger_response::triggertime())) { + [$sql, $params] = $lib->get_course_recordset_where($trigger->id); + $sql = preg_replace("/{course}/", "c", $sql, 1); + if (!empty($sql)) { + $where_tmp2 .= " $andor " . $sql; + $whereparams[] = array_merge($whereparams_tmp2, $params); + } else { $whereparams[] = []; } + } else { $whereparams[] = []; } + $where[] = $where_tmp2; + } + + $maxparams = 65535; + //$maxparams = 1000; + mtrace(''); + mtrace('Start - MAX params: '.$maxparams.', trigger where parts: '.count($where)/*.' '.print_r($where, true)*/.' & params: '.count($whereparams)/*.' '.print_r($whereparams, true)*/); + foreach($whereparams as $key => $whereparam) { + if(count($whereparam) > $maxparams) { + mtrace('More than '.$maxparams.' params with key '.$key.': '.count($whereparam)); + // Get where part of params array + $wherepart = $where[$key]; + // Get first & last param + $first = ':'.array_key_first($whereparam); + $last = ':'.array_key_last($whereparam); + mtrace(' 1. Get first param '.$first.' & last param '.$last); + // Get where part before first param & after last param (to re-create where part) + $position = strpos($wherepart, $first); + $before = substr($wherepart, 0, $position); + $position = strpos($wherepart, $last); + $after = substr($wherepart, $position + strlen($last)); + mtrace(' 2. Re-create where part: '.$before.' '.$after); + // Remove original where part & params + //unset($where[$key]); + //unset($whereparams[$key]); + $where[$key] = []; + $whereparams[$key] = []; + mtrace(' 3. Remove where part & params with key: '.$key); + // Chunk params + $whereparam_chunks = array_chunk($whereparam, $maxparams, true); + mtrace(' 4. Chunk params: '.count($whereparam_chunks)/*.print_r($whereparam_chunks, true))*/.' ('.count($whereparam).'/'.$maxparams.')'); + // For each chunk of params + $counter = 0; + foreach($whereparam_chunks as $whereparam_chunk) { + $counter++; + // Create param string of chunk params + $whereparam_chunk_string = implode(',', array_map(function($value) { return ':' . $value; }, array_keys($whereparam_chunk))); + // Re-create where part for chunk + $where_chunk = $before.$whereparam_chunk_string.$after; + if(count($whereparam_chunks) > 10 && $counter == 10 ) { mtrace(' ...'); } + if($counter < 5 || $counter >= (count($whereparam_chunks) - 5)) { mtrace(' 5.'.$counter.' Add chunk query: '.(strlen($where_chunk) > 150 ? substr($where_chunk, 0, 150) . '...' : $where_chunk).' & params: '.count($whereparam_chunk)/*.print_r($whereparam_chunk, true)*/); } + // Add where part & params of chunk + if(count($where) && count($whereparams)) { + $where[$key][] = $where_chunk; + $whereparams[$key][] = $whereparam_chunk; + } else { mtrace('ERROR: Amount of where parts & params are not the same!'); } + } + } + } + mtrace('End - MAX params: '.$maxparams.', trigger where parts: '.count($where)/*.' '.print_r($where, true)*/.' & params '.count($whereparams)/*.' '.print_r($whereparams, true)*/); + mtrace(''); + //die(); + + if ($forcounting) { + foreach ($where as $key => $where_tmp) { + $whereparams_tmp = $whereparams[$key]; + // Get course hasprocess and delay with the sql. + $sql = "SELECT c.id, + COALESCE(p.courseid, pe.courseid, 0) as hasprocess, + COALESCE(po.workflowid, peo.workflowid, 0) as hasotherwfprocess, + CASE + WHEN COALESCE(d.delayeduntil, 0) > COALESCE(dw.delayeduntil, 0) THEN d.delayeduntil + WHEN COALESCE(d.delayeduntil, 0) < COALESCE(dw.delayeduntil, 0) THEN dw.delayeduntil + ELSE 0 + END as delaycourse + FROM {course} c + LEFT JOIN {tool_lifecycle_process} p ON c.id = p.courseid AND p.workflowid = $workflow->id + LEFT JOIN {tool_lifecycle_proc_error} pe ON c.id = pe.courseid AND pe.workflowid = $workflow->id + LEFT JOIN {tool_lifecycle_process} po ON c.id = po.courseid AND po.workflowid <> $workflow->id + LEFT JOIN {tool_lifecycle_proc_error} peo ON c.id = peo.courseid AND peo.workflowid <> $workflow->id + LEFT JOIN {tool_lifecycle_delayed} d ON c.id = d.courseid + LEFT JOIN {tool_lifecycle_delayed_workf} dw ON + c.id = dw.courseid AND dw.workflowid = $workflow->id + WHERE "; + if(is_array($where_tmp)) { + //mtrace('Chunked recordset: '.count($where_tmp)/*.' '.print_r($where_tmp, true)*/.', params: '.count($whereparams_tmp)/*.' '.print_r($whereparams_tmp, true)*/); + $tmp = []; + foreach($where_tmp as $chunk_key => $chunk_where_tmp) { + // We include delayed courses here anyway, so we only take the site course into account. + if ($nositecourse) { + $chunk_where_tmp = "($chunk_where_tmp) AND c.id <> 1 "; + } + $sql_tmp = $sql.$chunk_where_tmp; + $debugsql = $sql_tmp; + foreach ($whereparams_tmp as $key => $value) { + $debugsql = str_replace(":".$key, $value, $debugsql); + } + // v5.0-r3 + //$SESSION->debugtriggersql = $debugsql; + // v5.2-r1 + $SESSION->debugprocesssql = $debugsql; + $tmp[] = $DB->get_recordset_sql($sql_tmp, $whereparams_tmp[$chunk_key]); + } + $recordsets[] = $tmp; + } else { + //mtrace('Nomrmal recordset: '.$where_tmp.', params: '.count($whereparams_tmp)/*.' '.print_r($whereparams_tmp, true)*/); + // We include delayed courses here anyway, so we only take the site course into account. + if ($nositecourse) { + $where_tmp = "($where_tmp) AND c.id <> 1 "; + } + $sql_tmp = $sql.$where_tmp; + $debugsql = $sql_tmp; + foreach ($whereparams_tmp as $key => $value) { + $debugsql = str_replace(":".$key, $value, $debugsql); + } + // v5.0-r3 + //$SESSION->debugtriggersql = $debugsql; + // v5.2-r1 + $SESSION->debugprocesssql = $debugsql; + $recordsets[] = $DB->get_recordset_sql($sql_tmp, $whereparams_tmp); + } + } + + //use tool_lifecycle\local\intersectedRecordset; + $recordsets = new \tool_lifecycle\local\intersectedRecordset($recordsets); + //mtrace('Intersected record sets (for counting): '.count($recordsets)); + } else { + foreach ($where as $key => $where_tmp) { + $whereparams_tmp = $whereparams[$key]; + // Get only courses which are not part of an existing process. + $sql = "SELECT c.id from {course} c + LEFT JOIN {tool_lifecycle_process} p ON c.id = p.courseid + LEFT JOIN {tool_lifecycle_proc_error} pe ON c.id = pe.courseid + WHERE p.courseid is null AND pe.courseid IS NULL AND "; + if(is_array($where_tmp)) { + //mtrace('Chunked recordset: '.count($where_tmp)/*.' '.print_r($where_tmp, true)*/.', params: '.count($whereparams_tmp)/*.' '.print_r($whereparams_tmp, true)*/); + $tmp = []; + foreach($where_tmp as $chunk_key => $chunk_where_tmp) { + if ($workflow) { + if (!$workflow->includesitecourse) { + $chunk_where_tmp = "($chunk_where_tmp) AND c.id <> 1 "; + } + if (!$workflow->includedelayedcourses) { + $chunk_where_tmp = "($chunk_where_tmp) AND NOT c.id in (select courseid FROM {tool_lifecycle_delayed_workf} + WHERE delayeduntil > :time1 AND workflowid = :workflowid) + AND NOT c.id in (select courseid FROM {tool_lifecycle_delayed} WHERE delayeduntil > :time2) "; + $inparams = ['time1' => time(), 'time2' => time(), 'workflowid' => $workflow->id]; + $whereparams_tmp = array_merge($whereparams_tmp, $inparams); + } + } + $sql_tmp = $sql.$chunk_where_tmp; + $debugsql = $sql_tmp; + foreach ($whereparams_tmp as $key => $value) { + $debugsql = str_replace(":".$key, $value, $debugsql); + } + // v5.0-r3 + //$SESSION->debugtriggersql = $debugsql; + // v5.2-r1 + $SESSION->debugprocesssql = $debugsql; + $tmp[] = $DB->get_recordset_sql($sql_tmp, $whereparams_tmp[$chunk_key]); + } + $recordsets[] = $tmp; + } else { + //mtrace('Nomrmal recordset: '.$where_tmp.', params: '.count($whereparams_tmp)/*.' '.print_r($whereparams_tmp, true)*/); + if ($workflow) { + if (!$workflow->includesitecourse) { + $where_tmp = "($where_tmp) AND c.id <> 1 "; + } + if (!$workflow->includedelayedcourses) { + $where_tmp = "($where_tmp) AND NOT c.id in (select courseid FROM {tool_lifecycle_delayed_workf} + WHERE delayeduntil > :time1 AND workflowid = :workflowid) + AND NOT c.id in (select courseid FROM {tool_lifecycle_delayed} WHERE delayeduntil > :time2) "; + $inparams = ['time1' => time(), 'time2' => time(), 'workflowid' => $workflow->id]; + $whereparams_tmp = array_merge($whereparams_tmp, $inparams); + } + } + $sql_tmp = $sql.$where_tmp; + $debugsql = $sql_tmp; + foreach ($whereparams_tmp as $key => $value) { + $debugsql = str_replace(":".$key, $value, $debugsql); + } + // v5.0-r3 + //$SESSION->debugtriggersql = $debugsql; + // v5.2-r1 + $SESSION->debugprocesssql = $debugsql; + $recordsets[] = $DB->get_recordset_sql($sql_tmp, $whereparams_tmp); + } + } + + //use tool_lifecycle\local\intersectedRecordset; + $recordsets = new \tool_lifecycle\local\intersectedRecordset($recordsets); + //mtrace('Intersected record sets: '.count($recordsets)); + } + + mtrace(''); + mtrace('FINAL recordsets: '.$recordsets->count()/*.' '.print_r($recordsets, true)*/); + mtrace(''); + //die(); + + return $recordsets; + } + + /* public function get_course_recordset($triggers, $nositecourse = true, $forcounting = false) { global $DB, $SESSION; @@ -419,6 +638,7 @@ public function get_course_recordset($triggers, $nositecourse = true, $forcounti return $DB->get_recordset_sql($sql, $whereparams); } + */ /** * Returns the number of courses for a trigger for counting. From 9b89d1df7ab24ff227de10cec065b0a1a1ff0871 Mon Sep 17 00:00:00 2001 From: Michael Schink Date: Tue, 23 Jun 2026 18:44:38 +0200 Subject: [PATCH 5/6] PRLMDL-858 * Bump version to rule out errors as a cause --- version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.php b/version.php index c9b2f76c..2d2e44c7 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ $plugin->component = 'tool_lifecycle'; $plugin->maturity = MATURITY_STABLE; -$plugin->version = 2026060100; +$plugin->version = 2026060101; $plugin->requires = 2024100700; // Requires Moodle 4.5+. $plugin->supported = [405, 502]; $plugin->release = 'v5.2-r1'; From b3525a7f0f8ad7b07e3551f26ed126d51e86f2ad Mon Sep 17 00:00:00 2001 From: Michael Schink Date: Tue, 23 Jun 2026 19:01:47 +0200 Subject: [PATCH 6/6] PRLMDL-858 * Move class intersectedRecordset to dir tool_lifecycle\local --- classes/{ => local}/intersectedRecordset.php | 0 version.php | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename classes/{ => local}/intersectedRecordset.php (100%) diff --git a/classes/intersectedRecordset.php b/classes/local/intersectedRecordset.php similarity index 100% rename from classes/intersectedRecordset.php rename to classes/local/intersectedRecordset.php diff --git a/version.php b/version.php index 2d2e44c7..d9206c80 100644 --- a/version.php +++ b/version.php @@ -26,7 +26,7 @@ $plugin->component = 'tool_lifecycle'; $plugin->maturity = MATURITY_STABLE; -$plugin->version = 2026060101; +$plugin->version = 2026060102; $plugin->requires = 2024100700; // Requires Moodle 4.5+. $plugin->supported = [405, 502]; $plugin->release = 'v5.2-r1';