diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b76ff0..3afdaec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ # Changelog +## Version 1.2.5 (XXXXXXXXXX) + +- Add an automatic plugin configuration feature, to simplify the setup process (#15 - Thanks to @melanietreitinger) +- Display a welcome message with setup instructions during plugin installation +- Add support for automated configuration using a CLI script +- Add error message during job creation, when plugin is not fully configured yet + + ## Version 1.2.4 (2024021901) - Fix image inlining for Moodle instances that reside in subdirectories (e.g., `https://your.domain/moodle`) diff --git a/README.md b/README.md index df78ddb..2646bc7 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ permissions, setup a webservice for the archive worker, and set configuration options for the Moodle plugin. -### 1. Prerequisites +### Prerequisites Installation of the additional [quiz archive worker service](https://github.com/ngandrass/moodle-quiz-archive-worker) is mandatory for this plugin to work. @@ -137,7 +137,81 @@ is mandatory for this plugin to work. [Quiz Archive Worker: Installation](https://github.com/ngandrass/moodle-quiz-archive-worker#installation)** -### 2. Create Moodle User and Role +### Automatic Configuration + +Creation of the dedicated Moodle user and role, as well as the setup of the +webservice for the archive worker, can be done automatically. + +The easiest way is to use the automatic configuration feature provided via the +Moodle admin interface. + +1. Navigate to _Site Administration_ > _Plugins_ (1) > _Activity modules_ > + _Quiz_ > _Quiz Archiver_ (2) +2. Click the _Automatic configuration_ button (3) +3. Enter the URL under which the quit archive worker can be reached (4) +4. (Optional) Change the configuration defaults (5) +5. Execute the automatic configuration (6) +6. Close the window (7) +7. (Optional) Adjust the default plugin setting on the plugin settings page + +[![Screenshot: Configuration - Automatic Configuration 1](doc/configuration/configuration_plugin_settings_1_thumb.png)](doc/configuration/configuration_plugin_settings_1.png) +[![Screenshot: Configuration - Automatic Configuration 2](doc/configuration/configuration_plugin_autoinstall_2_thumb.png)](doc/configuration/configuration_plugin_autoinstall_2.png) +[![Screenshot: Configuration - Automatic Configuration 3](doc/configuration/configuration_plugin_autoinstall_3_thumb.png)](doc/configuration/configuration_plugin_autoinstall_3.png) +[![Screenshot: Configuration - Automatic Configuration 4](doc/configuration/configuration_plugin_autoinstall_4_thumb.png)](doc/configuration/configuration_plugin_autoinstall_4.png) + + +#### Using the Command Line Interface (CLI) + +
+Expand here to show CLI configuration instructions + +If you want to configure this plugin in an automated fashion, you can use the +provided CLI script. The script is located at +`{$CFG->wwwroot}/mod/quiz/report/archiver/cli/autoinstall.php`. + +To execute the script: + +1. Open a terminal and navigate to the quiz archiver CLI directory: + ```bash + cd /path/to/moodle/mod/quiz/report/archiver/cli + ``` +2. Execute the CLI script using PHP: + ```bash + php autoinstall.php --help + ``` + +Usage: +```text +Automatically configures Moodle for use with the quiz archiver plugin. + +ATTENTION: This CLI script ... +- Enables web services and REST protocol +- Creates a quiz archiver service role and a corresponding user +- Creates a new web service with all required webservice functions +- Authorises the user to use the webservice. + +Usage: + $ php autoinstall.php + $ php autoinstall.php --username="my-custom-archive-user" + $ php autoinstall.php [--help|-h] + +Options: + --help, -h Show this help message + --force, -f Force the autoinstall, regardless of the current state of the system + --workerurl= Sets the URL of the worker (default: http://localhost:8080) + --wsname= Sets a custom name for the web service (default: quiz_archiver_webservice) + --rolename= Sets a custom name for the web service role (default: quiz_archiver) + --username= Sets a custom username for the web service user (default: quiz_archiver_serviceaccount) +``` +
+ + +### Manual Configuration + +
+Expand here to show manual configuration instructions + +### 1. Create Moodle User and Role 1. Create a designated Moodle user for the quiz archiver webservice 1. Navigate to _Site Administration_ > _Users_ (1) > _Accounts_ > _Add a new user_ (2) @@ -174,8 +248,7 @@ is mandatory for this plugin to work. [![Screenshot: Configuration - Assign Role 2](doc/configuration/configuration_assign_role_2_thumb.png)](doc/configuration/configuration_assign_role_2.png) [![Screenshot: Configuration - Assign Role 3](doc/configuration/configuration_assign_role_3_thumb.png)](doc/configuration/configuration_assign_role_3.png) - -### 3. Setup Webservice +### 2. Setup Webservice 1. Enable webservices globally 1. Navigate to _Site Administration_ > _Server_ (1) > _Web services_ > _Overview_ (2) @@ -215,7 +288,7 @@ is mandatory for this plugin to work. [![Screenshot: Configuration - Assign Webservice Functions 4](doc/configuration/configuration_assign_webservice_functions_4_thumb.png)](doc/configuration/configuration_assign_webservice_functions_4.png) -### 4. Configure Plugin Settings +### 3. Configure Plugin Settings 1. Navigate to _Site Administration_ > _Plugins_ (1) > _Activity modules_ > _Quiz_ > _Quiz Archiver_ (2) @@ -238,6 +311,7 @@ is mandatory for this plugin to work. [![Screenshot: Configuration - Plugin Settings 1](doc/configuration/configuration_plugin_settings_1_thumb.png)](doc/configuration/configuration_plugin_settings_1.png) [![Screenshot: Configuration - Plugin Settings 2](doc/configuration/configuration_plugin_settings_2_thumb.png)](doc/configuration/configuration_plugin_settings_2.png) +
### Known Pitfalls diff --git a/adminui/autoinstall.php b/adminui/autoinstall.php new file mode 100644 index 0000000..49e8fc6 --- /dev/null +++ b/adminui/autoinstall.php @@ -0,0 +1,94 @@ +. + +/** + * Handler for autoinstall feature from the admin UI of the quiz archiver plugin. + * + * @package quiz_archiver + * @copyright 2024 Niels Gandraß + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ + +/** @var bool Disables output buffering */ +const NO_OUTPUT_BUFFERING = true; + +require_once(__DIR__.'/../../../../../config.php'); +require_once("{$CFG->libdir}/moodlelib.php"); +require_once("{$CFG->dirroot}/mod/quiz/report/archiver/classes/form/autoinstall_form.php"); + +use quiz_archiver\form\autoinstall_form; +use quiz_archiver\local\autoinstall; + +// Ensure user has permissions +require_login(); +$ctx = context_system::instance(); +require_capability('moodle/site:config', $ctx); + +// Setup page +$PAGE->set_context($ctx); +$PAGE->set_url('/mod/quiz/report/archiver/adminui/autoinstall.php'); +$title = get_string('autoinstall_plugin', 'quiz_archiver'); +$PAGE->set_title($title); + +$returnlink = html_writer::link( + new moodle_url('/admin/settings.php', ['section' => 'quiz_archiver_settings']), + get_string('back') +); + +echo $OUTPUT->header(); +echo $OUTPUT->heading($title); + +// Content +if (autoinstall::plugin_is_unconfigured()) { + $form = new autoinstall_form(); + + if ($form->is_cancelled()) { + // Cancelled + echo $OUTPUT->paragraph(get_string('autoinstall_cancelled', 'quiz_archiver')); + echo $OUTPUT->paragraph($returnlink); + } else if ($data = $form->get_data()) { + // Perform autoinstall + list($success, $log) = autoinstall::execute( + $data->workerurl, + $data->wsname, + $data->rolename, + $data->username + ); + + // Show result + echo $OUTPUT->paragraph(get_string('autoinstall_started', 'quiz_archiver')); + echo $OUTPUT->paragraph(get_string('logs')); + echo "
{$log}

"; + + if ($success) { + echo $OUTPUT->paragraph(get_string('autoinstall_success', 'quiz_archiver')); + } else { + echo $OUTPUT->paragraph(get_string('autoinstall_failure', 'quiz_archiver')); + } + + echo $OUTPUT->paragraph($returnlink); + } else { + echo $OUTPUT->paragraph(get_string('autoinstall_explanation', 'quiz_archiver')); + echo $OUTPUT->paragraph(get_string('autoinstall_explanation_details', 'quiz_archiver')); + $form->display(); + } +} else { + echo $OUTPUT->paragraph(get_string('autoinstall_already_configured_long', 'quiz_archiver')); + echo $OUTPUT->paragraph($returnlink); +} + +// End page +echo $OUTPUT->footer(); diff --git a/classes/form/autoinstall_form.php b/classes/form/autoinstall_form.php new file mode 100644 index 0000000..8783a38 --- /dev/null +++ b/classes/form/autoinstall_form.php @@ -0,0 +1,76 @@ +. + +/** + * Defines the editing form for artifacts + * + * @package quiz_archiver + * @copyright 2024 Niels Gandraß + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace quiz_archiver\form; + +use quiz_archiver\local\autoinstall; + +defined('MOODLE_INTERNAL') || die(); + +require_once($CFG->dirroot.'/lib/formslib.php'); +require_once($CFG->dirroot.'/mod/quiz/report/archiver/classes/local/autoinstall.php'); + + +/** + * Form to trigger automatic installation of the quiz archiver plugin + */ +class autoinstall_form extends \moodleform { + + /** + * Form definiton. + * + * @throws \dml_exception + * @throws \coding_exception + * @throws \moodle_exception + */ + public function definition() { + $mform = $this->_form; + $mform->addElement('header', 'header', get_string('settings', 'plugin')); + + // Add configuration options + $mform->addElement('text', 'workerurl', get_string('setting_worker_url', 'quiz_archiver'), ['size' => 50]); + $mform->addElement('static', 'workerurl_help', '', get_string('setting_worker_url_desc', 'quiz_archiver')); + $mform->setType('workerurl', PARAM_TEXT); + $mform->addRule('workerurl', null, 'required', null, 'client'); + + $mform->addElement('text', 'wsname', get_string('autoinstall_wsname', 'quiz_archiver'), ['size' => 50]); + $mform->setDefault('wsname', autoinstall::DEFAULT_WSNAME); + $mform->setType('wsname', PARAM_TEXT); + $mform->addRule('wsname', null, 'required', null, 'client'); + + $mform->addElement('text', 'rolename', get_string('autoinstall_rolename', 'quiz_archiver'), ['size' => 50]); + $mform->setDefault('rolename', autoinstall::DEFAULT_ROLESHORTNAME); + $mform->setType('rolename', PARAM_TEXT); + $mform->addRule('rolename', null, 'required', null, 'client'); + + $mform->addElement('text', 'username', get_string('autoinstall_username', 'quiz_archiver'), ['size' => 50]); + $mform->setDefault('username', autoinstall::DEFAULT_USERNAME); + $mform->setType('username', PARAM_TEXT); + $mform->addRule('username', null, 'required', null, 'client'); + + // Action buttons + $this->add_action_buttons(true, get_string('confirm', 'moodle')); + } + +} diff --git a/classes/local/autoinstall.php b/classes/local/autoinstall.php new file mode 100644 index 0000000..315e788 --- /dev/null +++ b/classes/local/autoinstall.php @@ -0,0 +1,283 @@ +. + +namespace quiz_archiver\local; + +require_once("{$CFG->dirroot}/user/lib.php"); +require_once("{$CFG->dirroot}/webservice/lib.php"); + +use coding_exception; +use context_system; +use dml_exception; +use webservice; + +/** + * Autoinstall routines for the quiz_archiver plugin + * + * @package quiz_archiver + * @copyright 2024 Niels Gandraß + * 2024 Melanie Treitinger, Ruhr-Universität Bochum + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or late + */ +class autoinstall { + + /** @var string Default name for the webservice to create */ + const DEFAULT_WSNAME = 'quiz_archiver_webservice'; + + /** @var string Default username for the service account to create */ + const DEFAULT_USERNAME = 'quiz_archiver_serviceaccount'; + + /** @var string Default shortname for the role to create */ + const DEFAULT_ROLESHORTNAME = 'quiz_archiver'; + + /** @var string[] List of capabilities to assign to the created role */ + const WS_ROLECAPS = [ + 'mod/quiz:reviewmyattempts', + 'mod/quiz:view', + 'mod/quiz:viewreports', + 'mod/quiz_archiver:use_webservice', + 'moodle/backup:anonymise', + 'moodle/backup:backupactivity', + 'moodle/backup:backupcourse', + 'moodle/backup:backupsection', + 'moodle/backup:backuptargetimport', + 'moodle/backup:configure', + 'moodle/backup:downloadfile', + 'moodle/backup:userinfo', + 'moodle/course:ignoreavailabilityrestrictions', + 'moodle/course:view', + 'moodle/course:viewhiddenactivities', + 'moodle/course:viewhiddencourses', + 'moodle/course:viewhiddensections', + 'moodle/user:ignoreuserquota', + 'webservice/rest:use', + ]; + + /** @var string[] List of functions to add to the created webservice */ + const WS_FUNCTIONS = [ + 'quiz_archiver_generate_attempt_report', + 'quiz_archiver_get_attempts_metadata', + 'quiz_archiver_update_job_status', + 'quiz_archiver_process_uploaded_artifact', + 'quiz_archiver_get_backup_status', + ]; + + /** + * Determines if the quiz_archiver plugin was configured previously. + * + * @return bool True if the plugin is unconfigured, false otherwise + * @throws dml_exception If the plugin configuration cannot be retrieved + */ + public static function plugin_is_unconfigured(): bool { + return intval(get_config('quiz_archiver', 'webservice_id')) <= 0 + && intval(get_config('quiz_archiver', 'webservice_userid')) <= 0; + } + + /** + * Performs an automatic installation of the quiz_archiver plugin. + * + * This function: + * - Enables web services and REST protocol + * - Creates a quiz archiver service role and a corresponding user + * - Creates a new web service with all required webservice functions + * - Authorises the user to use the webservice. + * + * @param string $workerurl The URL of the quiz archive worker service + * @param string $wsname The name for the web service to create + * @param string $rolename The shortname for the role to create + * @param string $username The username for the service account to create + * @param bool $force If true, the installation is forced regardless of the + * current state of the system + * @return array An array with two elements: a boolean indicating success + * and a string with a log of the performed actions + */ + public static function execute( + string $workerurl, + string $wsname = self::DEFAULT_WSNAME, + string $rolename = self::DEFAULT_ROLESHORTNAME, + string $username = self::DEFAULT_USERNAME, + bool $force = false + ): array { + // Prepare return values + $success = false; + + try { + // Ensure current user is an admin. + if (!is_siteadmin()) { + $log[] = "Error: You need to be a site administrator to run this script."; + throw new \RuntimeException(); + } + + // Check if the plugin is already configured. + if (!self::plugin_is_unconfigured()) { + if ($force) { + $log[] = "Warning: The quiz archiver plugin is already configured. Forcing reconfiguration nonetheless ..."; + } else { + $log[] = "Error: The quiz archiver plugin is already configured. Use --force to bypass this check."; + throw new \RuntimeException(); + } + } + + // Check worker URL + if (empty($workerurl)) { + $log[] = "Error: The given worker URL is invalid."; + throw new \RuntimeException(); + } + + // Get system context. + try { + $systemcontext = context_system::instance(); + } catch (dml_exception $e) { + $log[] = "Error: Cannot get system context: ".$e->getMessage(); + throw new \RuntimeException(); + } + + // Create a web service user. + try { + $webserviceuserid = user_create_user([ + 'auth' => 'manual', + 'username' => $username, + 'password' => bin2hex(random_bytes(28))."#1A", + 'firstname' => 'Quiz Archiver', + 'lastname' => 'Service Account', + 'email' => 'noreply@localhost', + 'confirmed' => 1, + 'deleted' => 0, + 'policyagreed' => 1, + ]); + $webserviceuser = \core_user::get_user($webserviceuserid); + $log[] = " -> Web service user '{$webserviceuser->username}' with ID {$webserviceuser->id} created."; + } catch (dml_exception $e) { + $log[] = "Error: Cloud not create webservice user: ".$e->getMessage(); + throw new \RuntimeException(); + } catch (\Exception $e) { // \Random\RandomException is only thrown with PHP >= 8.2, generic \Exception otherwise + $log[] = "Error: Could not create webservice user: ".$e->getMessage(); + throw new \RuntimeException(); + } + + // Create a web service role. + try { + $wsroleid = create_role( + 'Quiz Archiver Service Account', + $rolename, + 'A role that bundles all access rights required for the quiz archiver plugin to work.' + ); + set_role_contextlevels($wsroleid, [CONTEXT_SYSTEM]); + + $log[] = " -> Role '{$rolename}' created."; + } catch (coding_exception $e) { + $log[] = "Error: Cannot create role {$rolename}: {$e->getMessage()}"; + throw new \RuntimeException(); + } + + foreach (self::WS_ROLECAPS as $cap){ + try { + assign_capability($cap, CAP_ALLOW, $wsroleid, $systemcontext->id, true); + $log[] = " -> Capability {$cap} assigned to role '{$rolename}'."; + } catch (coding_exception $e) { + $log[] = "Error: Cannot assign capability {$cap}: {$e->getMessage()}"; + throw new \RuntimeException(); + } + } + + // Give the user the role. + try { + role_assign($wsroleid, $webserviceuser->id, $systemcontext->id); + $log[] = " -> Role '{$rolename}' assigned to user '{$webserviceuser->username}'."; + } catch (coding_exception $e) { + $log[] = "Error: Cannot assign role to webservice user: ".$e->getMessage(); + throw new \RuntimeException(); + } + + // Enable web services and REST protocol. + try { + set_config('enablewebservices', true); + $log[] = " -> Web services enabled."; + + $enabledprotocols = get_config('core', 'webserviceprotocols'); + if (stripos($enabledprotocols, 'rest') === false) { + set_config('webserviceprotocols', $enabledprotocols . ',rest'); + } + $log[] = " -> REST webservice protocol enabled."; + } catch (dml_exception $e) { + $log[] = "Error: Cannot get config setting webserviceprotocols: ".$e->getMessage(); + throw new \RuntimeException(); + } + + // Enable the webservice. + $webservicemanager = new webservice(); + $serviceid = $webservicemanager->add_external_service((object)[ + 'name' => $wsname, + 'shortname' => $wsname, + 'enabled' => 1, + 'requiredcapability' => '', + 'restrictedusers' => true, + 'downloadfiles' => true, + 'uploadfiles' => true, + ]); + + if(!$serviceid){ + $log[] = "Error: Service {$wsname} could not be created."; + throw new \RuntimeException(); + } else { + $log[] = " -> Web service '{$wsname}' created with ID {$serviceid}."; + } + + // Add functions to the service + foreach (self::WS_FUNCTIONS as $f) { + $webservicemanager->add_external_function_to_service($f, $serviceid); + $log[] = " -> Function {$f} added to service '{$wsname}'."; + } + + // Authorise the user to use the service. + $webservicemanager->add_ws_authorised_user((object) [ + 'externalserviceid' => $serviceid, + 'userid' => $webserviceuser->id + ]); + + $service = $webservicemanager->get_external_service_by_id($serviceid); + $webservicemanager->update_external_service($service); + $log[] = " -> User '{$webserviceuser->username}' authorised to use service '{$wsname}'."; + + // Configure quiz_archiver plugin settings + try { + $log[] = " -> Configuring the quiz archiver plugin..."; + + set_config('webservice_id', $serviceid, 'quiz_archiver'); + $log[] = " -> Web service set to '{$wsname}'."; + + set_config('webservice_userid', $webserviceuser->id, 'quiz_archiver'); + $log[] = " -> Web service user set to '{$webserviceuser->username}'."; + + set_config('worker_url', $workerurl, 'quiz_archiver'); + $log[] = " -> Worker URL set to '{$workerurl}'."; + } catch (\Exception $e) { + $log[] = "Error: Failed to set config settings for quiz_archiver plugin: ".$e->getMessage(); + throw new \RuntimeException(); + } + + $success = true; + } catch (\RuntimeException $e) { + $success = false; + } catch (\Exception $e) { + $success = false; + $log[] = "Error: An unexpected error occurred: ".$e->getMessage(); + } finally { + return [$success, implode("\r\n", $log)]; + } + } + +} diff --git a/cli/autoinstall.php b/cli/autoinstall.php new file mode 100644 index 0000000..9c5f024 --- /dev/null +++ b/cli/autoinstall.php @@ -0,0 +1,116 @@ +. + +/** + * quiz_archiver - automatic install script + * + * @package quiz_archiver + * @copyright 2024 Niels Gandraß + * 2024 Melanie Treitinger, Ruhr-Universität Bochum + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +/** @var bool Identifies this script as a CLI script */ +const CLI_SCRIPT = true; + +require_once(__DIR__ . '/../../../../../config.php'); +require_once("{$CFG->libdir}/clilib.php"); +require_once("{$CFG->dirroot}/mod/quiz/report/archiver/classes/local/autoinstall.php"); + +use quiz_archiver\local\autoinstall; + +####################### +# CLI options parsing # +####################### + +list($options, $unrecognised) = cli_get_params( + [ + 'help' => false, + 'workerurl' => 'http://localhost:8080', + 'wsname' => autoinstall::DEFAULT_WSNAME, + 'rolename' => autoinstall::DEFAULT_ROLESHORTNAME, + 'username' => autoinstall::DEFAULT_USERNAME, + 'force' => false, + ], + [ + 'h' => 'help', + 'f' => 'force', + ] +); + +$usage = << Sets the URL of the worker (default: http://localhost:8080) + --wsname= Sets a custom name for the web service (default: quiz_archiver_webservice) + --rolename= Sets a custom name for the web service role (default: quiz_archiver) + --username= Sets a custom username for the web service user (default: quiz_archiver_serviceaccount) +EOT; + +if ($unrecognised) { + $unrecognised = implode(PHP_EOL . ' ', $unrecognised); + cli_error(get_string('cliunknowoption', 'core_admin', $unrecognised)); +} + +if ($options['help']) { + cli_writeln($usage); + exit(2); +} + +################################ +# Begin of autoinstall routine # +################################ + +// Set admin user. +$USER = get_admin(); + +cli_writeln("Starting automatic installation of quiz archiver plugin..."); +cli_separator(); + +list($success, $log) = autoinstall::execute( + $options['workerurl'], + $options['wsname'], + $options['rolename'], + $options['username'], + $options['force'] +); + +cli_write($log."\r\n"); + +if ($success) { + cli_separator(); + cli_writeln("Automatic installation of quiz archiver plugin finished successfully."); + exit(0); +} else { + cli_writeln("Aborted."); + cli_separator(); + cli_writeln("FAILED: Automatic installation of quiz archiver plugin failed."); + exit(1); +} diff --git a/db/install.php b/db/install.php index 01e11e0..6bb852d 100644 --- a/db/install.php +++ b/db/install.php @@ -30,5 +30,17 @@ */ function xmldb_quiz_archiver_install() { + // Print welcome message + $autoinstall_url = new moodle_url('/mod/quiz/report/archiver/adminui/autoinstall.php'); + $pluginsettings_url = new moodle_url('/admin/settings.php', ['section' => 'quiz_archiver_settings']); + + echo ''; + + return true; } diff --git a/doc/configuration/configuration_plugin_autoinstall_2.png b/doc/configuration/configuration_plugin_autoinstall_2.png new file mode 100644 index 0000000..9b14255 Binary files /dev/null and b/doc/configuration/configuration_plugin_autoinstall_2.png differ diff --git a/doc/configuration/configuration_plugin_autoinstall_2_thumb.png b/doc/configuration/configuration_plugin_autoinstall_2_thumb.png new file mode 100644 index 0000000..7b6c8bc Binary files /dev/null and b/doc/configuration/configuration_plugin_autoinstall_2_thumb.png differ diff --git a/doc/configuration/configuration_plugin_autoinstall_3.png b/doc/configuration/configuration_plugin_autoinstall_3.png new file mode 100644 index 0000000..0f38fe6 Binary files /dev/null and b/doc/configuration/configuration_plugin_autoinstall_3.png differ diff --git a/doc/configuration/configuration_plugin_autoinstall_3_thumb.png b/doc/configuration/configuration_plugin_autoinstall_3_thumb.png new file mode 100644 index 0000000..e44489f Binary files /dev/null and b/doc/configuration/configuration_plugin_autoinstall_3_thumb.png differ diff --git a/doc/configuration/configuration_plugin_autoinstall_4.png b/doc/configuration/configuration_plugin_autoinstall_4.png new file mode 100644 index 0000000..5308d9b Binary files /dev/null and b/doc/configuration/configuration_plugin_autoinstall_4.png differ diff --git a/doc/configuration/configuration_plugin_autoinstall_4_thumb.png b/doc/configuration/configuration_plugin_autoinstall_4_thumb.png new file mode 100644 index 0000000..c449645 Binary files /dev/null and b/doc/configuration/configuration_plugin_autoinstall_4_thumb.png differ diff --git a/lang/de/quiz_archiver.php b/lang/de/quiz_archiver.php index bf9d518..14c0e79 100644 --- a/lang/de/quiz_archiver.php +++ b/lang/de/quiz_archiver.php @@ -28,6 +28,8 @@ $string['archiverreport'] = 'Quiz Archiver'; $string['checksum'] = 'Prüfsumme'; $string['beta_version_warning'] = 'Dieses Plugin befindet sich derzeit in der Beta-Phase. Bitte melden Sie alle Probleme und Fehler dem Website-Administrator.'; +$string['thanks_for_installing'] = 'Vielen Dank für die Installation des Quiz Archiver Plugins!'; +$string['go_to_plugin_settings'] = 'Plugin-Einstellungen öffnen'; // Capabilities $string['quiz_archiver:view'] = 'Quiz Archiver Berichtsseite anzeigen'; @@ -83,6 +85,7 @@ $string['export_report_section_attachments_help'] = 'Alle Dateiabgaben (z.B. von Aufsätzen/Essay Aufgaben) im Archiv einschließen. Warnung: Dies kann die Archivgröße erheblich erhöhen.'; $string['job_overview'] = 'Testarchive'; $string['num_attempts'] = 'Anzahl Testversuche'; +$string['error_plugin_is_not_configured'] = 'Fehler: Das Quiz Archiver Plugin ist noch nicht konfiguriert. Bitte kontaktieren Sie Ihren Website-Administrator.'; // Job creation form: Filename pattern $string['archive_filename_pattern'] = 'Archivname'; @@ -160,6 +163,7 @@ $string['tsp_client_error_http_code'] = 'TSP Server hat unerwarteten HTTP Statuscode {$a} zurückgegeben'; // Settings +$string['setting_autoconfigure'] = 'Automatische Konfiguration'; $string['setting_header_archive_worker'] = 'Archive Worker Service'; $string['setting_header_archive_worker_desc'] = 'Konfiguration des Archive Worker Services und des Moodle Webservices.'; $string['setting_header_docs_desc'] = 'Dieses Plugin archiviert Testversuche als PDF- und HTML-Dateien zur langfristigen Speicherung unabhängig von Moodle. Es erfordert die Installation eines separaten Archive Worker Services um korrekt zu funktionieren. Die Dokumentation enthält alle notwendigen Informationen und Installationsanweisungen.'; @@ -215,3 +219,20 @@ $string['task_autodelete_job_artifacts'] = 'Löschen abgelaufener Testarchive'; $string['task_autodelete_job_artifacts_start'] = 'Lösche abgelaufene Testarchive ...'; $string['task_autodelete_job_artifacts_report'] = '{$a} Testarchive gelöscht.'; + +// Autoinstall +$string['autoinstall_already_configured'] = 'Plugin ist bereits konfiguriert'; +$string['autoinstall_already_configured_long'] = 'Das Quiz Archiver Plugin ist bereits konfiguriert. Eine erneute automatische Konfiguration ist nicht möglich.'; +$string['autoinstall_cancelled'] = 'Die automatische Konfiguration des Quiz Archiver Plugins wurde abgebrochen. Es wurden keine Einstellungen verändert.'; +$string['autoinstall_explanation'] = 'Das Quiz Archiver Plugin erfordert anfangs einige Konfigurationsschritte, um zu funktionieren (siehe Installation). Sie können diese Einstellungen manuell konfigurieren, oder die automatische Konfiguration verwenden, um alle Moodle-bezogenen Einstellungen zu setzen.'; +$string['autoinstall_failure'] = 'Die automatische Konfiguration des Quiz Archiver Plugins ist fehlgeschlagen.'; +$string['autoinstall_plugin'] = 'Quiz Archiver: Automatische Konfiguration'; +$string['autoinstall_started'] = 'Automatische Konfiguration gestartet ...'; +$string['autoinstall_start_now'] = 'Automatische Konfiguration jetzt starten'; +$string['autoinstall_success'] = 'Die automatische Konfiguration des Quiz Archiver Plugins wurde erfolgreich abgeschlossen.'; +$string['autoinstall_rolename'] = 'Rollenname'; +$string['autoinstall_rolename_help'] = 'TODO'; +$string['autoinstall_username'] = 'Nutzername'; +$string['autoinstall_username_help'] = 'TODO'; +$string['autoinstall_wsname'] = 'Webservice name'; +$string['autoinstall_wsname_help'] = 'TODO'; diff --git a/lang/en/quiz_archiver.php b/lang/en/quiz_archiver.php index 7385834..6d7290b 100644 --- a/lang/en/quiz_archiver.php +++ b/lang/en/quiz_archiver.php @@ -28,6 +28,8 @@ $string['archiverreport'] = 'Quiz Archiver'; $string['checksum'] = 'Checksum'; $string['beta_version_warning'] = 'This plugin is currently in beta stage. Please report any problems and bugs you experience to the site administrator.'; +$string['thanks_for_installing'] = 'Thank you for installing the Quiz Archiver plugin!'; +$string['go_to_plugin_settings'] = 'Go to plugin settings'; // Capabilities $string['quiz_archiver:view'] = 'View quiz archiver report page'; @@ -83,6 +85,7 @@ $string['export_report_section_attachments_help'] = 'Include all file attachments (e.g., essay file submissions) inside the archive. Warning: This can significantly increase the archive size.'; $string['job_overview'] = 'Archives'; $string['num_attempts'] = 'Number of attempts'; +$string['error_plugin_is_not_configured'] = 'Error: The quiz archiver plugin is not configured yet. Please contact your site administrator.'; // Job creation form: Filename pattern $string['archive_filename_pattern'] = 'Archive name'; @@ -159,6 +162,7 @@ $string['tsp_client_error_http_code'] = 'TSP server returned HTTP status code {$a}'; // Settings +$string['setting_autoconfigure'] = 'Automatic configuration'; $string['setting_header_archive_worker'] = 'Archive Worker Service'; $string['setting_header_archive_worker_desc'] = 'Configuration of the archive worker service and the Moodle web service it uses.'; $string['setting_header_docs_desc'] = 'This plugin archives quiz attempts as PDF and HTML files for long-term storage, independent of Moodle. It requires a separate worker service to be installed for the actual archiving process to work. Please refer to the documentation for more details and setup instructions.'; @@ -212,3 +216,21 @@ $string['task_autodelete_job_artifacts'] = 'Delete expired quiz archives'; $string['task_autodelete_job_artifacts_start'] = 'Deleting expired quiz archives ...'; $string['task_autodelete_job_artifacts_report'] = 'Deleted {$a} quiz archives.'; + +// Autoinstall +$string['autoinstall_already_configured'] = 'Plugin is already configured'; +$string['autoinstall_already_configured_long'] = 'The Quiz Archiver Plugin is already configured. Automatic configuration is not possible twice.'; +$string['autoinstall_cancelled'] = 'The automatic configuration of the Quiz Archiver Plugin was cancelled. No changes were made.'; +$string['autoinstall_explanation'] = 'The Quiz Archiver plugin requires a few initial configuration steps to work (see Installation). You can either configure all of these settings manually or use the automatic configuration feature to take care of all Moodle related settings.'; +$string['autoinstall_explanation_details'] = 'The automatic configuration feature will take care of the following steps:
  • Enabling web services and REST protocol
  • Creating a quiz archiver service role and a corresponding user
  • Creating a new web service with all required webservice functions
  • Authorising the user to use the webservice
'; +$string['autoinstall_failure'] = 'The automatic configuration of the Quiz Archiver Plugin failed.'; +$string['autoinstall_plugin'] = 'Quiz Archiver: Automatic configuration'; +$string['autoinstall_started'] = 'Automatic configuration started ...'; +$string['autoinstall_start_now'] = 'Start automatic configuration now'; +$string['autoinstall_success'] = 'The automatic configuration of the Quiz Archiver Plugin was successful.'; +$string['autoinstall_rolename'] = 'Role name'; +$string['autoinstall_rolename_help'] = 'TODO'; +$string['autoinstall_username'] = 'Username'; +$string['autoinstall_username_help'] = 'TODO'; +$string['autoinstall_wsname'] = 'Web service name'; +$string['autoinstall_wsname_help'] = 'TODO'; diff --git a/report.php b/report.php index 1708ef4..7576386 100644 --- a/report.php +++ b/report.php @@ -29,6 +29,7 @@ use quiz_archiver\ArchiveJob; use quiz_archiver\BackupManager; use quiz_archiver\form\artifact_delete_form; +use quiz_archiver\local\autoinstall; use quiz_archiver\local\util; use quiz_archiver\RemoteArchiveWorker; use quiz_archiver\Report; @@ -347,6 +348,11 @@ protected function initiate_archive_job( // Check permissions. require_capability('mod/quiz_archiver:create', $this->context); + // Check if webservice is configured properly + if (autoinstall::plugin_is_unconfigured()) { + throw new \RuntimeException(get_string('error_plugin_is_not_configured', 'quiz_archiver')); + } + // Create temporary webservice token if (class_exists('core_external\util')) { // Moodle 4.2 and above diff --git a/settings.php b/settings.php index fd03c01..39a33bd 100644 --- a/settings.php +++ b/settings.php @@ -23,10 +23,13 @@ * @license https://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +require_once(__DIR__ . '/classes/local/autoinstall.php'); + use quiz_archiver\ArchiveJob; use quiz_archiver\local\admin\setting\admin_setting_archive_filename_pattern; use quiz_archiver\local\admin\setting\admin_setting_attempt_filename_pattern; use quiz_archiver\local\admin\setting\admin_setting_configcheckbox_alwaystrue; +use quiz_archiver\local\autoinstall; use quiz_archiver\Report; defined('MOODLE_INTERNAL') || die(); @@ -44,6 +47,19 @@ get_string('setting_header_docs_desc', 'quiz_archiver') )); + // Autoinstall + if (autoinstall::plugin_is_unconfigured()) { + $autoinstall_url = new moodle_url('/mod/quiz/report/archiver/adminui/autoinstall.php'); + $autoinstall_desc = "".get_string('autoinstall_start_now', 'quiz_archiver').""; + $autoinstall_desc .= "

".get_string('autoinstall_explanation', 'quiz_archiver')."

"; + } else { + $autoinstall_desc = get_string('autoinstall_already_configured', 'quiz_archiver'); + } + $settings->add(new admin_setting_description('quiz_archiver/autoinstall', + get_string('setting_autoconfigure', 'quiz_archiver'), + $autoinstall_desc + )); + // Generic settings $settings->add(new admin_setting_heading('quiz_archiver/header_archive_worker', get_string('setting_header_archive_worker', 'quiz_archiver'), diff --git a/tests/classes/external/get_attempts_metadata_test.php b/tests/classes/external/get_attempts_metadata_test.php index 20c893d..b103788 100644 --- a/tests/classes/external/get_attempts_metadata_test.php +++ b/tests/classes/external/get_attempts_metadata_test.php @@ -22,10 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace tests\classes\external; - - -use quiz_archiver\external\get_attempts_metadata; +namespace quiz_archiver\external; /** * Tests for the get_attempts_metadata external service diff --git a/tests/classes/external/get_backup_status_test.php b/tests/classes/external/get_backup_status_test.php index 7c60132..bc3328a 100644 --- a/tests/classes/external/get_backup_status_test.php +++ b/tests/classes/external/get_backup_status_test.php @@ -22,11 +22,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace tests\classes\external; +namespace quiz_archiver\external; use quiz_archiver\ArchiveJob; -use quiz_archiver\external\get_backup_status; /** * Tests for the get_backup_status external service diff --git a/tests/classes/external/process_uploaded_artifact_test.php b/tests/classes/external/process_uploaded_artifact_test.php index df30392..13379bb 100644 --- a/tests/classes/external/process_uploaded_artifact_test.php +++ b/tests/classes/external/process_uploaded_artifact_test.php @@ -22,11 +22,10 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ -namespace tests\classes\external; +namespace quiz_archiver\external; use quiz_archiver\ArchiveJob; -use quiz_archiver\external\process_uploaded_artifact; use quiz_archiver\FileManager; /** diff --git a/tests/classes/local/autoinstall_test.php b/tests/classes/local/autoinstall_test.php new file mode 100644 index 0000000..0c6f011 --- /dev/null +++ b/tests/classes/local/autoinstall_test.php @@ -0,0 +1,117 @@ +. + +/** + * Tests for the autoinstall class + * + * @package quiz_archiver + * @copyright 2024 Niels Gandraß + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace quiz_archiver\local; + +/** + * Tests for the autoinstall class + */ +class autoinstall_test extends \advanced_testcase { + + /** + * Test one full autoinstall process + * + * @return void + * @throws \dml_exception + */ + public function test_autoinstall(): void { + global $DB; + $this->resetAfterTest(); + + // Gain privileges + $this->setAdminUser(); + + // Execute autoinstall + $workerurl = 'http://foo.bar:1337'; + $wsname = 'test_webservice_name'; + $rolename = 'test_role_name'; + $username = 'test_user_name'; + + list($success, $log) = autoinstall::execute( + $workerurl, + $wsname, + $rolename, + $username + ); + + // Check function return + $this->assertTrue($success, 'Autoinstall returned success=false'); + $this->assertNotEmpty($log, 'Autoinstall returned empty log'); + + // Check worker URL + $this->assertSame($workerurl, get_config('quiz_archiver', 'worker_url'), 'Worker URL was not set correctly'); + + // Check global config + $this->assertEquals(true, get_config('moodle', 'enablewebservices'), 'Webservices were not globally enabled'); // This can not be assertTrue, since Moodle stores a '1' + $this->assertStringContainsString('rest', get_config('moodle', 'webserviceprotocols'), 'REST protocol was not globally enabled'); + + // Check webservice + $webservice = $DB->get_record('external_services', ['name' => $wsname]); + $this->assertNotEmpty($webservice, 'Webservice was not created'); + $this->assertSame($webservice->name, $wsname, 'Webservice name was not set correctly'); + $this->assertNotEmpty($DB->get_records('external_services_functions', ['externalserviceid' => $webservice->id]), 'Webservice functions were not assigned'); + $this->assertSame($webservice->id, get_config('quiz_archiver', 'webservice_id'), 'Webservice ID was not set correctly'); + + // Check role + $role = $DB->get_record('role', ['shortname' => $rolename]); + $this->assertNotEmpty($role, 'Role was not created'); + $this->assertNotEmpty($DB->get_records('role_capabilities', ['roleid' => $role->id]), 'Role capabilities were not assigned'); + + // Check user + $user = $DB->get_record('user', ['username' => $username]); + $this->assertNotEmpty($user, 'User was not created'); + $this->assertNotEmpty($DB->get_records('role_assignments', ['userid' => $user->id, 'roleid' => $role->id]), 'User role was not assigned'); + $this->assertSame($user->id, get_config('quiz_archiver', 'webservice_userid'), 'User ID was not set correctly'); + } + + /** + * Tests if autoinstalls are properly detected and repeated autoinstalls + * are prevented. + * + * @return void + * @throws \dml_exception + */ + public function test_autoinstall_detection(): void { + $this->resetAfterTest(); + + // Gain privileges + $this->setAdminUser(); + + // Plugin should be unconfigured + $this->assertTrue(autoinstall::plugin_is_unconfigured(), 'Plugin was not unconfigured'); + + // Perform autoinstall + list($success, $log) = autoinstall::execute('http://foo.bar:1337'); + $this->assertTrue($success, 'First autoinstall failed'); + + // Try to detect autoinstall + $this->assertFalse(autoinstall::plugin_is_unconfigured(), 'Successful autoinstall was not detected'); + + // Try to autoinstall a second time + list($success, $log) = autoinstall::execute('http://foo.bar:1337'); + $this->assertFalse($success, 'Second autoinstall was successful'); + $this->assertNotEmpty($log, 'Second autoinstall returned empty log'); + } + +}