diff --git a/classes/local/form/form_step_instance.php b/classes/local/form/form_step_instance.php index f080413d..8b5503ec 100644 --- a/classes/local/form/form_step_instance.php +++ b/classes/local/form/form_step_instance.php @@ -161,8 +161,11 @@ public function definition_after_data() { $mform->setDefault('id', ''); $subpluginname = $this->subpluginname; } - $mform->setDefault('subpluginnamestatic', - get_string('pluginname', 'lifecyclestep_' . $subpluginname)); + + if (isset($this->lib)) { + $mform->setDefault('subpluginnamestatic', $this->lib->get_plugin_description()); + } + $mform->setDefault('subpluginname', $subpluginname); // Setting the default values for the local step settings. diff --git a/classes/local/form/form_trigger_instance.php b/classes/local/form/form_trigger_instance.php index 0ac8f901..353dbae0 100644 --- a/classes/local/form/form_trigger_instance.php +++ b/classes/local/form/form_trigger_instance.php @@ -162,8 +162,11 @@ public function definition_after_data() { $mform->setDefault('id', $this->trigger->id); $mform->setDefault('instancename', $this->trigger->instancename); } - $mform->setDefault('subpluginnamestatic', - get_string('pluginname', 'lifecycletrigger_' . $this->subpluginname)); + + if (isset($this->lib)) { + $mform->setDefault('subpluginnamestatic', $this->lib->get_plugin_description()); + } + $mform->setDefault('subpluginname', $this->subpluginname); // Setting the default values for the local trigger settings. diff --git a/classes/local/manager/lib_manager.php b/classes/local/manager/lib_manager.php index afd381ee..22861672 100644 --- a/classes/local/manager/lib_manager.php +++ b/classes/local/manager/lib_manager.php @@ -102,18 +102,27 @@ public static function get_step_interactionlib($subpluginname) { * @return null|base|libbase */ private static function get_lib($subpluginname, $subplugintype, $libsubtype = '') { + // Plugins defined in subplugins.json file. $triggerlist = \core_component::get_plugin_list('lifecycle' . $subplugintype); - if (!array_key_exists($subpluginname, $triggerlist)) { - return null; - } - $filename = $triggerlist[$subpluginname].'/'.$libsubtype.'lib.php'; - if (file_exists($filename)) { - require_once($filename); - $extendedclass = "tool_lifecycle\\$subplugintype\\$libsubtype$subpluginname"; - if (class_exists($extendedclass)) { - return new $extendedclass(); + if (array_key_exists($subpluginname, $triggerlist)) { + $filename = $triggerlist[$subpluginname].'/'.$libsubtype.'lib.php'; + if (file_exists($filename)) { + require_once($filename); + $extendedclass = "tool_lifecycle\\$subplugintype\\$libsubtype$subpluginname"; + if (class_exists($extendedclass)) { + return new $extendedclass(); + } } } + + // Plugins defined under "lifecycle" name space. + // The base class has already been checked by get_trigger_types or get_steps_types. + $classname = !$libsubtype ? $subplugintype : $libsubtype; + $classname = "$subpluginname\\lifecycle\\$classname"; + if (class_exists($classname)) { + return new $classname(); + } + return null; } } diff --git a/classes/local/manager/step_manager.php b/classes/local/manager/step_manager.php index cd58d1a5..6c6b3478 100644 --- a/classes/local/manager/step_manager.php +++ b/classes/local/manager/step_manager.php @@ -228,11 +228,27 @@ public static function get_step_instances_by_subpluginname($subpluginname) { * @throws \coding_exception */ public static function get_step_types() { + // Sub plugins in 'step' folder. $subplugins = \core_component::get_plugin_list('lifecyclestep'); $result = array(); foreach (array_keys($subplugins) as $plugin) { $result[$plugin] = get_string('pluginname', 'lifecyclestep_' . $plugin); } + + // Additional sub plugins defined under "lifecycle" name space, ie "local_newstep\lifecycle". + // The class name must be step (step.php) and placed under "classes/lifecycle" folder. + // The name space must be "local_newstep\lifecycle" + // The "local_newstep\lifecycle\step" class must extend the step base classes. + foreach (array_keys(\core_component::get_plugin_types()) as $plugintype) { + $potentialsteps = \core_component::get_plugin_list_with_class($plugintype, 'lifecycle\\step'); + foreach ($potentialsteps as $plugin => $potentialstep) { + // Check if it implements the step base class. + if (is_a($potentialstep, \tool_lifecycle\step\libbase::class, true)) { + $result[$plugin] = get_string('pluginname', $plugin); + } + } + } + return $result; } diff --git a/classes/local/manager/trigger_manager.php b/classes/local/manager/trigger_manager.php index dc33b086..9151ef84 100644 --- a/classes/local/manager/trigger_manager.php +++ b/classes/local/manager/trigger_manager.php @@ -241,6 +241,21 @@ public static function get_trigger_types() { foreach (array_keys($subplugins) as $plugin) { $result[$plugin] = get_string('pluginname', 'lifecycletrigger_' . $plugin); } + + // Additional sub plugins defined under "lifecycle" name space, ie "local_newtrigger\lifecycle". + // The class name must be trigger (trigger.php) and placed under "classes/lifecycle" folder. + // The name space must be "local_newtrigger\lifecycle" + // The "local_newtrigger\lifecycle\trigger" class must extend the trigger base classes (base_automatic or base_manual). + foreach (array_keys(\core_component::get_plugin_types()) as $plugintype) { + $potentialtriggers = \core_component::get_plugin_list_with_class($plugintype, 'lifecycle\\trigger'); + foreach ($potentialtriggers as $plugin => $potentialtrigger) { + // Check if it implements the trigger base class. + if (is_a($potentialtrigger, \tool_lifecycle\trigger\base::class, true)) { + $result[$plugin] = get_string('pluginname', $plugin); + } + } + } + return $result; } diff --git a/step/lib.php b/step/lib.php index 7209d1d3..b2ab6a79 100644 --- a/step/lib.php +++ b/step/lib.php @@ -142,6 +142,16 @@ public function extend_add_instance_form_definition_after_data($mform, $settings public function abort_course($process) { } + /** + * 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()); + } + } /** diff --git a/tests/fixtures/fakeplugins/samplestep/classes/lifecycle/interaction.php b/tests/fixtures/fakeplugins/samplestep/classes/lifecycle/interaction.php new file mode 100644 index 00000000..ac126624 --- /dev/null +++ b/tests/fixtures/fakeplugins/samplestep/classes/lifecycle/interaction.php @@ -0,0 +1,33 @@ +dirroot . '/admin/tool/lifecycle/step/interactionlib.php'); + +use tool_lifecycle\step\interactionlibbase; + +defined('MOODLE_INTERNAL') || die(); + +class interaction extends interactionlibbase { + + public function get_relevant_capability() + { + } + + public function get_action_tools($process) + { + } + + public function get_status_message($process) + { + } + + public function get_action_string($action, $user) + { + } + + public function handle_interaction($process, $step, $action = 'default') + { + } +} diff --git a/tests/fixtures/fakeplugins/samplestep/classes/lifecycle/step.php b/tests/fixtures/fakeplugins/samplestep/classes/lifecycle/step.php new file mode 100644 index 00000000..7fdc574e --- /dev/null +++ b/tests/fixtures/fakeplugins/samplestep/classes/lifecycle/step.php @@ -0,0 +1,27 @@ +dirroot . '/admin/tool/lifecycle/step/lib.php'); + +use tool_lifecycle\step\libbase; + +defined('MOODLE_INTERNAL') || die(); + +class step extends libbase { + public function get_subpluginname() + { + return 'sample step'; + } + + public function get_plugin_description() { + return "Sample step plugin"; + } + + public function process_course($processid, $instanceid, $course) + { + return null; + } + +} diff --git a/tests/fixtures/fakeplugins/samplestep/classes/privacy/provider.php b/tests/fixtures/fakeplugins/samplestep/classes/privacy/provider.php new file mode 100644 index 00000000..de402f95 --- /dev/null +++ b/tests/fixtures/fakeplugins/samplestep/classes/privacy/provider.php @@ -0,0 +1,38 @@ +. + +namespace tool_samplestep\privacy; + +use core_privacy\local\metadata\null_provider; + +/** + * Privacy subsystem implementation for tool_samplestep. + * + * @package tool_samplestep + * @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 the reason + */ + public static function get_reason() : string { + return 'privacy:metadata'; + } +} diff --git a/tests/fixtures/fakeplugins/samplestep/lang/en/tool_samplestep.php b/tests/fixtures/fakeplugins/samplestep/lang/en/tool_samplestep.php new file mode 100644 index 00000000..cbd7087c --- /dev/null +++ b/tests/fixtures/fakeplugins/samplestep/lang/en/tool_samplestep.php @@ -0,0 +1,18 @@ +. + +$string['pluginname'] = 'Sample step'; +$string['privacy:metadata'] = 'The plugin does not store any personal data.'; diff --git a/tests/fixtures/fakeplugins/samplestep/version.php b/tests/fixtures/fakeplugins/samplestep/version.php new file mode 100644 index 00000000..440d216e --- /dev/null +++ b/tests/fixtures/fakeplugins/samplestep/version.php @@ -0,0 +1,28 @@ +. + +/** + * Fake component for testing + * + * @package core + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2023100400; +$plugin->requires = 2022041200; +$plugin->component = 'tool_samplestep'; diff --git a/tests/fixtures/fakeplugins/sampletrigger/classes/lifecycle/trigger.php b/tests/fixtures/fakeplugins/sampletrigger/classes/lifecycle/trigger.php new file mode 100644 index 00000000..6ec21230 --- /dev/null +++ b/tests/fixtures/fakeplugins/sampletrigger/classes/lifecycle/trigger.php @@ -0,0 +1,28 @@ +dirroot . '/admin/tool/lifecycle/trigger/lib.php'); + +use tool_lifecycle\trigger\base_automatic; + +defined('MOODLE_INTERNAL') || die(); + +class trigger extends base_automatic { + + public function get_subpluginname() + { + return 'sample trigger'; + } + + public function get_plugin_description() { + return "Sample trigger"; + } + + public function check_course($course, $triggerid) + { + return null; + } + +} diff --git a/tests/fixtures/fakeplugins/sampletrigger/classes/privacy/provider.php b/tests/fixtures/fakeplugins/sampletrigger/classes/privacy/provider.php new file mode 100644 index 00000000..8ba4c99f --- /dev/null +++ b/tests/fixtures/fakeplugins/sampletrigger/classes/privacy/provider.php @@ -0,0 +1,38 @@ +. + +namespace tool_sampletrigger\privacy; + +use core_privacy\local\metadata\null_provider; + +/** + * Privacy subsystem implementation for tool_sampletrigger. + * + * @package tool_sampletrigger + * @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 the reason + */ + public static function get_reason() : string { + return 'privacy:metadata'; + } +} diff --git a/tests/fixtures/fakeplugins/sampletrigger/lang/en/tool_sampletrigger.php b/tests/fixtures/fakeplugins/sampletrigger/lang/en/tool_sampletrigger.php new file mode 100644 index 00000000..ed3104d6 --- /dev/null +++ b/tests/fixtures/fakeplugins/sampletrigger/lang/en/tool_sampletrigger.php @@ -0,0 +1,18 @@ +. + +$string['pluginname'] = 'Sample trigger'; +$string['privacy:metadata'] = 'The plugin does not store any personal data.'; diff --git a/tests/fixtures/fakeplugins/sampletrigger/version.php b/tests/fixtures/fakeplugins/sampletrigger/version.php new file mode 100644 index 00000000..38a6277f --- /dev/null +++ b/tests/fixtures/fakeplugins/sampletrigger/version.php @@ -0,0 +1,28 @@ +. + +/** + * Fake component for testing + * + * @package core + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +defined('MOODLE_INTERNAL') || die(); + +$plugin->version = 2023100400; +$plugin->requires = 2022041200; +$plugin->component = 'tool_sampletrigger'; diff --git a/tests/subplugin_test.php b/tests/subplugin_test.php new file mode 100644 index 00000000..338931b9 --- /dev/null +++ b/tests/subplugin_test.php @@ -0,0 +1,116 @@ +. + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->libdir.'/upgradelib.php'); + +use tool_lifecycle\local\manager\lib_manager; +use tool_lifecycle\local\manager\step_manager; +use tool_lifecycle\local\manager\trigger_manager; + + +class subplugin_test extends \advanced_testcase { + + public function test_builtin_triggers() { + $this->resetAfterTest(); + $builtintriggers = \core_component::get_plugin_list('lifecycletrigger'); + $triggers = trigger_manager::get_trigger_types(); + + // All builtin triggers are also in the list of triggers. + $this->assertEmpty(array_diff_key($builtintriggers, $triggers)); + + // Trigger classes are loadable. + foreach ($builtintriggers as $triggername => $triggerpath) { + $this->assertNotEmpty(lib_manager::get_trigger_lib($triggername)); + } + } + + public function test_builtin_steps() { + $this->resetAfterTest(); + $builtinsteps = \core_component::get_plugin_list('lifecyclestep'); + $steps = step_manager::get_step_types(); + + // All builtin steps are also in the list of steps. + $this->assertEmpty(array_diff_key($builtinsteps, $steps)); + + // Step classes are loadable. + foreach ($builtinsteps as $stepname => $steppath) { + $this->assertNotEmpty(lib_manager::get_step_lib($stepname)); + } + } + + public function test_additional_triggers() { + $this->resetAfterTest(); + + // Add a fake tool plugin, which define a trigger. + $mockedcomponent = new ReflectionClass(\core_component::class); + $mockedplugins = $mockedcomponent->getProperty('plugins'); + $mockedplugins->setAccessible(true); + $plugins = $mockedplugins->getValue(); + $plugins['tool'] += ['sampletrigger' => __DIR__ . '/fixtures/fakeplugins/sampletrigger']; + $mockedplugins->setValue($plugins); + + // The 'fixture' is not autoloaded, so we need to require it. + require_once(__DIR__ . '/fixtures/fakeplugins/sampletrigger/classes//lifecycle/trigger.php'); + + // Check if the trigger is available. + $triggers = trigger_manager::get_trigger_types(); + $this->assertArrayHasKey('tool_sampletrigger', $triggers); + + // Lib is loadable. + $this->assertInstanceOf('tool_sampletrigger\lifecycle\trigger', + lib_manager::get_trigger_lib('tool_sampletrigger')); + + // Unset the fake plugin. + unset($plugins['tool']['sampletrigger']); + $mockedplugins->setValue($plugins); + } + + public function test_additional_steps() { + $this->resetAfterTest(); + + // Add a fake tool plugin, which define a step. + $mockedcomponent = new ReflectionClass(\core_component::class); + $mockedplugins = $mockedcomponent->getProperty('plugins'); + $mockedplugins->setAccessible(true); + $plugins = $mockedplugins->getValue(); + $plugins['tool'] += ['samplestep' => __DIR__ . '/fixtures/fakeplugins/samplestep']; + $mockedplugins->setValue($plugins); + + // The 'fixture' is not autoloaded, so we need to require it. + require_once(__DIR__ . '/fixtures/fakeplugins/samplestep/classes//lifecycle/step.php'); + require_once(__DIR__ . '/fixtures/fakeplugins/samplestep/classes//lifecycle/interaction.php'); + + // Check if the step is available. + $steps = step_manager::get_step_types(); + $this->assertArrayHasKey('tool_samplestep', $steps); + + // Lib is loadable. + $this->assertInstanceOf('tool_samplestep\lifecycle\step', + lib_manager::get_step_lib('tool_samplestep')); + + // Lib subtype is loadable. + $this->assertInstanceOf('tool_samplestep\lifecycle\interaction', + lib_manager::get_step_interactionlib('tool_samplestep')); + + // Unset the fake plugin. + unset($plugins['tool']['samplestep']); + $mockedplugins->setValue($plugins); + } + +} diff --git a/trigger/lib.php b/trigger/lib.php index 3c01ffb7..df7cb0e0 100644 --- a/trigger/lib.php +++ b/trigger/lib.php @@ -115,6 +115,16 @@ public function get_status_message() { return get_string("workflow_started", "tool_lifecycle"); } + /** + * Define description of the trigger. + * Allow subplugins to have custom description. + * + * @return string description of the trigger. + */ + public function get_plugin_description() { + return get_string("pluginname", "lifecycletrigger_" . $this->get_subpluginname()); + } + } /**